1use crate::Engine;
9use crate::mmq_ffi::{DeviceExpertCsr, ExpertCsr, Fp8GroupedWorkspace};
10use crate::parallel::{PRODUCT_MAX_CARDS, STEP37_TRUNK_LAYERS};
11use cudarc::driver::{CudaEvent, CudaSlice, DevicePtr, DeviceSlice, LaunchConfig, PushKernelArg};
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_parallel_ep_q8_gu_paired(value: Option<&str>) -> Result<Option<bool>, String> {
1023 match value {
1024 None | Some("") => Ok(None),
1025 Some("0") => Ok(Some(false)),
1026 Some("1") => Ok(Some(true)),
1027 Some(value) => Err(format!(
1028 "MEMRA_PARALLEL_EP_Q8_GU_PAIRED={value:?} is invalid; expected 0 or 1"
1029 )),
1030 }
1031}
1032
1033fn resolve_parallel_ep_q8_gu_paired(
1034 value: Option<&str>,
1035 q8_active: bool,
1036 scope: Option<ParallelEpQ8Scope>,
1037) -> Result<bool, String> {
1038 let configured = parse_parallel_ep_q8_gu_paired(value)?;
1039 if configured == Some(true) && !q8_active {
1040 return Err("MEMRA_PARALLEL_EP_Q8_GU_PAIRED=1 requires MEMRA_PARALLEL_EP_Q8_ACT=1".into());
1041 }
1042 if configured == Some(true) && scope == Some(ParallelEpQ8Scope::Down) {
1043 return Err(
1044 "MEMRA_PARALLEL_EP_Q8_GU_PAIRED=1 requires Q8 gate/up arithmetic; \
1045 MEMRA_PARALLEL_EP_Q8_SCOPE=down keeps gate/up BF16"
1046 .into(),
1047 );
1048 }
1049 Ok(q8_active && scope != Some(ParallelEpQ8Scope::Down) && configured.unwrap_or(true))
1050}
1051
1052pub(crate) fn parallel_ep_q8_gu_paired_enabled(
1053 q8_active: bool,
1054 scope: Option<ParallelEpQ8Scope>,
1055) -> Result<bool, String> {
1056 resolve_parallel_ep_q8_gu_paired(
1057 std::env::var("MEMRA_PARALLEL_EP_Q8_GU_PAIRED")
1058 .ok()
1059 .as_deref(),
1060 q8_active,
1061 scope,
1062 )
1063}
1064
1065fn parse_step_layer_specs(
1066 flag: &str,
1067 value: Option<&str>,
1068 allow_full_model: bool,
1069) -> Result<Vec<StepEpLayerSpec>, String> {
1070 let trunk = allow_full_model.then_some(STEP37_TRUNK_LAYERS);
1071 parse_layer_specs_for_trunk(flag, value, trunk)
1072}
1073
1074pub(crate) fn refuse_door_composition(
1082 primary: &str,
1083 table: &[(&str, &str)],
1084 armed: impl Fn(&str) -> bool,
1085) -> Result<(), String> {
1086 for (flag, why) in table {
1087 if armed(flag) {
1088 return Err(format!(
1089 "{primary} + {flag}: unproven composition, refused ({why})"
1090 ));
1091 }
1092 }
1093 Ok(())
1094}
1095
1096pub(crate) fn parse_layer_specs_for_trunk(
1101 flag: &str,
1102 value: Option<&str>,
1103 full_model_trunk: Option<usize>,
1104) -> Result<Vec<StepEpLayerSpec>, String> {
1105 let Some(value) = value else {
1106 return Ok(Vec::new());
1107 };
1108 if value.is_empty() || value == "0" {
1109 return Ok(Vec::new());
1110 }
1111
1112 let mut specs = Vec::new();
1113 for item in value.split(';') {
1114 let (layers, devices) = item.split_once('@').ok_or_else(|| {
1115 let layers = if full_model_trunk.is_some() {
1116 "LAYER[-LAYER] or all"
1117 } else {
1118 "LAYER[-LAYER]"
1119 };
1120 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
1121 })?;
1122 let (first, last) = if layers == "all" {
1123 let Some(trunk) = full_model_trunk else {
1124 return Err(format!(
1125 "{flag} does not support the full-model shorthand; assign routed layers \
1126 explicitly"
1127 ));
1128 };
1129 (0, trunk - 1)
1130 } else {
1131 match layers.split_once('-') {
1132 Some((first, last)) => {
1133 let first = first
1134 .parse::<usize>()
1135 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
1136 let last = last
1137 .parse::<usize>()
1138 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
1139 if first > last {
1140 return Err(format!("{flag} layer range {first}-{last} is reversed"));
1141 }
1142 if last - first + 1 > 128 {
1143 return Err(format!(
1144 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
1145 ));
1146 }
1147 (first, last)
1148 }
1149 None => {
1150 let layer = layers
1151 .parse::<usize>()
1152 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
1153 (layer, layer)
1154 }
1155 }
1156 };
1157 let devices = devices
1158 .split(',')
1159 .map(|device| {
1160 device
1161 .parse::<usize>()
1162 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
1163 })
1164 .collect::<Result<Vec<_>, _>>()?;
1165 if !(2..=8).contains(&devices.len()) {
1166 return Err(format!(
1167 "{flag} requires 2..=8 devices, got {}",
1168 devices.len()
1169 ));
1170 }
1171 let mut unique = devices.clone();
1172 unique.sort_unstable();
1173 unique.dedup();
1174 if unique.len() != devices.len() {
1175 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
1176 }
1177 for layer in first..=last {
1178 if specs
1179 .iter()
1180 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
1181 {
1182 return Err(format!("{flag} assigns layer {layer} more than once"));
1183 }
1184 specs.push(StepEpLayerSpec {
1185 layer,
1186 devices: devices.clone(),
1187 });
1188 }
1189 }
1190 Ok(specs)
1191}
1192
1193pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
1194 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
1195}
1196
1197pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
1198 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
1199}
1200
1201pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
1202 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
1203}
1204
1205pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
1206 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
1207}
1208
1209#[derive(Clone, Copy)]
1210pub struct E4m3BlockMatrix<'a> {
1211 pub codes: &'a [u8],
1212 pub scales: &'a [f32],
1213 pub out_features: usize,
1214 pub in_features: usize,
1215}
1216
1217impl E4m3BlockMatrix<'_> {
1218 fn validate(&self) -> Result<(), String> {
1219 let code_count = self
1220 .out_features
1221 .checked_mul(self.in_features)
1222 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
1223 if self.codes.len() != code_count {
1224 return Err(format!(
1225 "E4M3 code count {} != {}x{} ({code_count})",
1226 self.codes.len(),
1227 self.out_features,
1228 self.in_features,
1229 ));
1230 }
1231 let scale_count =
1232 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1233 if self.scales.len() != scale_count {
1234 return Err(format!(
1235 "E4M3 scale count {} != {scale_count} for {}x{}",
1236 self.scales.len(),
1237 self.out_features,
1238 self.in_features,
1239 ));
1240 }
1241 if !self
1242 .scales
1243 .iter()
1244 .all(|scale| scale.is_finite() && *scale > 0.0)
1245 {
1246 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
1247 }
1248 Ok(())
1249 }
1250}
1251
1252#[derive(Clone, Copy)]
1253pub struct E4m3ExpertBank<'a> {
1254 pub codes: &'a [u8],
1255 pub scales: &'a [f32],
1256 pub expert_count: usize,
1257 pub out_features: usize,
1258 pub in_features: usize,
1259}
1260
1261impl E4m3ExpertBank<'_> {
1262 fn validate(&self) -> Result<(), String> {
1263 if self.expert_count == 0 {
1264 return Err("E4M3 expert bank is empty".to_string());
1265 }
1266 let code_stride = self
1267 .out_features
1268 .checked_mul(self.in_features)
1269 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
1270 let code_count = self
1271 .expert_count
1272 .checked_mul(code_stride)
1273 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
1274 if self.codes.len() != code_count {
1275 return Err(format!(
1276 "E4M3 expert code count {} != {}x{} ({code_count})",
1277 self.codes.len(),
1278 self.expert_count,
1279 code_stride,
1280 ));
1281 }
1282 let scale_stride =
1283 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1284 let scale_count = self
1285 .expert_count
1286 .checked_mul(scale_stride)
1287 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
1288 if self.scales.len() != scale_count {
1289 return Err(format!(
1290 "E4M3 expert scale count {} != {}x{} ({scale_count})",
1291 self.scales.len(),
1292 self.expert_count,
1293 scale_stride,
1294 ));
1295 }
1296 if !self
1297 .scales
1298 .iter()
1299 .all(|scale| scale.is_finite() && *scale > 0.0)
1300 {
1301 return Err(
1302 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
1303 );
1304 }
1305 Ok(())
1306 }
1307
1308 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
1309 if expert >= self.expert_count {
1310 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
1311 }
1312 let code_stride = self.out_features * self.in_features;
1313 let scale_stride =
1314 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1315 Ok(E4m3BlockMatrix {
1316 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
1317 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
1318 out_features: self.out_features,
1319 in_features: self.in_features,
1320 })
1321 }
1322}
1323
1324pub struct ColumnParallelResult {
1325 pub gathered: Vec<f32>,
1326 pub rank_outputs: Vec<Vec<f32>>,
1327}
1328
1329pub struct RowParallelResult {
1330 pub reduced: Vec<f32>,
1331 pub rank_partials: Vec<Vec<f32>>,
1332}
1333
1334#[derive(Clone, Copy)]
1335pub struct Bf16Matrix<'a> {
1336 pub bytes: &'a [u8],
1337 pub out_features: usize,
1338 pub in_features: usize,
1339}
1340
1341impl Bf16Matrix<'_> {
1342 pub fn validate(&self) -> Result<(), String> {
1343 if self.out_features == 0 || self.in_features == 0 {
1344 return Err("BF16 matrix dimensions must be nonzero".into());
1345 }
1346 let expected = self
1347 .out_features
1348 .checked_mul(self.in_features)
1349 .and_then(|values| values.checked_mul(2))
1350 .ok_or("BF16 matrix byte count overflow")?;
1351 if self.bytes.len() != expected {
1352 return Err(format!(
1353 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
1354 self.bytes.len(),
1355 self.out_features,
1356 self.in_features,
1357 ));
1358 }
1359 Ok(())
1360 }
1361}
1362
1363struct ResidentE4m3Rank {
1364 codes: CudaSlice<u8>,
1365 scales: CudaSlice<f32>,
1366 out_features: usize,
1367 in_features: usize,
1368}
1369
1370enum ResidentBf16Weight {
1371 Bf16(CudaSlice<u8>),
1372 F32(CudaSlice<f32>),
1373}
1374
1375impl ResidentBf16Weight {
1376 fn ordinal(&self) -> usize {
1377 match self {
1378 Self::Bf16(bytes) => bytes.ordinal(),
1379 Self::F32(values) => values.ordinal(),
1380 }
1381 }
1382}
1383
1384struct ResidentBf16Rank {
1385 weight: ResidentBf16Weight,
1386 out_features: usize,
1387 in_features: usize,
1388 q8: Option<CudaSlice<u8>>,
1391}
1392
1393pub struct ResidentColumnParallel {
1394 ranks: Vec<ResidentE4m3Rank>,
1395 out_features: usize,
1396 in_features: usize,
1397}
1398
1399pub struct ResidentRowParallel {
1400 ranks: Vec<ResidentE4m3Rank>,
1401 out_features: usize,
1402 in_features: usize,
1403}
1404
1405pub struct ResidentBf16ColumnParallel {
1406 ranks: Vec<ResidentBf16Rank>,
1407 out_features: usize,
1408 in_features: usize,
1409 canonical_chunk_rows: Option<usize>,
1410}
1411
1412pub struct ResidentBf16RowParallel {
1413 ranks: Vec<ResidentBf16Rank>,
1414 out_features: usize,
1415 in_features: usize,
1416}
1417
1418pub struct ResidentStepBf16RowParallel {
1419 ranks: Vec<Vec<ResidentBf16Rank>>,
1420 out_features: usize,
1421 in_features: usize,
1422 canonical_chunk_cols: usize,
1423}
1424
1425pub struct ResidentSigmoidTopKRouter {
1427 weight: CudaSlice<f32>,
1428 correction_bias: CudaSlice<f32>,
1429 active: CudaSlice<u8>,
1430 root_device: usize,
1431 input_width: usize,
1432 expert_count: usize,
1433 experts_per_token: usize,
1434 active_count: usize,
1435 scaling_factor: f32,
1436 route_norm: bool,
1437}
1438
1439pub struct SigmoidTopKHostOutput {
1440 pub logits: Vec<f32>,
1441 pub selected: Vec<u32>,
1442 pub weights: Vec<f32>,
1443}
1444
1445pub struct ResidentReplicatedBf16SwiGlu {
1447 gate: Vec<ResidentBf16Rank>,
1448 up: Vec<ResidentBf16Rank>,
1449 down: Vec<ResidentBf16Rank>,
1450 input_width: usize,
1451 intermediate_width: usize,
1452}
1453
1454pub struct ResidentReplicatedDeviceRows {
1459 ranks: Vec<CudaSlice<f32>>,
1460 tokens: usize,
1461 width: usize,
1462}
1463
1464impl ResidentReplicatedDeviceRows {
1465 pub fn tokens(&self) -> usize {
1466 self.tokens
1467 }
1468
1469 pub fn width(&self) -> usize {
1470 self.width
1471 }
1472
1473 pub fn ranks(&self) -> usize {
1474 self.ranks.len()
1475 }
1476}
1477
1478pub fn moe_residual_host(
1480 residual: &[f32],
1481 routed: &[f32],
1482 shared: &[f32],
1483) -> Result<Vec<f32>, String> {
1484 if residual.len() != routed.len() || residual.len() != shared.len() {
1485 return Err(format!(
1486 "MoE residual lengths residual={} routed={} shared={}",
1487 residual.len(),
1488 routed.len(),
1489 shared.len()
1490 ));
1491 }
1492 let ffn = routed
1493 .iter()
1494 .zip(shared)
1495 .map(|(&routed, &shared)| routed + shared)
1496 .collect::<Vec<_>>();
1497 Ok(residual
1498 .iter()
1499 .zip(ffn)
1500 .map(|(&residual, ffn)| residual + ffn)
1501 .collect())
1502}
1503
1504pub use memra_kv::{
1505 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1506};
1507
1508pub struct ResidentTpExpert {
1514 gate: ResidentColumnParallel,
1515 up: ResidentColumnParallel,
1516 down: ResidentRowParallel,
1517 input_width: usize,
1518 expert_width: usize,
1519}
1520
1521struct ResidentE4m3ExpertBankRank {
1522 codes: CudaSlice<u8>,
1523 scales: CudaSlice<f32>,
1524 expert_range: Range<usize>,
1525 out_features: usize,
1526 in_features: usize,
1527 code_stride: usize,
1528 scale_stride: usize,
1529 k_blocks: Option<usize>,
1532}
1533
1534struct PackedE4m3ExpertBankRank {
1535 codes: Vec<u8>,
1536 scales: Vec<f32>,
1537 expert_range: Range<usize>,
1538 out_features: usize,
1539 in_features: usize,
1540 code_stride: usize,
1541 scale_stride: usize,
1542 k_blocks: Option<usize>,
1543}
1544
1545struct ResidentEpRank {
1546 gate: ResidentE4m3ExpertBankRank,
1547 up: ResidentE4m3ExpertBankRank,
1548 down: ResidentE4m3ExpertBankRank,
1549}
1550
1551pub struct ResidentExpertParallel {
1558 ranks: Vec<ResidentEpRank>,
1559 expert_count: usize,
1560 input_width: usize,
1561 expert_width: usize,
1562}
1563
1564pub struct StepGroupedFp8ProjectionOutput {
1569 pub gate: Vec<f32>,
1570 pub up: Vec<f32>,
1571 pub down: Vec<f32>,
1572}
1573
1574pub struct PreparedStepGroupedFp8Gate {
1579 device: usize,
1580 gate: ResidentE4m3ExpertBankRank,
1581 up: ResidentE4m3ExpertBankRank,
1582 down: ResidentE4m3ExpertBankRank,
1583 input: CudaSlice<f32>,
1584 route_csr: DeviceExpertCsr,
1585 down_csr: DeviceExpertCsr,
1586 gate_workspace: Fp8GroupedWorkspace,
1587 up_workspace: Fp8GroupedWorkspace,
1588 down_workspace: Fp8GroupedWorkspace,
1589 activation: CudaSlice<f32>,
1590 activation_limit: Option<f32>,
1591 tokens: usize,
1592 pairs: usize,
1593}
1594
1595impl PreparedStepGroupedFp8Gate {
1596 pub fn tokens(&self) -> usize {
1597 self.tokens
1598 }
1599
1600 pub fn pairs(&self) -> usize {
1601 self.pairs
1602 }
1603}
1604
1605struct PreparedStepGroupedExpertOwner {
1606 rank: usize,
1607 global_pairs: Vec<usize>,
1608 route_csr: DeviceExpertCsr,
1609 down_csr: DeviceExpertCsr,
1610 gate_workspace: Fp8GroupedWorkspace,
1611 up_workspace: Fp8GroupedWorkspace,
1612 down_workspace: Fp8GroupedWorkspace,
1613 activation: CudaSlice<f32>,
1614}
1615
1616struct StepGroupedExpertOwnerSchedule {
1617 global_pairs: Vec<usize>,
1618 route_csr: ExpertCsr,
1619 down_csr: ExpertCsr,
1620}
1621
1622pub struct PreparedStepGroupedExpertParallelGate {
1628 rank_inputs: Vec<CudaSlice<f32>>,
1629 owners: Vec<PreparedStepGroupedExpertOwner>,
1630 activation_limit: Option<f32>,
1631 tokens: usize,
1632 pairs: usize,
1633 max_tokens: usize,
1634 max_pairs: usize,
1635 input_width: usize,
1636 expert_width: usize,
1637 generation: u64,
1638 executed_generation: Option<u64>,
1639 ready: bool,
1640}
1641
1642impl PreparedStepGroupedExpertParallelGate {
1643 pub fn tokens(&self) -> usize {
1644 self.tokens
1645 }
1646
1647 pub fn pairs(&self) -> usize {
1648 self.pairs
1649 }
1650
1651 pub fn max_tokens(&self) -> usize {
1652 self.max_tokens
1653 }
1654
1655 pub fn input_width(&self) -> usize {
1656 self.input_width
1657 }
1658
1659 pub fn expert_width(&self) -> usize {
1660 self.expert_width
1661 }
1662
1663 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1664 validate_step_expert_activation_limit(limit)?;
1665 self.activation_limit = limit;
1666 self.executed_generation = None;
1667 Ok(())
1668 }
1669
1670 pub fn active_owners(&self) -> usize {
1671 self.owners
1672 .iter()
1673 .filter(|owner| !owner.global_pairs.is_empty())
1674 .count()
1675 }
1676
1677 pub fn owner_pair_counts(&self) -> Vec<usize> {
1678 self.owners
1679 .iter()
1680 .map(|owner| owner.global_pairs.len())
1681 .collect()
1682 }
1683
1684 pub fn generation(&self) -> u64 {
1685 self.generation
1686 }
1687}
1688
1689struct PreparedPeerWeightedRouteOwner {
1690 token_rows: CudaSlice<i32>,
1691 slots: CudaSlice<i32>,
1692 weights: CudaSlice<f32>,
1693 active_pairs: usize,
1694}
1695
1696pub struct PreparedPeerWeightedRouteCombine {
1702 root_device: usize,
1703 owners: Vec<PreparedPeerWeightedRouteOwner>,
1704 peer_staging: CudaSlice<f32>,
1705 slots: CudaSlice<f32>,
1706 weights: CudaSlice<f32>,
1707 output: CudaSlice<f32>,
1708 peer_devices: Vec<usize>,
1709 peer_outputs: Vec<CudaSlice<f32>>,
1710 width: usize,
1711 experts_per_token: usize,
1712 max_tokens: usize,
1713 max_pairs: usize,
1714 tokens: usize,
1715 pairs: usize,
1716 projection_generation: u64,
1717 output_generation: Option<u64>,
1718 broadcast_generation: Option<u64>,
1719 ready: bool,
1720}
1721
1722impl PreparedPeerWeightedRouteCombine {
1723 pub fn tokens(&self) -> usize {
1724 self.tokens
1725 }
1726
1727 pub fn pairs(&self) -> usize {
1728 self.pairs
1729 }
1730
1731 pub fn owner_pair_counts(&self) -> Vec<usize> {
1732 self.owners.iter().map(|owner| owner.active_pairs).collect()
1733 }
1734
1735 pub fn distributed_ranks(&self) -> usize {
1736 1 + self.peer_outputs.len()
1737 }
1738}
1739
1740struct ResidentTpExpertBank {
1741 gate: Vec<ResidentE4m3ExpertBankRank>,
1742 up: Vec<ResidentE4m3ExpertBankRank>,
1743 down: Vec<ResidentE4m3ExpertBankRank>,
1744 expert_count: usize,
1745 input_width: usize,
1746 expert_width: usize,
1747}
1748
1749pub struct ResidentTensorParallel {
1755 bank: ResidentTpExpertBank,
1756}
1757
1758pub struct TpE4m3HostBounce {
1764 devices: Vec<usize>,
1765 ranks: Vec<Engine>,
1766 native_p2p: bool,
1767 ep_device_arithmetic: bool,
1768 bulk_p2p: bool,
1769 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1772}
1773
1774pub struct TpKvVerifiedLayer<'a> {
1775 pub cache: &'a mut ResidentTpKvCache,
1776 pub start: usize,
1777 pub logical_len: usize,
1778 pub source_k_raw: u64,
1779 pub source_v_raw: u64,
1780 pub source_k_tok_bytes: usize,
1781 pub source_v_tok_bytes: usize,
1782}
1783
1784pub struct Tp2ReplicatedRowJoin {
1792 width: usize,
1793 parity: usize,
1794 stage0: [CudaSlice<f32>; 2],
1795 stage1: [CudaSlice<f32>; 2],
1796 stage0_raw: [u64; 2],
1797 stage1_raw: [u64; 2],
1798 event0: [CudaEvent; 2],
1799 event1: [CudaEvent; 2],
1800}
1801
1802fn validate_tp2_replicated_row_join(
1803 ranks: usize,
1804 native_p2p: bool,
1805 width: usize,
1806) -> Result<(), String> {
1807 if ranks != 2 {
1808 return Err(format!(
1809 "replicated-row join requires exactly two ranks, got {ranks}"
1810 ));
1811 }
1812 if !native_p2p {
1813 return Err("replicated-row join requires native P2P".into());
1814 }
1815 if width == 0 || width > i32::MAX as usize {
1816 return Err(format!(
1817 "replicated-row join width must be in 1..={}, got {width}",
1818 i32::MAX
1819 ));
1820 }
1821 Ok(())
1822}
1823
1824fn launch_tp2_peer_push(
1825 engine: &Engine,
1826 source: &CudaSlice<f32>,
1827 destination: u64,
1828 width: usize,
1829) -> Result<(), Box<dyn std::error::Error>> {
1830 let function = engine.func("q4e_push_f32");
1831 let config = LaunchConfig::for_num_elems(width as u32);
1832 let width = width as i64;
1833 let stream = engine.gpu.stream();
1834 let mut launch = stream.launch_builder(&function);
1835 launch.arg(source).arg(&destination).arg(&width);
1836 unsafe {
1837 launch.launch(config)?;
1838 }
1839 Ok(())
1840}
1841
1842pub enum StepTpGateShards<'a> {
1851 F32(&'a [crate::CudaSlice<f32>]),
1852 Bf16(&'a [crate::CudaSlice<u8>]),
1853}
1854
1855pub struct StepTpDecodeV2Ws {
1856 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1860 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1861 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1862 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1863 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1864 pub(crate) tcol_cap: usize,
1865 w8_aq: Vec<CudaSlice<i8>>,
1869 w8_ad: Vec<CudaSlice<f32>>,
1870 w8_in: usize,
1871 w8o_aq: Vec<CudaSlice<i8>>,
1874 w8o_ad: Vec<CudaSlice<f32>>,
1875 w8o_in: usize,
1876 w8t_aq: Vec<CudaSlice<i8>>,
1880 w8t_ad: Vec<CudaSlice<f32>>,
1881 w8t_in: usize,
1882 w8t_oaq: Vec<CudaSlice<i8>>,
1883 w8t_oad: Vec<CudaSlice<f32>>,
1884 w8t_oin: usize,
1885 w8t_cap: usize,
1886 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1893 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1894 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1895 pub(crate) fa2_cap: usize,
1896 rope_k_t: Vec<CudaSlice<f32>>,
1900 rope_ctr_t: Vec<CudaSlice<u32>>,
1901 rope_pos_t: Vec<CudaSlice<i32>>,
1902 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1906 rows_tab_t: Vec<CudaSlice<u64>>,
1916 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1920 tcol_gated: Vec<CudaSlice<f32>>,
1921 tcol_opart: Vec<CudaSlice<f32>>,
1922 tcol_opeer: Option<CudaSlice<f32>>,
1923 tcol_omix: Option<CudaSlice<f32>>,
1924 tcol_ocap: usize,
1925 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1928 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1929 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1930 pub(crate) q: Vec<CudaSlice<f32>>,
1931 pub(crate) k: Vec<CudaSlice<f32>>,
1932 pub(crate) pos: Vec<CudaSlice<i32>>,
1933 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1935 pub(crate) gate: Vec<CudaSlice<f32>>,
1936 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1937 pub(crate) gated: Vec<CudaSlice<f32>>,
1938 o_partials: Vec<Vec<CudaSlice<f32>>>,
1940 raw_o_partials: Vec<Vec<u64>>,
1944 raw_k: Vec<u64>,
1945 raw_v_raw: Vec<u64>,
1946 ev_rank: Vec<CudaEvent>,
1948 peer_partial: CudaSlice<f32>,
1950 reduce_a: CudaSlice<f32>,
1951 reduce_b: CudaSlice<f32>,
1952 zeros: CudaSlice<f32>,
1954 pub(crate) k_shadow: CudaSlice<f32>,
1955 pub(crate) v_shadow: CudaSlice<f32>,
1956 ev_refresh: CudaEvent,
1957 ev_oproj: CudaEvent,
1958 gate_e: CudaSlice<f32>,
1960 pub(crate) h_stage: Option<CudaSlice<f32>>,
1963 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1964 attn_in: Vec<CudaSlice<f32>>,
1968 raw_h_stage: u64,
1970 raw_pos_stage: u64,
1971 raw_attn_in: Vec<u64>,
1972 raw_pos: Vec<u64>,
1973 raw_o_partial1: u64,
1974 raw_peer_partial: u64,
1975 raw_k1: u64,
1976 raw_v1: u64,
1977 raw_k_shadow: u64,
1978 raw_v_shadow: u64,
1979 raw_mixed_stage_e: u64,
1983 raw_reduce_a: u64,
1984 raw_shadow_stage_e: (u64, u64),
1985 ev_entry: CudaEvent,
1986 e_device: usize,
1987 local_q_dim: usize,
1989 local_kv_dim: usize,
1990 heads: usize,
1991 pub(crate) o_out: usize,
1992 o_block_cols: usize,
1993 blocks_per_rank: usize,
1994}
1995
1996impl TpE4m3HostBounce {
1997 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1998 Self::new_inner(devices, false, false, false, false)
1999 }
2000
2001 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
2002 Self::new_inner(devices, false, true, false, false)
2003 }
2004
2005 pub fn new_native_p2p_device_arithmetic(
2006 devices: &[usize],
2007 ) -> Result<Self, Box<dyn std::error::Error>> {
2008 Self::new_inner(devices, false, true, true, false)
2009 }
2010
2011 pub(crate) fn new_configured(
2012 devices: &[usize],
2013 native_p2p: bool,
2014 ep_device_arithmetic: bool,
2015 bulk_p2p: bool,
2016 ) -> Result<Self, Box<dyn std::error::Error>> {
2017 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
2018 }
2019
2020 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
2025 Self::new_inner(&[device], true, false, false, false)
2026 }
2027
2028 fn new_inner(
2029 devices: &[usize],
2030 allow_single_rank: bool,
2031 native_p2p: bool,
2032 ep_device_arithmetic: bool,
2033 bulk_p2p: bool,
2034 ) -> Result<Self, Box<dyn std::error::Error>> {
2035 if ep_device_arithmetic && !native_p2p {
2036 return Err("device-resident EP arithmetic requires native P2P".into());
2037 }
2038 if bulk_p2p && !native_p2p {
2039 return Err("bulk TP transport requires native P2P".into());
2040 }
2041 let minimum = if allow_single_rank { 1 } else { 2 };
2042 if !(minimum..=8).contains(&devices.len()) {
2043 return Err(format!(
2044 "TP reference requires {minimum}..=8 devices, got {}",
2045 devices.len()
2046 )
2047 .into());
2048 }
2049 let mut unique = devices.to_vec();
2050 unique.sort_unstable();
2051 unique.dedup();
2052 if unique.len() != devices.len() {
2053 return Err(format!("TP devices must be distinct, got {devices:?}").into());
2054 }
2055 let ranks = devices
2056 .iter()
2057 .map(|&device| Engine::new(device))
2058 .collect::<Result<Vec<_>, _>>()?;
2059 if native_p2p {
2060 configure_native_p2p(&ranks, devices)?;
2061 }
2062 if allow_single_rank {
2063 eprintln!(
2064 "[tp] canonical oracle transport=local device={} performance_claim=false",
2065 devices[0]
2066 );
2067 } else if native_p2p {
2068 if ep_device_arithmetic {
2069 eprintln!(
2070 "[tp] correctness transport=native-p2p devices={devices:?} \
2071 native_p2p=true activation=device-host-exact \
2072 accumulation=device-host-exact output=root-readback \
2073 bulk_p2p={bulk_p2p} performance_claim=false"
2074 );
2075 } else {
2076 eprintln!(
2077 "[tp] correctness transport=native-p2p devices={devices:?} \
2078 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
2079 performance_claim=false"
2080 );
2081 }
2082 } else {
2083 eprintln!(
2084 "[tp] correctness transport=host-bounce devices={devices:?} \
2085 native_p2p=false performance_claim=false"
2086 );
2087 }
2088 Ok(Self {
2089 devices: devices.to_vec(),
2090 ranks,
2091 native_p2p,
2092 ep_device_arithmetic,
2093 bulk_p2p,
2094 decode_v2: std::sync::Mutex::new(Vec::new()),
2095 })
2096 }
2097
2098 pub fn devices(&self) -> &[usize] {
2099 &self.devices
2100 }
2101
2102 pub fn native_p2p(&self) -> bool {
2103 self.native_p2p
2104 }
2105
2106 pub fn bulk_p2p(&self) -> bool {
2107 self.bulk_p2p
2108 }
2109
2110 pub fn expert_activation_label(&self) -> &'static str {
2111 if self.ep_device_arithmetic {
2112 "device-host-exact"
2113 } else {
2114 "host-canonical"
2115 }
2116 }
2117
2118 pub fn expert_accumulation_label(&self) -> &'static str {
2119 self.expert_activation_label()
2120 }
2121
2122 pub fn expert_output_label(&self) -> &'static str {
2123 if self.ep_device_arithmetic {
2124 "root-readback"
2125 } else {
2126 "host-accumulated"
2127 }
2128 }
2129
2130 pub fn transport_label(&self) -> &'static str {
2131 if self.devices.len() == 1 {
2132 "local"
2133 } else if self.native_p2p {
2134 "native-p2p"
2135 } else {
2136 "host-bounce"
2137 }
2138 }
2139
2140 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
2141 self.ranks
2142 .iter()
2143 .map(|rank| rank.ctx().name().map_err(Into::into))
2144 .collect()
2145 }
2146
2147 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
2153 self.ranks.get(rank)
2154 }
2155
2156 pub fn prepare_tp2_replicated_row_join(
2159 &self,
2160 width: usize,
2161 ) -> Result<Tp2ReplicatedRowJoin, Box<dyn std::error::Error>> {
2162 validate_tp2_replicated_row_join(self.ranks.len(), self.native_p2p, width)?;
2163 let rank0 = &self.ranks[0];
2164 let rank1 = &self.ranks[1];
2165
2166 let (stage0, stage0_raw, event0) = {
2167 let _main = rank0.gpu.enter_main()?;
2168 let stage = [rank0.zeros(width)?, rank0.zeros(width)?];
2169 let stream = rank0.gpu.stream();
2170 let raw = [
2171 stage[0].device_ptr(&stream).0,
2172 stage[1].device_ptr(&stream).0,
2173 ];
2174 let events = [rank0.ctx().new_event(None)?, rank0.ctx().new_event(None)?];
2175 (stage, raw, events)
2176 };
2177 let (stage1, stage1_raw, event1) = {
2178 let _main = rank1.gpu.enter_main()?;
2179 let stage = [rank1.zeros(width)?, rank1.zeros(width)?];
2180 let stream = rank1.gpu.stream();
2181 let raw = [
2182 stage[0].device_ptr(&stream).0,
2183 stage[1].device_ptr(&stream).0,
2184 ];
2185 let events = [rank1.ctx().new_event(None)?, rank1.ctx().new_event(None)?];
2186 (stage, raw, events)
2187 };
2188 Ok(Tp2ReplicatedRowJoin {
2189 width,
2190 parity: 0,
2191 stage0,
2192 stage1,
2193 stage0_raw,
2194 stage1_raw,
2195 event0,
2196 event1,
2197 })
2198 }
2199
2200 pub fn tp2_replicated_row_join(
2204 &self,
2205 join: &mut Tp2ReplicatedRowJoin,
2206 partial0: &CudaSlice<f32>,
2207 partial1: &CudaSlice<f32>,
2208 output0: &mut CudaSlice<f32>,
2209 output1: &mut CudaSlice<f32>,
2210 ) -> Result<(), Box<dyn std::error::Error>> {
2211 validate_tp2_replicated_row_join(self.ranks.len(), self.native_p2p, join.width)?;
2212 let rank0 = &self.ranks[0];
2213 let rank1 = &self.ranks[1];
2214 let width = join.width;
2215 if partial0.len() < width
2216 || partial1.len() < width
2217 || output0.len() < width
2218 || output1.len() < width
2219 || partial0.ordinal() != rank0.ctx().ordinal()
2220 || output0.ordinal() != rank0.ctx().ordinal()
2221 || partial1.ordinal() != rank1.ctx().ordinal()
2222 || output1.ordinal() != rank1.ctx().ordinal()
2223 {
2224 return Err("replicated-row join buffer geometry or ownership mismatch".into());
2225 }
2226
2227 let parity = join.parity;
2228 {
2229 let _main = rank0.gpu.enter_main()?;
2230 launch_tp2_peer_push(rank0, partial0, join.stage1_raw[parity], width)?;
2231 join.event0[parity].record(&rank0.gpu.stream())?;
2232 }
2233 {
2234 let _main = rank1.gpu.enter_main()?;
2235 launch_tp2_peer_push(rank1, partial1, join.stage0_raw[parity], width)?;
2236 join.event1[parity].record(&rank1.gpu.stream())?;
2237 }
2238 {
2239 let _main = rank0.gpu.enter_main()?;
2240 rank0.gpu.stream().wait(&join.event1[parity])?;
2241 rank0.add(partial0, &join.stage0[parity], output0, width)?;
2242 }
2243 {
2244 let _main = rank1.gpu.enter_main()?;
2245 rank1.gpu.stream().wait(&join.event0[parity])?;
2246 rank1.add(&join.stage1[parity], partial1, output1, width)?;
2247 }
2248 join.parity ^= 1;
2249 Ok(())
2250 }
2251
2252 pub fn allocate_tp_kv_cache(
2253 &self,
2254 kv_dim_k: usize,
2255 kv_dim_v: usize,
2256 capacity: usize,
2257 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2258 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
2259 }
2260
2261 pub fn allocate_tp_swa_kv_cache(
2262 &self,
2263 kv_dim_k: usize,
2264 kv_dim_v: usize,
2265 capacity: usize,
2266 window: usize,
2267 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2268 if window == 0 {
2269 return Err("TP SWA KV window must be nonzero".into());
2270 }
2271 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
2272 }
2273
2274 fn allocate_tp_kv_cache_inner(
2275 &self,
2276 kv_dim_k: usize,
2277 kv_dim_v: usize,
2278 capacity: usize,
2279 window: Option<usize>,
2280 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2281 if capacity == 0 || capacity > i32::MAX as usize {
2282 return Err(
2283 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
2284 );
2285 }
2286 let tp = self.ranks.len();
2287 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
2288 let physical_rows = window
2289 .map(|window| crate::cache::swa_ring_rows(window, capacity))
2290 .unwrap_or(capacity);
2291 let k_plane_bytes = physical_rows
2292 .checked_mul(shape.k_token_bytes)
2293 .and_then(|bytes| bytes.checked_add(8))
2294 .ok_or("TP KV K plane-byte overflow")?;
2295 let v_plane_bytes = physical_rows
2296 .checked_mul(shape.v_token_bytes)
2297 .and_then(|bytes| bytes.checked_add(8))
2298 .ok_or("TP KV V plane-byte overflow")?;
2299 let mut ranks = Vec::with_capacity(tp);
2300 for engine in &self.ranks {
2301 let _main = engine.gpu.enter_main()?;
2302 ranks.push(ResidentTpKvCacheRank::new(
2303 engine.alloc_u8(k_plane_bytes)?,
2304 engine.alloc_u8(v_plane_bytes)?,
2305 engine.htod_i32(&[0])?,
2306 ));
2307 }
2308 Ok(match window {
2309 Some(window) => ResidentTpKvCache::new_swa(
2310 ranks,
2311 shape.kv_dim_k,
2312 shape.kv_dim_v,
2313 shape.k_token_bytes,
2314 shape.v_token_bytes,
2315 capacity,
2316 window,
2317 ),
2318 None => ResidentTpKvCache::new(
2319 ranks,
2320 shape.kv_dim_k,
2321 shape.kv_dim_v,
2322 shape.k_token_bytes,
2323 shape.v_token_bytes,
2324 capacity,
2325 ),
2326 })
2327 }
2328
2329 pub fn grow_tp_kv_cache(
2330 &self,
2331 source: &ResidentTpKvCache,
2332 target_capacity: usize,
2333 rows: usize,
2334 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2335 self.validate_tp_kv_cache(source)?;
2336 let plan = source.prepare_grow(target_capacity, rows)?;
2337 let ranks = self.ranks.len();
2338 let global_k = source
2339 .kv_dim_k()
2340 .checked_mul(ranks)
2341 .ok_or("TP KV grow global K dimension overflow")?;
2342 let global_v = source
2343 .kv_dim_v()
2344 .checked_mul(ranks)
2345 .ok_or("TP KV grow global V dimension overflow")?;
2346 let mut target = match source.ring_window() {
2347 Some(window) => {
2348 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
2349 }
2350 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
2351 };
2352 self.validate_tp_kv_cache(&target)?;
2353
2354 for (rank, engine) in self.ranks.iter().enumerate() {
2355 let _main = engine.gpu.enter_main()?;
2356 let src = source
2357 .rank(rank)
2358 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
2359 let dst = target
2360 .rank_mut(rank)
2361 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
2362 if plan.k_bytes() > 0 {
2363 engine.copy_u8_range_into(
2364 dst.k_mut(),
2365 0,
2366 src.k(),
2367 plan.source_row() * source.k_tok_bytes(),
2368 plan.k_bytes(),
2369 )?;
2370 }
2371 if plan.v_bytes() > 0 {
2372 engine.copy_u8_range_into(
2373 dst.v_mut(),
2374 0,
2375 src.v(),
2376 plan.source_row() * source.v_tok_bytes(),
2377 plan.v_bytes(),
2378 )?;
2379 }
2380 }
2381 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
2382
2383 for engine in &self.ranks {
2386 let _main = engine.gpu.enter_main()?;
2387 engine.stream().synchronize()?;
2388 }
2389 let physical_copy_rows = plan.copy_rows();
2390 target.publish_grow(plan)?;
2391 eprintln!(
2392 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
2393 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
2394 rank_streams_synchronized=true generation_preserved=true",
2395 rows,
2396 source.capacity(),
2397 target_capacity,
2398 ranks,
2399 physical_copy_rows,
2400 source.ring_window(),
2401 );
2402 Ok(target)
2403 }
2404
2405 pub fn hydrate_tp_kv_cache(
2406 &self,
2407 cache: &mut ResidentTpKvCache,
2408 rows: usize,
2409 k_rows: &[u8],
2410 v_rows: &[u8],
2411 ) -> Result<(), Box<dyn std::error::Error>> {
2412 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
2413 }
2414
2415 pub fn hydrate_tp_kv_cache_from(
2416 &self,
2417 cache: &mut ResidentTpKvCache,
2418 logical_len: usize,
2419 resident_start: usize,
2420 k_rows: &[u8],
2421 v_rows: &[u8],
2422 ) -> Result<(), Box<dyn std::error::Error>> {
2423 self.validate_tp_kv_cache(cache)?;
2424 if cache.committed_len() != 0 || cache.staged_len() != 0 {
2425 return Err(format!(
2426 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
2427 cache.committed_len(),
2428 cache.staged_len()
2429 )
2430 .into());
2431 }
2432 if resident_start > logical_len || logical_len > cache.capacity() {
2433 return Err(format!(
2434 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
2435 cache.capacity(),
2436 )
2437 .into());
2438 }
2439 let rows = logical_len - resident_start;
2440 if rows > cache.physical_capacity() {
2441 return Err(format!(
2442 "TP KV hydration rows {rows} exceed physical capacity {}",
2443 cache.physical_capacity()
2444 )
2445 .into());
2446 }
2447 for rank in 0..self.ranks.len() {
2448 let k_rank =
2449 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
2450 let v_rank =
2451 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
2452 let engine = &self.ranks[rank];
2453 let _main = engine.gpu.enter_main()?;
2454 let rank_cache = cache
2455 .rank_mut(rank)
2456 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2457 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
2458 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
2459 }
2460 cache.publish_hydration(logical_len, resident_start)?;
2461 Ok(())
2462 }
2463
2464 #[allow(clippy::too_many_arguments)]
2469 pub fn restore_tp_kv_rows_from_device(
2470 &self,
2471 cache: &mut ResidentTpKvCache,
2472 start: usize,
2473 logical_len: usize,
2474 source_k_raw: u64,
2475 source_v_raw: u64,
2476 source_k_tok_bytes: usize,
2477 source_v_tok_bytes: usize,
2478 ) -> Result<(), Box<dyn std::error::Error>> {
2479 self.validate_tp_kv_cache(cache)?;
2480 if cache.committed_len() != cache.staged_len() {
2481 return Err(format!(
2482 "TP KV verify restore requires quiescent state, got committed/staged={}/{}",
2483 cache.committed_len(),
2484 cache.staged_len()
2485 )
2486 .into());
2487 }
2488 if start > logical_len || logical_len > cache.capacity() {
2489 return Err(format!(
2490 "TP KV verify restore range [{start},{logical_len}) exceeds capacity {}",
2491 cache.capacity()
2492 )
2493 .into());
2494 }
2495 let rows = logical_len - start;
2496 let physical = cache.physical_range(start, logical_len)?;
2497 if physical.len() != rows {
2498 return Err(format!(
2499 "TP KV verify restore range [{start},{logical_len}) is not physically contiguous"
2500 )
2501 .into());
2502 }
2503 let ranks = self.ranks.len();
2504 let k_tok_bytes = cache.k_tok_bytes();
2505 let v_tok_bytes = cache.v_tok_bytes();
2506 if source_k_tok_bytes != k_tok_bytes * ranks || source_v_tok_bytes != v_tok_bytes * ranks {
2507 return Err(format!(
2508 "TP KV verify source token bytes k={source_k_tok_bytes} v={source_v_tok_bytes} \
2509 do not match distributed k={}x{ranks} v={}x{ranks}",
2510 k_tok_bytes, v_tok_bytes
2511 )
2512 .into());
2513 }
2514 for rank in 0..self.ranks.len() {
2515 let engine = &self.ranks[rank];
2516 let _main = engine.gpu.enter_main()?;
2517 use cudarc::driver::DevicePtr;
2518 let stream = engine.stream();
2519 let k_offset = physical
2520 .start
2521 .checked_mul(k_tok_bytes)
2522 .ok_or("TP KV verify K offset overflow")?;
2523 let v_offset = physical
2524 .start
2525 .checked_mul(v_tok_bytes)
2526 .ok_or("TP KV verify V offset overflow")?;
2527 let rank_cache = cache
2528 .rank_mut(rank)
2529 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2530 let k_dst = {
2531 let (pointer, _guard) = rank_cache.k_mut().device_ptr(&stream);
2532 pointer
2533 };
2534 let v_dst = {
2535 let (pointer, _guard) = rank_cache.v_mut().device_ptr(&stream);
2536 pointer
2537 };
2538 for row in 0..rows {
2539 let k_src = source_k_raw + (row * source_k_tok_bytes + rank * k_tok_bytes) as u64;
2540 let v_src = source_v_raw + (row * source_v_tok_bytes + rank * v_tok_bytes) as u64;
2541 let k_out = k_dst + (k_offset + row * k_tok_bytes) as u64;
2542 let v_out = v_dst + (v_offset + row * v_tok_bytes) as u64;
2543 raw_copy_bytes(k_out, k_src, k_tok_bytes, engine)?;
2544 raw_copy_bytes(v_out, v_src, v_tok_bytes, engine)?;
2545 }
2546 }
2547 cache.rewind_to(logical_len)?;
2548 Ok(())
2549 }
2550
2551 pub fn restore_tp_kv_layers_from_device(
2555 &self,
2556 layers: &mut [TpKvVerifiedLayer<'_>],
2557 ) -> Result<bool, Box<dyn std::error::Error>> {
2558 let Some(first) = layers.first() else {
2559 return Ok(false);
2560 };
2561 if !self.native_p2p || first.start >= first.logical_len {
2562 return Ok(false);
2563 }
2564 let ranks = self.ranks.len();
2565 let rows = first.logical_len - first.start;
2566 let logical_len = first.logical_len;
2567 let k_row_bytes = first.cache.k_tok_bytes();
2568 let v_row_bytes = first.cache.v_tok_bytes();
2569 let k_src_stride = first.source_k_tok_bytes;
2570 let v_src_stride = first.source_v_tok_bytes;
2571 let Some(expected_k_stride) = k_row_bytes.checked_mul(ranks) else {
2572 return Ok(false);
2573 };
2574 let Some(expected_v_stride) = v_row_bytes.checked_mul(ranks) else {
2575 return Ok(false);
2576 };
2577 if k_src_stride != expected_k_stride || v_src_stride != expected_v_stride {
2578 return Ok(false);
2579 }
2580
2581 for layer in layers.iter() {
2582 self.validate_tp_kv_cache(layer.cache)?;
2583 if layer.cache.committed_len() != layer.cache.staged_len()
2584 || layer.start > layer.logical_len
2585 || layer.logical_len != logical_len
2586 || layer.logical_len - layer.start != rows
2587 || layer.cache.k_tok_bytes() != k_row_bytes
2588 || layer.cache.v_tok_bytes() != v_row_bytes
2589 || layer.source_k_tok_bytes != k_src_stride
2590 || layer.source_v_tok_bytes != v_src_stride
2591 {
2592 return Ok(false);
2593 }
2594 let physical = layer.cache.physical_range(layer.start, layer.logical_len)?;
2595 if physical.len() != rows {
2596 return Ok(false);
2597 }
2598 }
2599
2600 for rank in 0..ranks {
2601 let engine = &self.ranks[rank];
2602 let _main = engine.gpu.enter_main()?;
2603 let stream = engine.stream();
2604 let n = layers.len();
2605 let mut table = vec![0u64; 5 * n];
2606 for (index, layer) in layers.iter_mut().enumerate() {
2607 let physical = layer.cache.physical_range(layer.start, layer.logical_len)?;
2608 let rank_cache = layer
2609 .cache
2610 .rank_mut(rank)
2611 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2612 table[index] = layer.source_k_raw + (rank * k_row_bytes) as u64;
2613 table[n + index] = layer.source_v_raw + (rank * v_row_bytes) as u64;
2614 table[2 * n + index] = rank_cache.k_mut().device_ptr(&stream).0
2615 + (physical.start * k_row_bytes) as u64;
2616 table[3 * n + index] = rank_cache.v_mut().device_ptr(&stream).0
2617 + (physical.start * v_row_bytes) as u64;
2618 table[4 * n + index] = rank_cache.len_d_mut().device_ptr(&stream).0;
2619 }
2620 let table = engine.htod_u64(&table)?;
2621 engine.copy_batch_uniform_kv_u8_set_len(
2622 &table,
2623 n,
2624 rows,
2625 k_row_bytes,
2626 v_row_bytes,
2627 k_src_stride,
2628 v_src_stride,
2629 logical_len,
2630 )?;
2631 }
2632 let layer_count = layers.len();
2633 for layer in layers.iter_mut() {
2634 layer.cache.publish_device_rewind(logical_len)?;
2635 }
2636 static ANNOUNCED: std::sync::Once = std::sync::Once::new();
2637 ANNOUNCED.call_once(|| {
2638 eprintln!(
2639 "[tp-kv-verify-batch] engaged: layers={} ranks={ranks} rows={rows} \
2640 k_row_bytes={k_row_bytes} v_row_bytes={v_row_bytes}",
2641 layer_count
2642 );
2643 });
2644 Ok(true)
2645 }
2646
2647 pub fn append_tp_kv_transaction(
2648 &self,
2649 cache: &mut ResidentTpKvCache,
2650 transaction: TpKvTransaction,
2651 k_shards: &[CudaSlice<f32>],
2652 v_shards: &[CudaSlice<f32>],
2653 rows: usize,
2654 ) -> Result<(), Box<dyn std::error::Error>> {
2655 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
2656 }
2657
2658 #[allow(clippy::too_many_arguments)]
2663 pub fn append_tp_kv_transaction_inner(
2664 &self,
2665 cache: &mut ResidentTpKvCache,
2666 transaction: TpKvTransaction,
2667 k_shards: &[CudaSlice<f32>],
2668 v_shards: &[CudaSlice<f32>],
2669 rows: usize,
2670 external_rank_appends: bool,
2671 ) -> Result<(), Box<dyn std::error::Error>> {
2672 self.validate_tp_kv_cache(cache)?;
2673 let plan = cache.prepare_append(transaction, rows)?;
2674 let target = plan.target();
2675 let expected_k = rows
2676 .checked_mul(cache.kv_dim_k())
2677 .ok_or("TP KV K append size overflow")?;
2678 let expected_v = rows
2679 .checked_mul(cache.kv_dim_v())
2680 .ok_or("TP KV V append size overflow")?;
2681 if !external_rank_appends
2684 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
2685 {
2686 return Err(format!(
2687 "TP KV append shard counts k={} v={} != ranks {}",
2688 k_shards.len(),
2689 v_shards.len(),
2690 self.ranks.len()
2691 )
2692 .into());
2693 }
2694 let kv_dim_k = cache.kv_dim_k();
2695 let kv_dim_v = cache.kv_dim_v();
2696 let k_tok_bytes = cache.k_tok_bytes();
2697 let v_tok_bytes = cache.v_tok_bytes();
2698 if let Some(KvRingAppend::Rebase {
2699 src_row,
2700 keep_rows,
2701 new_base,
2702 ..
2703 }) = plan.ring_append()
2704 {
2705 for rank in 0..self.ranks.len() {
2706 let engine = &self.ranks[rank];
2707 let _main = engine.gpu.enter_main()?;
2708 let rank_cache = cache
2709 .rank_mut(rank)
2710 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2711 if keep_rows > 0 {
2712 let k_len = keep_rows
2713 .checked_mul(k_tok_bytes)
2714 .ok_or("TP KV K rebase-byte overflow")?;
2715 let v_len = keep_rows
2716 .checked_mul(v_tok_bytes)
2717 .ok_or("TP KV V rebase-byte overflow")?;
2718 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
2719 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2720 engine.copy_u8_range_into(
2721 &mut k_tmp,
2722 0,
2723 rank_cache.k(),
2724 src_row * k_tok_bytes,
2725 k_len,
2726 )?;
2727 engine.copy_u8_range_into(
2728 &mut v_tmp,
2729 0,
2730 rank_cache.v(),
2731 src_row * v_tok_bytes,
2732 v_len,
2733 )?;
2734 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2735 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2736 }
2737 if rank_cache.base_d().is_some() {
2741 let value = new_base as i32;
2742 let rank_cache = cache
2743 .rank_mut(rank)
2744 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2745 if let Some(base_d) = rank_cache.base_d_mut() {
2746 engine.set_i32_one(base_d, value)?;
2747 }
2748 }
2749 }
2750 }
2751 cache.publish_append_rebase(plan)?;
2752 let write_row = plan.write_row();
2753 for rank in 0..self.ranks.len() {
2754 if external_rank_appends {
2755 break;
2756 }
2757 let engine = &self.ranks[rank];
2758 let _main = engine.gpu.enter_main()?;
2759 if k_shards[rank].len() != expected_k
2760 || v_shards[rank].len() != expected_v
2761 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2762 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2763 {
2764 return Err(format!(
2765 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2766 != expected {expected_k}/{expected_v} on device {}",
2767 k_shards[rank].len(),
2768 k_shards[rank].ordinal(),
2769 v_shards[rank].len(),
2770 v_shards[rank].ordinal(),
2771 engine.ctx().ordinal(),
2772 )
2773 .into());
2774 }
2775 let rank_cache = cache
2776 .rank_mut(rank)
2777 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2778 let (rank_k, rank_v) = rank_cache.planes_mut();
2779 engine.append_kv_quantized_rows(
2780 &k_shards[rank],
2781 &v_shards[rank],
2782 rank_k,
2783 rank_v,
2784 write_row,
2785 rows,
2786 kv_dim_k,
2787 kv_dim_v,
2788 k_tok_bytes,
2789 v_tok_bytes,
2790 Engine::kv_fp8_on(),
2791 )?;
2792 }
2793 if !external_rank_appends {
2794 self.set_tp_kv_len_mirrors(cache, target)?;
2797 }
2798 cache.publish_append_plan(plan)?;
2799 Ok(())
2800 }
2801
2802 pub fn commit_tp_kv_transaction(
2803 &self,
2804 cache: &mut ResidentTpKvCache,
2805 transaction: TpKvTransaction,
2806 accepted_rows: usize,
2807 ) -> Result<(), Box<dyn std::error::Error>> {
2808 self.validate_tp_kv_cache(cache)?;
2809 let target = cache.commit_target(transaction, accepted_rows)?;
2810 self.set_tp_kv_len_mirrors(cache, target)?;
2811 cache.publish_finalize(transaction, target)?;
2812 Ok(())
2813 }
2814
2815 pub fn commit_tp_kv_transaction_external(
2821 &self,
2822 cache: &mut ResidentTpKvCache,
2823 transaction: TpKvTransaction,
2824 accepted_rows: usize,
2825 ) -> Result<(), Box<dyn std::error::Error>> {
2826 self.validate_tp_kv_cache(cache)?;
2827 let target = cache.commit_target(transaction, accepted_rows)?;
2828 cache.publish_finalize(transaction, target)?;
2829 Ok(())
2830 }
2831
2832 pub fn rollback_tp_kv_transaction(
2833 &self,
2834 cache: &mut ResidentTpKvCache,
2835 transaction: TpKvTransaction,
2836 ) -> Result<(), Box<dyn std::error::Error>> {
2837 self.validate_tp_kv_cache(cache)?;
2838 cache.validate_transaction(transaction)?;
2839 let target = transaction.base_len();
2840 self.set_tp_kv_len_mirrors(cache, target)?;
2841 cache.publish_finalize(transaction, target)?;
2842 Ok(())
2843 }
2844
2845 pub fn tp_kv_device_lengths(
2846 &self,
2847 cache: &ResidentTpKvCache,
2848 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2849 self.validate_tp_kv_cache(cache)?;
2850 let mut lengths = Vec::with_capacity(self.ranks.len());
2851 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2852 let _main = engine.gpu.enter_main()?;
2853 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2854 }
2855 Ok(lengths)
2856 }
2857
2858 fn set_tp_kv_len_mirrors(
2859 &self,
2860 cache: &mut ResidentTpKvCache,
2861 len: usize,
2862 ) -> Result<(), Box<dyn std::error::Error>> {
2863 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2864 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2865 let _main = engine.gpu.enter_main()?;
2866 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2867 }
2868 Ok(())
2869 }
2870
2871 fn validate_tp_kv_cache(
2872 &self,
2873 cache: &ResidentTpKvCache,
2874 ) -> Result<(), Box<dyn std::error::Error>> {
2875 if cache.ranks_len() != self.ranks.len() {
2876 return Err(format!(
2877 "TP KV cache ranks {} != runtime ranks {}",
2878 cache.ranks_len(),
2879 self.ranks.len()
2880 )
2881 .into());
2882 }
2883 let expected_k = cache
2884 .physical_capacity()
2885 .checked_mul(cache.k_tok_bytes())
2886 .and_then(|bytes| bytes.checked_add(8))
2887 .ok_or("TP KV K plane validation overflow")?;
2888 let expected_v = cache
2889 .physical_capacity()
2890 .checked_mul(cache.v_tok_bytes())
2891 .and_then(|bytes| bytes.checked_add(8))
2892 .ok_or("TP KV V plane validation overflow")?;
2893 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2894 let device = engine.ctx().ordinal();
2895 if rank_cache.k().len() != expected_k
2896 || rank_cache.v().len() != expected_v
2897 || rank_cache.len_d().len() != 1
2898 || rank_cache.k().ordinal() != device
2899 || rank_cache.v().ordinal() != device
2900 || rank_cache.len_d().ordinal() != device
2901 {
2902 return Err(format!(
2903 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2904 )
2905 .into());
2906 }
2907 }
2908 Ok(())
2909 }
2910
2911 pub fn full(
2912 &self,
2913 matrix: E4m3BlockMatrix<'_>,
2914 activations: &[f32],
2915 tokens: usize,
2916 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2917 matrix.validate()?;
2918 validate_activations(activations, tokens, matrix.in_features)?;
2919 run_rank(&self.ranks[0], matrix, activations, tokens)
2920 }
2921
2922 #[allow(clippy::manual_is_multiple_of)] pub fn column_parallel(
2927 &self,
2928 matrix: E4m3BlockMatrix<'_>,
2929 activations: &[f32],
2930 tokens: usize,
2931 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2932 matrix.validate()?;
2933 validate_activations(activations, tokens, matrix.in_features)?;
2934 let tp = self.ranks.len();
2935 if matrix.out_features % tp != 0 {
2936 return Err(format!(
2937 "column-parallel out_features {} is not divisible by TP={tp}",
2938 matrix.out_features
2939 )
2940 .into());
2941 }
2942 let local_out = matrix.out_features / tp;
2943 if !local_out.is_multiple_of(FP8_BLOCK) {
2944 return Err(format!(
2945 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2946 E4M3 scale block"
2947 )
2948 .into());
2949 }
2950
2951 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2952 let mut rank_outputs = Vec::with_capacity(tp);
2953 for (rank_index, rank) in self.ranks.iter().enumerate() {
2954 let shard = column_shard(matrix, tp, rank_index)?;
2955 let output = run_rank(rank, shard, activations, tokens)?;
2956 let row_start = rank_index * local_out;
2957 for token in 0..tokens {
2958 gathered[token * matrix.out_features + row_start
2959 ..token * matrix.out_features + row_start + local_out]
2960 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2961 }
2962 rank_outputs.push(output);
2963 }
2964 Ok(ColumnParallelResult {
2965 gathered,
2966 rank_outputs,
2967 })
2968 }
2969
2970 pub fn upload_column_parallel(
2971 &self,
2972 matrix: E4m3BlockMatrix<'_>,
2973 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2974 matrix.validate()?;
2975 let tp = self.ranks.len();
2976 validate_column_shape(matrix, tp)?;
2977 let mut ranks = Vec::with_capacity(tp);
2978 for (rank_index, engine) in self.ranks.iter().enumerate() {
2979 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2980 }
2981 Ok(ResidentColumnParallel {
2982 ranks,
2983 out_features: matrix.out_features,
2984 in_features: matrix.in_features,
2985 })
2986 }
2987
2988 pub fn column_parallel_resident(
2989 &self,
2990 matrix: &ResidentColumnParallel,
2991 activations: &[f32],
2992 tokens: usize,
2993 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2994 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2995 validate_activations(activations, tokens, matrix.in_features)?;
2996 let local_out = matrix.out_features / self.ranks.len();
2997 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2998 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2999 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3000 let output = run_resident_rank(engine, shard, activations, tokens)?;
3001 let row_start = rank_index * local_out;
3002 for token in 0..tokens {
3003 gathered[token * matrix.out_features + row_start
3004 ..token * matrix.out_features + row_start + local_out]
3005 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
3006 }
3007 rank_outputs.push(output);
3008 }
3009 Ok(ColumnParallelResult {
3010 gathered,
3011 rank_outputs,
3012 })
3013 }
3014
3015 #[allow(clippy::manual_is_multiple_of)] pub fn row_parallel(
3020 &self,
3021 matrix: E4m3BlockMatrix<'_>,
3022 activations: &[f32],
3023 tokens: usize,
3024 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3025 matrix.validate()?;
3026 validate_activations(activations, tokens, matrix.in_features)?;
3027 let tp = self.ranks.len();
3028 if matrix.in_features % tp != 0 {
3029 return Err(format!(
3030 "row-parallel in_features {} is not divisible by TP={tp}",
3031 matrix.in_features
3032 )
3033 .into());
3034 }
3035 let local_in = matrix.in_features / tp;
3036 if !local_in.is_multiple_of(FP8_BLOCK) {
3037 return Err(format!(
3038 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
3039 E4M3 scale block"
3040 )
3041 .into());
3042 }
3043
3044 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3045 let mut rank_partials = Vec::with_capacity(tp);
3046 for (rank_index, rank) in self.ranks.iter().enumerate() {
3047 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
3048 let local_activations =
3049 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
3050 let shard = E4m3BlockMatrix {
3051 codes: &codes,
3052 scales: &scales,
3053 out_features: matrix.out_features,
3054 in_features: local_in,
3055 };
3056 let partial = run_rank(rank, shard, &local_activations, tokens)?;
3057 for (sum, value) in reduced.iter_mut().zip(&partial) {
3058 *sum += *value;
3059 }
3060 rank_partials.push(partial);
3061 }
3062 Ok(RowParallelResult {
3063 reduced,
3064 rank_partials,
3065 })
3066 }
3067
3068 pub fn upload_row_parallel(
3069 &self,
3070 matrix: E4m3BlockMatrix<'_>,
3071 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
3072 matrix.validate()?;
3073 let tp = self.ranks.len();
3074 validate_row_shape(matrix, tp)?;
3075 let local_in = matrix.in_features / tp;
3076 let mut ranks = Vec::with_capacity(tp);
3077 for (rank_index, engine) in self.ranks.iter().enumerate() {
3078 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
3079 ranks.push(upload_rank(
3080 engine,
3081 E4m3BlockMatrix {
3082 codes: &codes,
3083 scales: &scales,
3084 out_features: matrix.out_features,
3085 in_features: local_in,
3086 },
3087 )?);
3088 }
3089 Ok(ResidentRowParallel {
3090 ranks,
3091 out_features: matrix.out_features,
3092 in_features: matrix.in_features,
3093 })
3094 }
3095
3096 pub fn row_parallel_resident(
3097 &self,
3098 matrix: &ResidentRowParallel,
3099 activations: &[f32],
3100 tokens: usize,
3101 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3102 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
3103 validate_activations(activations, tokens, matrix.in_features)?;
3104 let tp = self.ranks.len();
3105 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3106 let mut rank_partials = Vec::with_capacity(tp);
3107 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3108 let local_activations =
3109 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
3110 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
3111 for (sum, value) in reduced.iter_mut().zip(&partial) {
3112 *sum += *value;
3113 }
3114 rank_partials.push(partial);
3115 }
3116 Ok(RowParallelResult {
3117 reduced,
3118 rank_partials,
3119 })
3120 }
3121
3122 pub fn upload_bf16_column_parallel(
3123 &self,
3124 matrix: Bf16Matrix<'_>,
3125 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3126 self.upload_bf16_column_parallel_inner(matrix, None, false)
3127 }
3128
3129 pub fn upload_step_bf16_column_parallel(
3131 &self,
3132 matrix: Bf16Matrix<'_>,
3133 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3134 self.upload_step_bf16_column_parallel_inner(matrix, false)
3135 }
3136
3137 pub fn upload_step_bf16_column_parallel_f32_mirror(
3142 &self,
3143 matrix: Bf16Matrix<'_>,
3144 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3145 self.upload_step_bf16_column_parallel_inner(matrix, true)
3146 }
3147
3148 fn upload_step_bf16_column_parallel_inner(
3149 &self,
3150 matrix: Bf16Matrix<'_>,
3151 f32_mirror: bool,
3152 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3153 let canonical_chunk_rows =
3154 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
3155 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
3156 }
3157
3158 #[allow(clippy::manual_is_multiple_of)] fn upload_bf16_column_parallel_inner(
3160 &self,
3161 matrix: Bf16Matrix<'_>,
3162 canonical_chunk_rows: Option<usize>,
3163 f32_mirror: bool,
3164 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3165 matrix.validate()?;
3166 let tp = self.ranks.len();
3167 if matrix.out_features % tp != 0 {
3168 return Err(format!(
3169 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
3170 matrix.out_features
3171 )
3172 .into());
3173 }
3174 let mut ranks = Vec::with_capacity(tp);
3175 for (rank, engine) in self.ranks.iter().enumerate() {
3176 ranks.push(upload_bf16_rank(
3177 engine,
3178 bf16_column_shard(matrix, tp, rank)?,
3179 f32_mirror,
3180 )?);
3181 }
3182 Ok(ResidentBf16ColumnParallel {
3183 ranks,
3184 out_features: matrix.out_features,
3185 in_features: matrix.in_features,
3186 canonical_chunk_rows,
3187 })
3188 }
3189
3190 pub fn bf16_column_parallel_resident(
3191 &self,
3192 matrix: &ResidentBf16ColumnParallel,
3193 activations: &[f32],
3194 tokens: usize,
3195 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
3196 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3197 validate_activations(activations, tokens, matrix.in_features)?;
3198 let local_out = matrix.out_features / self.ranks.len();
3199 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
3200 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3201 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3202 let output = run_resident_bf16_rank(
3203 engine,
3204 shard,
3205 activations,
3206 tokens,
3207 matrix.canonical_chunk_rows,
3208 )?;
3209 for token in 0..tokens {
3210 let src = &output[token * local_out..(token + 1) * local_out];
3211 let dst_start = token * matrix.out_features + rank * local_out;
3212 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
3213 }
3214 rank_outputs.push(output);
3215 }
3216 Ok(ColumnParallelResult {
3217 gathered,
3218 rank_outputs,
3219 })
3220 }
3221
3222 pub fn bf16_column_parallel_resident_native(
3229 &self,
3230 matrix: &ResidentBf16ColumnParallel,
3231 activations: &[f32],
3232 tokens: usize,
3233 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3234 let rank_outputs =
3235 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
3236 let local_out = matrix.out_features / self.ranks.len();
3237 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
3238 }
3239
3240 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
3245 self.ranks
3246 .first()
3247 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
3248 }
3249
3250 pub fn bf16_column_parallel_resident_native_device(
3263 &self,
3264 matrix: &ResidentBf16ColumnParallel,
3265 root_activation: &CudaSlice<f32>,
3266 tokens: usize,
3267 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3268 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
3269 matrix,
3270 root_activation,
3271 tokens,
3272 )?;
3273 let local_out = matrix.out_features / self.ranks.len();
3274 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
3275 let root = &self.ranks[0];
3276 let _main = root.gpu.enter_main()?;
3277 root.stream().synchronize()?;
3278 Ok(gathered)
3279 }
3280
3281 pub fn bf16_column_parallel_resident_device_shards_from_root(
3286 &self,
3287 matrix: &ResidentBf16ColumnParallel,
3288 root_activation: &CudaSlice<f32>,
3289 tokens: usize,
3290 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3291 if self.ranks.len() > 1 && !self.native_p2p {
3292 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3293 }
3294 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3295 let values = tokens
3296 .checked_mul(matrix.in_features)
3297 .ok_or("device BF16 column activation size overflow")?;
3298 let root = &self.ranks[0];
3299 if tokens == 0
3300 || root_activation.len() < values
3301 || root_activation.ordinal() != root.ctx().ordinal()
3302 {
3303 return Err("device BF16 column root activation geometry mismatch".into());
3304 }
3305
3306 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3307 let root_input = {
3308 let _main = root.gpu.enter_main()?;
3309 let mut root_input = root.uninit(values)?;
3310 root.stream()
3311 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3312 root_input
3313 };
3314 {
3318 let _main = root.gpu.enter_main()?;
3319 root.stream().synchronize()?;
3320 }
3321 rank_inputs.push(root_input);
3322 for engine in &self.ranks[1..] {
3323 let peer_input = {
3324 let _main = engine.gpu.enter_main()?;
3325 let mut peer_input = engine.uninit(values)?;
3326 engine
3327 .stream()
3328 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3329 peer_input
3330 };
3331 rank_inputs.push(peer_input);
3332 }
3333
3334 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3335 #[allow(clippy::needless_range_loop)]
3336 for rank in 0..self.ranks.len() {
3338 rank_outputs.push(run_resident_bf16_rank_device(
3339 &self.ranks[rank],
3340 &matrix.ranks[rank],
3341 &rank_inputs[rank],
3342 tokens,
3343 matrix.canonical_chunk_rows,
3344 self.bulk_p2p,
3345 )?);
3346 }
3347 Ok(rank_outputs)
3348 }
3349
3350 pub fn bf16_column_parallel_resident_device_shards(
3357 &self,
3358 matrix: &ResidentBf16ColumnParallel,
3359 activations: &[f32],
3360 tokens: usize,
3361 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3362 if self.ranks.len() > 1 && !self.native_p2p {
3363 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3364 }
3365 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3366 validate_activations(activations, tokens, matrix.in_features)?;
3367
3368 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3369 let root_input = {
3370 let root = &self.ranks[0];
3371 let _main = root.gpu.enter_main()?;
3372 root.htod(activations)?
3373 };
3374 {
3380 let root = &self.ranks[0];
3381 let _main = root.gpu.enter_main()?;
3382 root.stream().synchronize()?;
3383 }
3384 rank_inputs.push(root_input);
3385 for engine in &self.ranks[1..] {
3386 let peer_input = {
3387 let _main = engine.gpu.enter_main()?;
3388 let mut peer_input = engine.uninit(activations.len())?;
3389 engine
3390 .stream()
3391 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3392 peer_input
3393 };
3394 rank_inputs.push(peer_input);
3395 }
3396
3397 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3398 #[allow(clippy::needless_range_loop)]
3399 for rank in 0..self.ranks.len() {
3401 rank_outputs.push(run_resident_bf16_rank_device(
3402 &self.ranks[rank],
3403 &matrix.ranks[rank],
3404 &rank_inputs[rank],
3405 tokens,
3406 matrix.canonical_chunk_rows,
3407 self.bulk_p2p,
3408 )?);
3409 }
3410 Ok(rank_outputs)
3411 }
3412
3413 pub fn allocate_replicated_device_rows(
3417 &self,
3418 tokens: usize,
3419 width: usize,
3420 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3421 if self.ranks.len() > 1 && !self.native_p2p {
3422 return Err("replicated device rows require native P2P ranks".into());
3423 }
3424 let values = tokens
3425 .checked_mul(width)
3426 .ok_or("replicated device row size overflow")?;
3427 let rank_lengths = vec![values; self.ranks.len()];
3428 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
3429 let mut ranks = Vec::with_capacity(self.ranks.len());
3430 for engine in &self.ranks {
3431 let _main = engine.gpu.enter_main()?;
3432 ranks.push(engine.uninit(values)?);
3433 }
3434 Ok(ResidentReplicatedDeviceRows {
3435 ranks,
3436 tokens,
3437 width,
3438 })
3439 }
3440
3441 pub fn refresh_replicated_device_rows_from_root(
3443 &self,
3444 rows: &mut ResidentReplicatedDeviceRows,
3445 source: &CudaSlice<f32>,
3446 ) -> Result<(), Box<dyn std::error::Error>> {
3447 if self.ranks.len() > 1 && !self.native_p2p {
3448 return Err("replicated device rows require native P2P ranks".into());
3449 }
3450 validate_replicated_device_rows(&self.ranks, rows)?;
3451 let root = self
3452 .ranks
3453 .first()
3454 .ok_or("replicated rows have no root rank")?;
3455 let values = replicated_device_row_source_values(
3456 rows.tokens,
3457 rows.width,
3458 source.len(),
3459 source.ordinal(),
3460 root.ctx().ordinal(),
3461 )?;
3462 let (root_rows, peer_rows) = rows
3463 .ranks
3464 .split_first_mut()
3465 .ok_or("replicated rows have no root allocation")?;
3466 {
3467 let _main = root.gpu.enter_main()?;
3468 let mut destination = root_rows.slice_mut(0..values);
3469 root.stream()
3470 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
3471 root.stream().synchronize()?;
3472 }
3473 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
3474 let _main = engine.gpu.enter_main()?;
3475 let mut destination = peer_rows.slice_mut(0..values);
3476 engine
3477 .stream()
3478 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
3479 }
3480 Ok(())
3481 }
3482
3483 pub fn upload_replicated_device_rows(
3485 &self,
3486 rows: &[f32],
3487 tokens: usize,
3488 width: usize,
3489 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3490 if self.ranks.len() > 1 && !self.native_p2p {
3491 return Err("replicated device rows require native P2P ranks".into());
3492 }
3493 validate_activations(rows, tokens, width)?;
3494 let root = self
3495 .ranks
3496 .first()
3497 .ok_or("replicated rows have no root rank")?;
3498 let root_rows = {
3499 let _main = root.gpu.enter_main()?;
3500 root.htod(rows)?
3501 };
3502 {
3503 let _main = root.gpu.enter_main()?;
3504 root.stream().synchronize()?;
3505 }
3506 let mut ranks = Vec::with_capacity(self.ranks.len());
3507 ranks.push(root_rows);
3508 for engine in self.ranks.iter().skip(1) {
3509 let _main = engine.gpu.enter_main()?;
3510 let mut peer_rows = engine.uninit(rows.len())?;
3511 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
3512 ranks.push(peer_rows);
3513 }
3514 Ok(ResidentReplicatedDeviceRows {
3515 ranks,
3516 tokens,
3517 width,
3518 })
3519 }
3520
3521 pub fn bf16_column_parallel_resident_replicated_device_shards(
3523 &self,
3524 matrix: &ResidentBf16ColumnParallel,
3525 activations: &ResidentReplicatedDeviceRows,
3526 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3527 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3528 validate_replicated_device_rows(&self.ranks, activations)?;
3529 if activations.width != matrix.in_features {
3530 return Err(format!(
3531 "replicated BF16 column input width {} != matrix width {}",
3532 activations.width, matrix.in_features
3533 )
3534 .into());
3535 }
3536 let mut outputs = Vec::with_capacity(self.ranks.len());
3537 for rank in 0..self.ranks.len() {
3538 outputs.push(run_resident_bf16_rank_device(
3539 &self.ranks[rank],
3540 &matrix.ranks[rank],
3541 &activations.ranks[rank],
3542 activations.tokens,
3543 matrix.canonical_chunk_rows,
3544 self.bulk_p2p,
3545 )?);
3546 }
3547 Ok(outputs)
3548 }
3549
3550 #[allow(clippy::too_many_arguments)]
3552 pub fn upload_sigmoid_topk_router(
3553 &self,
3554 weight: Bf16Matrix<'_>,
3555 correction_bias: &[f32],
3556 active: Option<&[bool]>,
3557 experts_per_token: usize,
3558 scaling_factor: f32,
3559 route_norm: bool,
3560 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
3561 weight.validate()?;
3562 if correction_bias.len() != weight.out_features
3563 || experts_per_token == 0
3564 || experts_per_token > weight.out_features
3565 || !correction_bias.iter().all(|value| value.is_finite())
3566 || !scaling_factor.is_finite()
3567 || scaling_factor <= 0.0
3568 {
3569 return Err(format!(
3570 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
3571 weight.out_features,
3572 weight.in_features,
3573 correction_bias.len(),
3574 experts_per_token,
3575 )
3576 .into());
3577 }
3578 let active_row = active
3579 .map(|mask| {
3580 if mask.len() != weight.out_features {
3581 return Err(format!(
3582 "sigmoid router active mask {} != experts {}",
3583 mask.len(),
3584 weight.out_features
3585 ));
3586 }
3587 Ok(mask
3588 .iter()
3589 .map(|&enabled| u8::from(enabled))
3590 .collect::<Vec<_>>())
3591 })
3592 .transpose()?
3593 .unwrap_or_else(|| vec![1; weight.out_features]);
3594 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
3595 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
3596
3597 let root = self
3598 .ranks
3599 .first()
3600 .ok_or("sigmoid router runtime has no root rank")?;
3601 let _main = root.gpu.enter_main()?;
3602 let bf16 = root.htod_bytes(weight.bytes)?;
3603 let weight_f32 = root.bf16_to_f32(
3604 &bf16.slice(0..bf16.len()),
3605 weight.out_features * weight.in_features,
3606 )?;
3607 Ok(ResidentSigmoidTopKRouter {
3608 weight: weight_f32,
3609 correction_bias: root.htod(correction_bias)?,
3610 active: root.htod_bytes(&active_row)?,
3611 root_device: root.ctx().ordinal(),
3612 input_width: weight.in_features,
3613 expert_count: weight.out_features,
3614 experts_per_token,
3615 active_count,
3616 scaling_factor,
3617 route_norm,
3618 })
3619 }
3620
3621 pub fn sigmoid_topk_replicated_device_rows_host(
3626 &self,
3627 router: &ResidentSigmoidTopKRouter,
3628 input: &ResidentReplicatedDeviceRows,
3629 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
3630 validate_replicated_device_rows(&self.ranks, input)?;
3631 if input.width != router.input_width {
3632 return Err(format!(
3633 "sigmoid router input width {} != resident width {}",
3634 input.width, router.input_width
3635 )
3636 .into());
3637 }
3638 let root = self
3639 .ranks
3640 .first()
3641 .ok_or("sigmoid router runtime has no root rank")?;
3642 let _main = root.gpu.enter_main()?;
3643 if root.ctx().ordinal() != router.root_device
3644 || router.weight.ordinal() != router.root_device
3645 || router.correction_bias.ordinal() != router.root_device
3646 || router.active.ordinal() != router.root_device
3647 {
3648 return Err("sigmoid router root residency changed".into());
3649 }
3650 let logits = root.router_gemv(
3651 &router.weight,
3652 &input.ranks[0],
3653 router.input_width,
3654 router.expert_count,
3655 input.tokens,
3656 )?;
3657 let (selected, weights) = root.moe_router_sigmoid_topk_host(
3658 &logits,
3659 input.tokens,
3660 router.expert_count,
3661 router.experts_per_token,
3662 router.active_count,
3663 &router.correction_bias,
3664 &router.active,
3665 router.scaling_factor,
3666 router.route_norm,
3667 )?;
3668 Ok(SigmoidTopKHostOutput {
3669 logits: root.dtoh(&logits)?,
3670 selected,
3671 weights,
3672 })
3673 }
3674
3675 pub fn upload_replicated_bf16_swiglu(
3677 &self,
3678 gate: Bf16Matrix<'_>,
3679 up: Bf16Matrix<'_>,
3680 down: Bf16Matrix<'_>,
3681 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
3682 gate.validate()?;
3683 up.validate()?;
3684 down.validate()?;
3685 if gate.in_features != up.in_features
3686 || gate.out_features != up.out_features
3687 || down.in_features != gate.out_features
3688 || down.out_features != gate.in_features
3689 {
3690 return Err(format!(
3691 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
3692 gate.out_features,
3693 gate.in_features,
3694 up.out_features,
3695 up.in_features,
3696 down.out_features,
3697 down.in_features,
3698 )
3699 .into());
3700 }
3701 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
3702 let mut up_ranks = Vec::with_capacity(self.ranks.len());
3703 let mut down_ranks = Vec::with_capacity(self.ranks.len());
3704 for engine in &self.ranks {
3705 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
3706 up_ranks.push(upload_bf16_rank(engine, up, false)?);
3707 down_ranks.push(upload_bf16_rank(engine, down, false)?);
3708 }
3709 Ok(ResidentReplicatedBf16SwiGlu {
3710 gate: gate_ranks,
3711 up: up_ranks,
3712 down: down_ranks,
3713 input_width: gate.in_features,
3714 intermediate_width: gate.out_features,
3715 })
3716 }
3717
3718 pub fn replicated_bf16_swiglu_resident_device(
3720 &self,
3721 mlp: &ResidentReplicatedBf16SwiGlu,
3722 input: &ResidentReplicatedDeviceRows,
3723 activation_limit: Option<f32>,
3724 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3725 validate_step_expert_activation_limit(activation_limit)?;
3726 validate_replicated_device_rows(&self.ranks, input)?;
3727 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3728 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3729 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3730 if input.width != mlp.input_width
3731 || mlp.gate.len() != self.ranks.len()
3732 || mlp.up.len() != self.ranks.len()
3733 || mlp.down.len() != self.ranks.len()
3734 {
3735 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3736 }
3737
3738 let mut outputs = Vec::with_capacity(self.ranks.len());
3739 for rank in 0..self.ranks.len() {
3740 let engine = &self.ranks[rank];
3741 let gate = run_resident_bf16_rank_device(
3742 engine,
3743 &mlp.gate[rank],
3744 &input.ranks[rank],
3745 input.tokens,
3746 None,
3747 self.bulk_p2p,
3748 )?;
3749 let up = run_resident_bf16_rank_device(
3750 engine,
3751 &mlp.up[rank],
3752 &input.ranks[rank],
3753 input.tokens,
3754 None,
3755 self.bulk_p2p,
3756 )?;
3757 let _main = engine.gpu.enter_main()?;
3758 let values = input
3759 .tokens
3760 .checked_mul(mlp.intermediate_width)
3761 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3762 let mut activation = engine.uninit(values)?;
3763 if let Some(limit) = activation_limit {
3764 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3765 } else {
3766 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3767 }
3768 outputs.push(run_resident_bf16_rank_device(
3769 engine,
3770 &mlp.down[rank],
3771 &activation,
3772 input.tokens,
3773 None,
3774 self.bulk_p2p,
3775 )?);
3776 }
3777 Ok(ResidentReplicatedDeviceRows {
3778 ranks: outputs,
3779 tokens: input.tokens,
3780 width: mlp.input_width,
3781 })
3782 }
3783
3784 pub fn rms_norm_replicated_device_rows(
3786 &self,
3787 input: &ResidentReplicatedDeviceRows,
3788 weight: &[f32],
3789 eps: f32,
3790 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3791 validate_replicated_device_rows(&self.ranks, input)?;
3792 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3793 return Err(format!(
3794 "replicated RMS norm weight/eps {}/{} != width {}",
3795 weight.len(),
3796 eps,
3797 input.width
3798 )
3799 .into());
3800 }
3801 let mut ranks = Vec::with_capacity(self.ranks.len());
3802 for (rank, engine) in self.ranks.iter().enumerate() {
3803 let _main = engine.gpu.enter_main()?;
3804 let weight = engine.htod(weight)?;
3805 let mut output = engine.uninit(input.tokens * input.width)?;
3806 engine.rms_norm(
3807 &input.ranks[rank],
3808 &weight,
3809 &mut output,
3810 input.width,
3811 input.tokens,
3812 eps,
3813 )?;
3814 ranks.push(output);
3815 }
3816 Ok(ResidentReplicatedDeviceRows {
3817 ranks,
3818 tokens: input.tokens,
3819 width: input.width,
3820 })
3821 }
3822
3823 pub fn add_rms_norm_replicated_device_rows(
3825 &self,
3826 input: &ResidentReplicatedDeviceRows,
3827 update: &ResidentReplicatedDeviceRows,
3828 weight: &[f32],
3829 eps: f32,
3830 ) -> Result<
3831 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3832 Box<dyn std::error::Error>,
3833 > {
3834 validate_replicated_device_rows(&self.ranks, input)?;
3835 validate_replicated_device_rows(&self.ranks, update)?;
3836 if input.tokens != update.tokens
3837 || input.width != update.width
3838 || weight.len() != input.width
3839 || !eps.is_finite()
3840 || eps <= 0.0
3841 {
3842 return Err(format!(
3843 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3844 input.tokens,
3845 input.width,
3846 update.tokens,
3847 update.width,
3848 weight.len(),
3849 )
3850 .into());
3851 }
3852 let values = input.tokens * input.width;
3853 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3854 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3855 for (rank, engine) in self.ranks.iter().enumerate() {
3856 let _main = engine.gpu.enter_main()?;
3857 let weight = engine.htod(weight)?;
3858 let mut residual = engine.uninit(values)?;
3859 let mut normalized = engine.uninit(values)?;
3860 engine.add_rms_norm(
3861 &input.ranks[rank],
3862 &update.ranks[rank],
3863 &weight,
3864 &mut residual,
3865 &mut normalized,
3866 input.width,
3867 input.tokens,
3868 eps,
3869 )?;
3870 residual_ranks.push(residual);
3871 normalized_ranks.push(normalized);
3872 }
3873 Ok((
3874 ResidentReplicatedDeviceRows {
3875 ranks: residual_ranks,
3876 tokens: input.tokens,
3877 width: input.width,
3878 },
3879 ResidentReplicatedDeviceRows {
3880 ranks: normalized_ranks,
3881 tokens: input.tokens,
3882 width: input.width,
3883 },
3884 ))
3885 }
3886
3887 pub fn collect_replicated_device_rows(
3888 &self,
3889 rows: &ResidentReplicatedDeviceRows,
3890 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3891 validate_replicated_device_rows(&self.ranks, rows)?;
3892 let mut outputs = Vec::with_capacity(self.ranks.len());
3893 for (rank, engine) in self.ranks.iter().enumerate() {
3894 let _main = engine.gpu.enter_main()?;
3895 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3896 }
3897 Ok(outputs)
3898 }
3899
3900 #[allow(clippy::manual_is_multiple_of)] pub fn upload_bf16_row_parallel(
3902 &self,
3903 matrix: Bf16Matrix<'_>,
3904 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3905 matrix.validate()?;
3906 let tp = self.ranks.len();
3907 if matrix.in_features % tp != 0 {
3908 return Err(format!(
3909 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3910 matrix.in_features
3911 )
3912 .into());
3913 }
3914 let mut ranks = Vec::with_capacity(tp);
3915 for (rank, engine) in self.ranks.iter().enumerate() {
3916 let shard = bf16_row_shard(matrix, tp, rank)?;
3917 ranks.push(upload_bf16_rank(
3918 engine,
3919 Bf16Matrix {
3920 bytes: &shard,
3921 out_features: matrix.out_features,
3922 in_features: matrix.in_features / tp,
3923 },
3924 false,
3925 )?);
3926 }
3927 Ok(ResidentBf16RowParallel {
3928 ranks,
3929 out_features: matrix.out_features,
3930 in_features: matrix.in_features,
3931 })
3932 }
3933
3934 pub fn bf16_row_parallel_resident(
3935 &self,
3936 matrix: &ResidentBf16RowParallel,
3937 activations: &[f32],
3938 tokens: usize,
3939 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3940 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3941 validate_activations(activations, tokens, matrix.in_features)?;
3942 let tp = self.ranks.len();
3943 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3944 let mut rank_partials = Vec::with_capacity(tp);
3945 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3946 let local_activations =
3947 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3948 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3949 for (sum, value) in reduced.iter_mut().zip(&partial) {
3950 *sum += value;
3951 }
3952 rank_partials.push(partial);
3953 }
3954 Ok(RowParallelResult {
3955 reduced,
3956 rank_partials,
3957 })
3958 }
3959
3960 pub fn upload_step_bf16_row_parallel(
3962 &self,
3963 matrix: Bf16Matrix<'_>,
3964 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3965 self.upload_step_bf16_row_parallel_inner(matrix, false)
3966 }
3967
3968 pub fn upload_step_bf16_row_parallel_f32_mirror(
3969 &self,
3970 matrix: Bf16Matrix<'_>,
3971 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3972 self.upload_step_bf16_row_parallel_inner(matrix, true)
3973 }
3974
3975 fn upload_step_bf16_row_parallel_inner(
3976 &self,
3977 matrix: Bf16Matrix<'_>,
3978 f32_mirror: bool,
3979 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3980 matrix.validate()?;
3981 let tp = self.ranks.len();
3982 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3983 let local_in = matrix.in_features / tp;
3984 let blocks_per_rank = local_in / canonical_chunk_cols;
3985 let mut ranks = Vec::with_capacity(tp);
3986 for (rank, engine) in self.ranks.iter().enumerate() {
3987 let mut blocks = Vec::with_capacity(blocks_per_rank);
3988 for block in 0..blocks_per_rank {
3989 let global_block = rank * blocks_per_rank + block;
3990 let col_start = global_block * canonical_chunk_cols;
3991 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3992 blocks.push(upload_bf16_rank(
3993 engine,
3994 Bf16Matrix {
3995 bytes: &bytes,
3996 out_features: matrix.out_features,
3997 in_features: canonical_chunk_cols,
3998 },
3999 f32_mirror,
4000 )?);
4001 }
4002 ranks.push(blocks);
4003 }
4004 Ok(ResidentStepBf16RowParallel {
4005 ranks,
4006 out_features: matrix.out_features,
4007 in_features: matrix.in_features,
4008 canonical_chunk_cols,
4009 })
4010 }
4011
4012 pub fn step_bf16_row_parallel_resident(
4017 &self,
4018 matrix: &ResidentStepBf16RowParallel,
4019 activations: &[f32],
4020 tokens: usize,
4021 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4022 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4023 validate_activations(activations, tokens, matrix.in_features)?;
4024 let root = &self.ranks[0];
4025 let output_len = tokens
4026 .checked_mul(matrix.out_features)
4027 .ok_or("Step BF16 row output size overflow")?;
4028 let mut reduced = {
4029 let _main = root.gpu.enter_main()?;
4030 root.htod(&vec![0.0f32; output_len])?
4031 };
4032 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4033 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4034 for (block, resident) in blocks.iter().enumerate() {
4035 let global_block = rank * blocks_per_rank + block;
4036 let input = activation_shard(
4037 activations,
4038 tokens,
4039 matrix.in_features,
4040 PRODUCT_MAX_CARDS,
4041 global_block,
4042 );
4043 let partial =
4044 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
4045 let next = {
4046 let _main = root.gpu.enter_main()?;
4047 let partial = root.htod(&partial)?;
4048 let mut next = root.uninit(output_len)?;
4049 root.add(&reduced, &partial, &mut next, output_len)?;
4050 next
4051 };
4052 reduced = next;
4053 }
4054 }
4055 let _main = root.gpu.enter_main()?;
4056 root.dtoh(&reduced)
4057 }
4058
4059 pub fn step_bf16_row_parallel_resident_native(
4065 &self,
4066 matrix: &ResidentStepBf16RowParallel,
4067 activations: &[f32],
4068 tokens: usize,
4069 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4070 if self.ranks.len() > 1 && !self.native_p2p {
4071 return Err("native Step BF16 row parallelism requires P2P ranks".into());
4072 }
4073 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4074 validate_activations(activations, tokens, matrix.in_features)?;
4075 let root = &self.ranks[0];
4076 let root_input = {
4077 let _main = root.gpu.enter_main()?;
4078 root.htod(activations)?
4079 };
4080 {
4083 let _main = root.gpu.enter_main()?;
4084 root.stream().synchronize()?;
4085 }
4086 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
4087 let _main = root.gpu.enter_main()?;
4088 root.dtoh(&reduced)
4089 }
4090
4091 pub fn step_bf16_row_parallel_resident_native_device(
4099 &self,
4100 matrix: &ResidentStepBf16RowParallel,
4101 root_activation: &CudaSlice<f32>,
4102 tokens: usize,
4103 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4104 if self.ranks.len() > 1 && !self.native_p2p {
4105 return Err("native Step BF16 row parallelism requires P2P ranks".into());
4106 }
4107 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4108 let values = tokens
4109 .checked_mul(matrix.in_features)
4110 .ok_or("device Step BF16 row activation size overflow")?;
4111 let root = &self.ranks[0];
4112 if tokens == 0
4113 || root_activation.len() < values
4114 || root_activation.ordinal() != root.ctx().ordinal()
4115 {
4116 return Err("device Step BF16 row root activation geometry mismatch".into());
4117 }
4118 let root_input = {
4119 let _main = root.gpu.enter_main()?;
4120 let mut root_input = root.uninit(values)?;
4121 root.stream()
4122 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
4123 root.stream().synchronize()?; root_input
4125 };
4126 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
4127 let _main = root.gpu.enter_main()?;
4128 root.stream().synchronize()?;
4129 Ok(reduced)
4130 }
4131
4132 fn step_bf16_row_native_reduce_from_root(
4137 &self,
4138 matrix: &ResidentStepBf16RowParallel,
4139 root_input: &CudaSlice<f32>,
4140 tokens: usize,
4141 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4142 let root = &self.ranks[0];
4143 let output_len = tokens
4144 .checked_mul(matrix.out_features)
4145 .ok_or("native Step BF16 row output size overflow")?;
4146 let mut reduced = {
4147 let _main = root.gpu.enter_main()?;
4148 root.htod(&vec![0.0f32; output_len])?
4149 };
4150 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4151 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
4152 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
4153 let mut remote_partial_keepalive = Vec::new();
4154 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4155 for (block, resident) in blocks.iter().enumerate() {
4156 let global_block = rank * blocks_per_rank + block;
4157 let col_start = global_block * matrix.canonical_chunk_cols;
4158 let block_len = tokens
4159 .checked_mul(matrix.canonical_chunk_cols)
4160 .ok_or("native Step BF16 row block size overflow")?;
4161 let block_input = if self.bulk_p2p {
4162 let root_packed = {
4163 let _main = root.gpu.enter_main()?;
4164 let mut root_packed = root.uninit(block_len)?;
4165 root.copy_rows_strided(
4166 root_input,
4167 &mut root_packed,
4168 matrix.canonical_chunk_cols,
4169 tokens,
4170 matrix.in_features,
4171 col_start,
4172 )?;
4173 root_packed
4174 };
4175 if rank == 0 {
4176 root_packed
4177 } else {
4178 {
4181 let _main = root.gpu.enter_main()?;
4182 root.stream().synchronize()?;
4183 }
4184 let engine = &self.ranks[rank];
4185 let _main = engine.gpu.enter_main()?;
4186 let mut block_input = engine.uninit(block_len)?;
4187 engine
4188 .stream()
4189 .memcpy_dtod(&root_packed, &mut block_input)?;
4190 root_packed_keepalive.push(root_packed);
4191 block_input
4192 }
4193 } else {
4194 let engine = &self.ranks[rank];
4195 let _main = engine.gpu.enter_main()?;
4196 let mut block_input = engine.uninit(block_len)?;
4197 for token in 0..tokens {
4198 let source_start = token * matrix.in_features + col_start;
4199 let source = root_input
4200 .slice(source_start..source_start + matrix.canonical_chunk_cols);
4201 let destination_start = token * matrix.canonical_chunk_cols;
4202 let mut destination = block_input.slice_mut(
4203 destination_start..destination_start + matrix.canonical_chunk_cols,
4204 );
4205 engine.stream().memcpy_dtod(&source, &mut destination)?;
4206 }
4207 block_input
4208 };
4209 let partial = run_resident_bf16_rank_device(
4210 &self.ranks[rank],
4211 resident,
4212 &block_input,
4213 tokens,
4214 None,
4215 self.bulk_p2p,
4216 )?;
4217 block_input_keepalive.push(block_input);
4218 let root_partial = if rank == 0 {
4219 partial
4220 } else {
4221 {
4224 let engine = &self.ranks[rank];
4225 let _main = engine.gpu.enter_main()?;
4226 engine.stream().synchronize()?;
4227 }
4228 let _main = root.gpu.enter_main()?;
4229 let mut peer_partial = root.uninit(output_len)?;
4230 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4231 remote_partial_keepalive.push(partial);
4232 peer_partial
4233 };
4234 let next = {
4235 let _main = root.gpu.enter_main()?;
4236 let mut next = root.uninit(output_len)?;
4237 root.add(&reduced, &root_partial, &mut next, output_len)?;
4238 next
4239 };
4240 reduced = next;
4241 }
4242 }
4243 {
4244 let _main = root.gpu.enter_main()?;
4245 root.stream().synchronize()?;
4246 }
4247 drop(remote_partial_keepalive);
4248 drop(root_packed_keepalive);
4249 drop(block_input_keepalive);
4250 Ok(reduced)
4251 }
4252
4253 pub fn step_bf16_row_parallel_resident_root_device(
4256 &self,
4257 matrix: &ResidentStepBf16RowParallel,
4258 rank_activations: &[CudaSlice<f32>],
4259 tokens: usize,
4260 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4261 if self.ranks.len() > 1 && !self.native_p2p {
4262 return Err(
4263 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
4264 );
4265 }
4266 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4267 let local_width = matrix.in_features / self.ranks.len();
4268 let shard_len = tokens
4269 .checked_mul(local_width)
4270 .ok_or("device Step BF16 row shard size overflow")?;
4271 if tokens == 0
4272 || rank_activations.len() != self.ranks.len()
4273 || rank_activations
4274 .iter()
4275 .zip(&self.ranks)
4276 .any(|(rows, engine)| {
4277 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
4278 })
4279 {
4280 return Err("device Step BF16 row activation shard geometry changed".into());
4281 }
4282
4283 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4284 let mut block_inputs = Vec::with_capacity(self.ranks.len());
4285 let mut partials = Vec::with_capacity(self.ranks.len());
4286 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4287 if blocks.len() != blocks_per_rank {
4288 return Err(format!(
4289 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
4290 blocks.len()
4291 )
4292 .into());
4293 }
4294 let engine = &self.ranks[rank];
4295 let _main = engine.gpu.enter_main()?;
4296 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
4297 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
4298 for (block, resident) in blocks.iter().enumerate() {
4299 let block_len = tokens
4300 .checked_mul(matrix.canonical_chunk_cols)
4301 .ok_or("device Step BF16 row block size overflow")?;
4302 let mut block_input = engine.uninit(block_len)?;
4303 let local_col_start = block * matrix.canonical_chunk_cols;
4304 if self.bulk_p2p {
4305 engine.copy_rows_strided(
4306 &rank_activations[rank],
4307 &mut block_input,
4308 matrix.canonical_chunk_cols,
4309 tokens,
4310 local_width,
4311 local_col_start,
4312 )?;
4313 } else {
4314 for token in 0..tokens {
4315 let source_start = token * local_width + local_col_start;
4316 let source = rank_activations[rank]
4317 .slice(source_start..source_start + matrix.canonical_chunk_cols);
4318 let destination_start = token * matrix.canonical_chunk_cols;
4319 let mut destination = block_input.slice_mut(
4320 destination_start..destination_start + matrix.canonical_chunk_cols,
4321 );
4322 engine.stream().memcpy_dtod(&source, &mut destination)?;
4323 }
4324 }
4325 let partial = run_resident_bf16_rank_device(
4326 engine,
4327 resident,
4328 &block_input,
4329 tokens,
4330 None,
4331 self.bulk_p2p,
4332 )?;
4333 rank_inputs.push(block_input);
4334 rank_partials.push(partial);
4335 }
4336 block_inputs.push(rank_inputs);
4337 partials.push(rank_partials);
4338 }
4339 for engine in self.ranks.iter().skip(1) {
4340 let _main = engine.gpu.enter_main()?;
4341 engine.stream().synchronize()?;
4342 }
4343
4344 let output_len = tokens
4345 .checked_mul(matrix.out_features)
4346 .ok_or("device Step BF16 row output size overflow")?;
4347 let root = &self.ranks[0];
4348 let _main = root.gpu.enter_main()?;
4349 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
4350 let mut remote_partials = Vec::new();
4351 for (rank, rank_partials) in partials.into_iter().enumerate() {
4352 for partial in rank_partials {
4353 let root_partial = if rank == 0 {
4354 partial
4355 } else {
4356 let mut peer_partial = root.uninit(output_len)?;
4357 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4358 remote_partials.push(partial);
4359 peer_partial
4360 };
4361 let mut next = root.uninit(output_len)?;
4362 root.add(&reduced, &root_partial, &mut next, output_len)?;
4363 reduced = next;
4364 }
4365 }
4366 root.stream().synchronize()?;
4367 drop(remote_partials);
4368 drop(block_inputs);
4369 Ok(reduced)
4370 }
4371
4372 pub fn step_bf16_row_parallel_resident_replicated_device(
4374 &self,
4375 matrix: &ResidentStepBf16RowParallel,
4376 rank_activations: &[CudaSlice<f32>],
4377 tokens: usize,
4378 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4379 let reduced =
4380 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
4381 let output_len = tokens
4382 .checked_mul(matrix.out_features)
4383 .ok_or("device Step BF16 row output size overflow")?;
4384 let mut ranks = Vec::with_capacity(self.ranks.len());
4385 ranks.push(reduced);
4386 for engine in self.ranks.iter().skip(1) {
4387 let _main = engine.gpu.enter_main()?;
4388 let mut peer_output = engine.uninit(output_len)?;
4389 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
4390 ranks.push(peer_output);
4391 }
4392 Ok(ResidentReplicatedDeviceRows {
4393 ranks,
4394 tokens,
4395 width: matrix.out_features,
4396 })
4397 }
4398
4399 pub fn upload_expert(
4400 &self,
4401 gate: E4m3BlockMatrix<'_>,
4402 up: E4m3BlockMatrix<'_>,
4403 down: E4m3BlockMatrix<'_>,
4404 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
4405 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4406 return Err("TP expert gate/up dimensions differ".into());
4407 }
4408 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4409 return Err(format!(
4410 "TP expert down {}x{} does not invert gate/up {}x{}",
4411 down.out_features, down.in_features, gate.out_features, gate.in_features
4412 )
4413 .into());
4414 }
4415 Ok(ResidentTpExpert {
4416 gate: self.upload_column_parallel(gate)?,
4417 up: self.upload_column_parallel(up)?,
4418 down: self.upload_row_parallel(down)?,
4419 input_width: gate.in_features,
4420 expert_width: gate.out_features,
4421 })
4422 }
4423
4424 pub fn run_expert(
4425 &self,
4426 expert: &ResidentTpExpert,
4427 input: &[f32],
4428 tokens: usize,
4429 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4430 validate_activations(input, tokens, expert.input_width)?;
4431 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
4432 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
4433 let activated: Vec<f32> = gate
4434 .gathered
4435 .iter()
4436 .zip(&up.gathered)
4437 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4438 .collect();
4439 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
4440 Ok(self
4441 .row_parallel_resident(&expert.down, &activated, tokens)?
4442 .reduced)
4443 }
4444
4445 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel(
4447 &self,
4448 gate: E4m3ExpertBank<'_>,
4449 up: E4m3ExpertBank<'_>,
4450 down: E4m3ExpertBank<'_>,
4451 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
4452 gate.validate()?;
4453 up.validate()?;
4454 down.validate()?;
4455 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4456 return Err("EP gate/up/down expert counts differ".into());
4457 }
4458 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4459 return Err("EP gate/up dimensions differ".into());
4460 }
4461 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4462 return Err(format!(
4463 "EP down {}x{} does not invert gate/up {}x{}",
4464 down.out_features, down.in_features, gate.out_features, gate.in_features
4465 )
4466 .into());
4467 }
4468 if gate.expert_count % self.ranks.len() != 0 {
4469 return Err(format!(
4470 "EP expert count {} is not divisible by {} ranks",
4471 gate.expert_count,
4472 self.ranks.len()
4473 )
4474 .into());
4475 }
4476
4477 let per_rank = gate.expert_count / self.ranks.len();
4478 let mut ranks = Vec::with_capacity(self.ranks.len());
4479 for (rank, engine) in self.ranks.iter().enumerate() {
4480 let expert_range = rank * per_rank..(rank + 1) * per_rank;
4481 ranks.push(ResidentEpRank {
4482 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
4483 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
4484 down: upload_expert_bank_rank(engine, down, expert_range)?,
4485 });
4486 }
4487 Ok(ResidentExpertParallel {
4488 ranks,
4489 expert_count: gate.expert_count,
4490 input_width: gate.in_features,
4491 expert_width: gate.out_features,
4492 })
4493 }
4494
4495 #[allow(clippy::too_many_arguments)]
4501 pub fn prepare_step_grouped_fp8_gate(
4502 &self,
4503 gate: E4m3ExpertBank<'_>,
4504 up: E4m3ExpertBank<'_>,
4505 down: E4m3ExpertBank<'_>,
4506 input: &[f32],
4507 tokens: usize,
4508 selected: &[usize],
4509 activation_limit: Option<f32>,
4510 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
4511 gate.validate()?;
4512 up.validate()?;
4513 down.validate()?;
4514 validate_step_expert_activation_limit(activation_limit)?;
4515 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
4516 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
4517 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
4518 {
4519 return Err(format!(
4520 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
4521 got gate/up/down={}/{}/{}",
4522 gate.expert_count, up.expert_count, down.expert_count,
4523 )
4524 .into());
4525 }
4526 if gate.in_features != up.in_features
4527 || gate.out_features != STEP_GROUPED_FP8_WIDTH
4528 || up.out_features != STEP_GROUPED_FP8_WIDTH
4529 || down.in_features != STEP_GROUPED_FP8_WIDTH
4530 || down.out_features != gate.in_features
4531 {
4532 return Err(format!(
4533 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
4534 gate.out_features,
4535 gate.in_features,
4536 up.out_features,
4537 up.in_features,
4538 down.out_features,
4539 down.in_features,
4540 )
4541 .into());
4542 }
4543 validate_activations(input, tokens, gate.in_features)?;
4544 let pairs = tokens
4545 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4546 .ok_or("official Step grouped FP8 route count overflow")?;
4547 if selected.len() != pairs {
4548 return Err(format!(
4549 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
4550 ({pairs})",
4551 selected.len()
4552 )
4553 .into());
4554 }
4555 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
4556 let mut unique = routes.to_vec();
4557 unique.sort_unstable();
4558 unique.dedup();
4559 if unique.len() != STEP_GROUPED_FP8_TOP_K {
4560 return Err(format!(
4561 "official Step grouped FP8 token {token} routes are not top-8 unique: \
4562 {routes:?}"
4563 )
4564 .into());
4565 }
4566 }
4567
4568 let engine = self
4569 .ranks
4570 .first()
4571 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4572 let _main = engine.gpu.enter_main()?;
4573 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
4574 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
4575 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
4576 let down = upload_expert_bank_rank(engine, down, expert_range)?;
4577 let input = engine.htod(input)?;
4578 let route_csr = ExpertCsr::from_token_routes(
4579 STEP_GROUPED_FP8_EXPERTS,
4580 tokens,
4581 STEP_GROUPED_FP8_TOP_K,
4582 selected,
4583 )?
4584 .upload(engine)?;
4585 let pair_rows = (0..pairs).collect::<Vec<_>>();
4586 let down_csr =
4587 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
4588 .upload(engine)?;
4589 let gate_workspace =
4590 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
4591 let up_workspace =
4592 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
4593 let down_workspace =
4594 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
4595 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
4596 Ok(PreparedStepGroupedFp8Gate {
4597 device: engine.ctx().ordinal(),
4598 gate,
4599 up,
4600 down,
4601 input,
4602 route_csr,
4603 down_csr,
4604 gate_workspace,
4605 up_workspace,
4606 down_workspace,
4607 activation,
4608 activation_limit,
4609 tokens,
4610 pairs,
4611 })
4612 }
4613
4614 pub fn run_step_grouped_fp8_gate(
4616 &self,
4617 plan: &mut PreparedStepGroupedFp8Gate,
4618 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4619 let engine = self
4620 .ranks
4621 .first()
4622 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4623 if engine.ctx().ordinal() != plan.device {
4624 return Err(format!(
4625 "official Step grouped FP8 plan device {} != rank-zero device {}",
4626 plan.device,
4627 engine.ctx().ordinal()
4628 )
4629 .into());
4630 }
4631 let _main = engine.gpu.enter_main()?;
4632
4633 plan.gate_workspace.quantize(engine, &plan.input)?;
4634 plan.gate_workspace.project(
4635 engine,
4636 &plan.gate.codes,
4637 &plan.gate.scales,
4638 &plan.route_csr,
4639 plan.gate.code_stride,
4640 plan.gate.scale_stride,
4641 1.0,
4642 )?;
4643 plan.up_workspace.quantize(engine, &plan.input)?;
4644 plan.up_workspace.project(
4645 engine,
4646 &plan.up.codes,
4647 &plan.up.scales,
4648 &plan.route_csr,
4649 plan.up.code_stride,
4650 plan.up.scale_stride,
4651 1.0,
4652 )?;
4653 if let Some(limit) = plan.activation_limit {
4654 engine.silu_clamped_mul_host_expf(
4655 plan.gate_workspace.output(),
4656 plan.up_workspace.output(),
4657 limit,
4658 &mut plan.activation,
4659 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4660 )?;
4661 } else {
4662 engine.silu_mul_host_expf(
4663 plan.gate_workspace.output(),
4664 plan.up_workspace.output(),
4665 &mut plan.activation,
4666 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4667 )?;
4668 }
4669 plan.down_workspace.quantize(engine, &plan.activation)?;
4670 plan.down_workspace.project(
4671 engine,
4672 &plan.down.codes,
4673 &plan.down.scales,
4674 &plan.down_csr,
4675 plan.down.code_stride,
4676 plan.down.scale_stride,
4677 1.0,
4678 )?;
4679
4680 Ok(StepGroupedFp8ProjectionOutput {
4681 gate: engine.dtoh(plan.gate_workspace.output())?,
4682 up: engine.dtoh(plan.up_workspace.output())?,
4683 down: engine.dtoh(plan.down_workspace.output())?,
4684 })
4685 }
4686
4687 pub fn prepare_step_grouped_expert_parallel_gate(
4688 &self,
4689 experts: &ResidentExpertParallel,
4690 input: &[f32],
4691 tokens: usize,
4692 selected: &[usize],
4693 activation_limit: Option<f32>,
4694 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4695 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
4696 experts,
4697 input,
4698 tokens,
4699 selected,
4700 activation_limit,
4701 tokens,
4702 )
4703 }
4704
4705 #[allow(clippy::too_many_arguments)]
4706 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
4707 &self,
4708 experts: &ResidentExpertParallel,
4709 input: &[f32],
4710 tokens: usize,
4711 selected: &[usize],
4712 activation_limit: Option<f32>,
4713 max_tokens: usize,
4714 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4715 if !self.native_p2p || !self.ep_device_arithmetic {
4716 return Err(
4717 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
4718 );
4719 }
4720 validate_step_expert_activation_limit(activation_limit)?;
4721 validate_ep_residency(&self.ranks, experts)?;
4722 validate_activations(input, tokens, experts.input_width)?;
4723 if max_tokens < tokens || max_tokens > i32::MAX as usize {
4724 return Err(format!(
4725 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
4726 )
4727 .into());
4728 }
4729 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4730 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4731 {
4732 return Err(format!(
4733 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4734 STEP_GROUPED_FP8_EXPERTS,
4735 STEP_GROUPED_FP8_WIDTH,
4736 experts.expert_count,
4737 experts.expert_width,
4738 )
4739 .into());
4740 }
4741 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4742 let max_pairs = max_tokens
4743 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4744 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4745 let input_capacity = max_tokens
4746 .checked_mul(experts.input_width)
4747 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4748
4749 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4750 for engine in &self.ranks {
4751 let _main = engine.gpu.enter_main()?;
4752 rank_inputs.push(engine.uninit(input_capacity)?);
4753 }
4754
4755 let mut owners = Vec::with_capacity(self.ranks.len());
4756 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4757 if rank.gate.expert_range != rank.up.expert_range
4758 || rank.gate.expert_range != rank.down.expert_range
4759 {
4760 return Err(format!(
4761 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4762 owner_rank
4763 )
4764 .into());
4765 }
4766 let local_experts = rank.gate.expert_range.len();
4767 let engine = &self.ranks[owner_rank];
4768 let _main = engine.gpu.enter_main()?;
4769 let route_csr =
4770 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4771 let down_csr =
4772 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4773 let gate_workspace = Fp8GroupedWorkspace::new(
4774 engine,
4775 experts.input_width,
4776 experts.expert_width,
4777 max_tokens,
4778 max_pairs,
4779 )?;
4780 let up_workspace = Fp8GroupedWorkspace::new(
4781 engine,
4782 experts.input_width,
4783 experts.expert_width,
4784 max_tokens,
4785 max_pairs,
4786 )?;
4787 let down_workspace = Fp8GroupedWorkspace::new(
4788 engine,
4789 experts.expert_width,
4790 experts.input_width,
4791 max_pairs,
4792 max_pairs,
4793 )?;
4794 let activation = engine.uninit(
4795 max_pairs
4796 .checked_mul(experts.expert_width)
4797 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4798 )?;
4799 owners.push(PreparedStepGroupedExpertOwner {
4800 rank: owner_rank,
4801 global_pairs: Vec::new(),
4802 route_csr,
4803 down_csr,
4804 gate_workspace,
4805 up_workspace,
4806 down_workspace,
4807 activation,
4808 });
4809 }
4810
4811 let mut plan = PreparedStepGroupedExpertParallelGate {
4812 rank_inputs,
4813 owners,
4814 activation_limit,
4815 tokens: 0,
4816 pairs: 0,
4817 max_tokens,
4818 max_pairs,
4819 input_width: experts.input_width,
4820 expert_width: experts.expert_width,
4821 generation: 0,
4822 executed_generation: None,
4823 ready: false,
4824 };
4825 self.refresh_step_grouped_expert_parallel_gate(
4826 experts, &mut plan, input, tokens, selected,
4827 )?;
4828 Ok(plan)
4829 }
4830
4831 #[allow(clippy::type_complexity)] fn prepare_step_grouped_expert_parallel_refresh(
4833 &self,
4834 experts: &ResidentExpertParallel,
4835 plan: &PreparedStepGroupedExpertParallelGate,
4836 tokens: usize,
4837 selected: &[usize],
4838 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4839 {
4840 validate_ep_residency(&self.ranks, experts)?;
4841 if plan.rank_inputs.len() != self.ranks.len()
4842 || plan.owners.len() != self.ranks.len()
4843 || plan.input_width != experts.input_width
4844 || plan.expert_width != experts.expert_width
4845 || tokens > plan.max_tokens
4846 {
4847 return Err(format!(
4848 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4849 input={}/{} expert={}/{} tokens={}/{}",
4850 plan.rank_inputs.len(),
4851 self.ranks.len(),
4852 plan.owners.len(),
4853 self.ranks.len(),
4854 plan.input_width,
4855 experts.input_width,
4856 plan.expert_width,
4857 experts.expert_width,
4858 tokens,
4859 plan.max_tokens,
4860 )
4861 .into());
4862 }
4863 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4864 if pairs > plan.max_pairs {
4865 return Err(format!(
4866 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4867 plan.max_pairs
4868 )
4869 .into());
4870 }
4871 let next_generation = plan
4872 .generation
4873 .checked_add(1)
4874 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4875 let owner_routes = partition_expert_owner_routes(
4876 experts.expert_count,
4877 self.ranks.len(),
4878 tokens,
4879 STEP_GROUPED_FP8_TOP_K,
4880 selected,
4881 )?;
4882 let mut schedules = Vec::with_capacity(self.ranks.len());
4883 for routes in owner_routes {
4884 if routes.selected.is_empty() {
4885 schedules.push(None);
4886 continue;
4887 }
4888 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4889 let local_pairs = routes.selected.len();
4890 let route_csr = ExpertCsr::from_pair_rows(
4891 local_experts,
4892 tokens,
4893 &routes.selected,
4894 &routes.token_rows,
4895 )?;
4896 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4897 let down_csr = ExpertCsr::from_pair_rows(
4898 local_experts,
4899 local_pairs,
4900 &routes.selected,
4901 &down_rows,
4902 )?;
4903 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4904 global_pairs: routes.global_pairs,
4905 route_csr,
4906 down_csr,
4907 }));
4908 }
4909 Ok((pairs, next_generation, schedules))
4910 }
4911
4912 fn commit_step_grouped_expert_parallel_refresh(
4913 &self,
4914 plan: &mut PreparedStepGroupedExpertParallelGate,
4915 tokens: usize,
4916 pairs: usize,
4917 next_generation: u64,
4918 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4919 ) -> Result<(), Box<dyn std::error::Error>> {
4920 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4921 let engine = &self.ranks[owner.rank];
4922 let _main = engine.gpu.enter_main()?;
4923 if let Some(schedule) = schedule {
4924 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4925 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4926 owner.global_pairs = schedule.global_pairs;
4927 } else {
4928 owner.route_csr.clear();
4929 owner.down_csr.clear();
4930 owner.global_pairs.clear();
4931 }
4932 }
4933 plan.tokens = tokens;
4934 plan.pairs = pairs;
4935 plan.generation = next_generation;
4936 plan.ready = true;
4937 Ok(())
4938 }
4939
4940 pub fn refresh_step_grouped_expert_parallel_gate(
4941 &self,
4942 experts: &ResidentExpertParallel,
4943 plan: &mut PreparedStepGroupedExpertParallelGate,
4944 input: &[f32],
4945 tokens: usize,
4946 selected: &[usize],
4947 ) -> Result<(), Box<dyn std::error::Error>> {
4948 validate_activations(input, tokens, experts.input_width)?;
4949 let (pairs, next_generation, schedules) =
4950 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4951
4952 plan.ready = false;
4953 plan.executed_generation = None;
4954 {
4955 let root = &self.ranks[0];
4956 let _main = root.gpu.enter_main()?;
4957 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4958 root.stream().memcpy_htod(input, &mut destination)?;
4959 root.stream().synchronize()?;
4960 }
4961 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4962 let root_input = &root_inputs[0];
4963 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4964 let engine = &self.ranks[rank + 1];
4965 let _main = engine.gpu.enter_main()?;
4966 let mut destination = peer_input.slice_mut(0..input.len());
4967 engine
4968 .stream()
4969 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4970 }
4971 self.commit_step_grouped_expert_parallel_refresh(
4972 plan,
4973 tokens,
4974 pairs,
4975 next_generation,
4976 schedules,
4977 )
4978 }
4979
4980 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4985 &self,
4986 experts: &ResidentExpertParallel,
4987 plan: &mut PreparedStepGroupedExpertParallelGate,
4988 input: &CudaSlice<f32>,
4989 tokens: usize,
4990 selected: &[usize],
4991 ) -> Result<(), Box<dyn std::error::Error>> {
4992 let input_values = tokens
4993 .checked_mul(experts.input_width)
4994 .ok_or("Step owner-grouped FP8 input size overflow")?;
4995 let root = self
4996 .ranks
4997 .first()
4998 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4999 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
5000 return Err(format!(
5001 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
5002 device {}",
5003 input.len(),
5004 input.ordinal(),
5005 input_values,
5006 root.ctx().ordinal(),
5007 )
5008 .into());
5009 }
5010 let (pairs, next_generation, schedules) =
5011 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
5012
5013 plan.ready = false;
5014 plan.executed_generation = None;
5015 {
5016 let _main = root.gpu.enter_main()?;
5017 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
5018 root.stream()
5019 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
5020 root.stream().synchronize()?;
5021 }
5022 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
5023 let root_input = &root_inputs[0];
5024 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
5025 let engine = &self.ranks[rank + 1];
5026 let _main = engine.gpu.enter_main()?;
5027 let mut destination = peer_input.slice_mut(0..input_values);
5028 engine
5029 .stream()
5030 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
5031 }
5032 self.commit_step_grouped_expert_parallel_refresh(
5033 plan,
5034 tokens,
5035 pairs,
5036 next_generation,
5037 schedules,
5038 )
5039 }
5040
5041 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
5046 &self,
5047 experts: &ResidentExpertParallel,
5048 plan: &mut PreparedStepGroupedExpertParallelGate,
5049 input: &ResidentReplicatedDeviceRows,
5050 ) -> Result<(), Box<dyn std::error::Error>> {
5051 validate_ep_residency(&self.ranks, experts)?;
5052 validate_replicated_device_rows(&self.ranks, input)?;
5053 if !plan.ready
5054 || input.tokens != plan.tokens
5055 || input.width != plan.input_width
5056 || input.tokens > plan.max_tokens
5057 || plan.rank_inputs.len() != self.ranks.len()
5058 || plan.owners.len() != self.ranks.len()
5059 || plan.input_width != experts.input_width
5060 || plan.expert_width != experts.expert_width
5061 {
5062 return Err("Step owner-grouped replicated input geometry changed".into());
5063 }
5064 let values = input
5065 .tokens
5066 .checked_mul(input.width)
5067 .ok_or("Step owner-grouped replicated input size overflow")?;
5068 let next_generation = plan
5069 .generation
5070 .checked_add(1)
5071 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
5072 plan.ready = false;
5073 plan.executed_generation = None;
5074 for (rank, engine) in self.ranks.iter().enumerate() {
5075 let _main = engine.gpu.enter_main()?;
5076 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
5077 engine
5078 .stream()
5079 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
5080 }
5081 plan.generation = next_generation;
5082 plan.ready = true;
5083 Ok(())
5084 }
5085
5086 pub fn execute_step_grouped_expert_parallel_gate(
5087 &self,
5088 experts: &ResidentExpertParallel,
5089 plan: &mut PreparedStepGroupedExpertParallelGate,
5090 ) -> Result<(), Box<dyn std::error::Error>> {
5091 validate_ep_residency(&self.ranks, experts)?;
5092 if !plan.ready
5093 || plan.rank_inputs.len() != self.ranks.len()
5094 || plan.owners.len() != self.ranks.len()
5095 || plan.input_width != experts.input_width
5096 || plan.expert_width != experts.expert_width
5097 {
5098 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
5099 }
5100 plan.executed_generation = None;
5101
5102 for owner in &mut plan.owners {
5103 if owner.global_pairs.is_empty() {
5104 continue;
5105 }
5106 let engine = &self.ranks[owner.rank];
5107 let bank = &experts.ranks[owner.rank];
5108 let _main = engine.gpu.enter_main()?;
5109 let local_pairs = owner.global_pairs.len();
5110 owner.gate_workspace.quantize_for_shape(
5111 engine,
5112 &plan.rank_inputs[owner.rank],
5113 plan.tokens,
5114 local_pairs,
5115 )?;
5116 owner.gate_workspace.project(
5117 engine,
5118 &bank.gate.codes,
5119 &bank.gate.scales,
5120 &owner.route_csr,
5121 bank.gate.code_stride,
5122 bank.gate.scale_stride,
5123 1.0,
5124 )?;
5125 owner.up_workspace.quantize_for_shape(
5126 engine,
5127 &plan.rank_inputs[owner.rank],
5128 plan.tokens,
5129 local_pairs,
5130 )?;
5131 owner.up_workspace.project(
5132 engine,
5133 &bank.up.codes,
5134 &bank.up.scales,
5135 &owner.route_csr,
5136 bank.up.code_stride,
5137 bank.up.scale_stride,
5138 1.0,
5139 )?;
5140 }
5141 for owner in &mut plan.owners {
5142 if owner.global_pairs.is_empty() {
5143 continue;
5144 }
5145 let engine = &self.ranks[owner.rank];
5146 let _main = engine.gpu.enter_main()?;
5147 let values = owner.global_pairs.len() * plan.expert_width;
5148 if let Some(limit) = plan.activation_limit {
5149 engine.silu_clamped_mul_host_expf(
5150 owner.gate_workspace.output(),
5151 owner.up_workspace.output(),
5152 limit,
5153 &mut owner.activation,
5154 values,
5155 )?;
5156 } else {
5157 engine.silu_mul_host_expf(
5158 owner.gate_workspace.output(),
5159 owner.up_workspace.output(),
5160 &mut owner.activation,
5161 values,
5162 )?;
5163 }
5164 }
5165 for owner in &mut plan.owners {
5166 if owner.global_pairs.is_empty() {
5167 continue;
5168 }
5169 let engine = &self.ranks[owner.rank];
5170 let bank = &experts.ranks[owner.rank];
5171 let _main = engine.gpu.enter_main()?;
5172 let local_pairs = owner.global_pairs.len();
5173 owner.down_workspace.quantize_for_shape(
5174 engine,
5175 &owner.activation,
5176 local_pairs,
5177 local_pairs,
5178 )?;
5179 owner.down_workspace.project(
5180 engine,
5181 &bank.down.codes,
5182 &bank.down.scales,
5183 &owner.down_csr,
5184 bank.down.code_stride,
5185 bank.down.scale_stride,
5186 1.0,
5187 )?;
5188 }
5189 plan.executed_generation = Some(plan.generation);
5190 Ok(())
5191 }
5192
5193 pub fn collect_step_grouped_expert_parallel_gate(
5194 &self,
5195 plan: &PreparedStepGroupedExpertParallelGate,
5196 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
5197 if !plan.ready || plan.executed_generation != Some(plan.generation) {
5198 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
5199 }
5200 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
5201 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
5202 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
5203 for owner in &plan.owners {
5204 if owner.global_pairs.is_empty() {
5205 continue;
5206 }
5207 let engine = &self.ranks[owner.rank];
5208 let _main = engine.gpu.enter_main()?;
5209 let owner_gate = engine.dtoh_view(
5210 &owner
5211 .gate_workspace
5212 .output()
5213 .slice(0..owner.gate_workspace.output_len()),
5214 )?;
5215 let owner_up = engine.dtoh_view(
5216 &owner
5217 .up_workspace
5218 .output()
5219 .slice(0..owner.up_workspace.output_len()),
5220 )?;
5221 let owner_down = engine.dtoh_view(
5222 &owner
5223 .down_workspace
5224 .output()
5225 .slice(0..owner.down_workspace.output_len()),
5226 )?;
5227 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
5228 let local_expert = local_pair * plan.expert_width;
5229 let global_expert = global_pair * plan.expert_width;
5230 gate[global_expert..global_expert + plan.expert_width]
5231 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
5232 up[global_expert..global_expert + plan.expert_width]
5233 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
5234
5235 let local_hidden = local_pair * plan.input_width;
5236 let global_hidden = global_pair * plan.input_width;
5237 down[global_hidden..global_hidden + plan.input_width]
5238 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
5239 }
5240 }
5241 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
5242 }
5243
5244 pub fn run_step_grouped_expert_parallel_gate(
5245 &self,
5246 experts: &ResidentExpertParallel,
5247 plan: &mut PreparedStepGroupedExpertParallelGate,
5248 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
5249 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
5250 self.collect_step_grouped_expert_parallel_gate(plan)
5251 }
5252
5253 pub fn prepare_step_grouped_expert_parallel_combine(
5254 &self,
5255 plan: &PreparedStepGroupedExpertParallelGate,
5256 route_weights: &[f32],
5257 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
5258 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
5259 return Err(
5260 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
5261 );
5262 }
5263 let owner_pairs = plan
5264 .owners
5265 .iter()
5266 .map(|owner| owner.global_pairs.as_slice())
5267 .collect::<Vec<_>>();
5268 let shape = validate_weighted_route_combine(
5269 plan.input_width,
5270 STEP_GROUPED_FP8_TOP_K,
5271 plan.max_tokens,
5272 plan.tokens,
5273 &owner_pairs,
5274 route_weights,
5275 )?;
5276 if shape.max_pairs != plan.max_pairs {
5277 return Err(format!(
5278 "Step owner-grouped combine capacity {} != projection capacity {}",
5279 shape.max_pairs, plan.max_pairs
5280 )
5281 .into());
5282 }
5283 let root = self
5284 .ranks
5285 .first()
5286 .ok_or("Step owner-grouped combine has no root rank")?;
5287 let slot_values = shape
5288 .max_pairs
5289 .checked_mul(plan.input_width)
5290 .ok_or("Step owner-grouped combine slot capacity overflow")?;
5291 let output_values = plan
5292 .max_tokens
5293 .checked_mul(plan.input_width)
5294 .ok_or("Step owner-grouped combine output capacity overflow")?;
5295 let (root_device, owners, peer_staging, slots, weights, output) = {
5296 let _main = root.gpu.enter_main()?;
5297 let mut owners = Vec::with_capacity(plan.owners.len());
5298 for _ in &plan.owners {
5299 owners.push(PreparedPeerWeightedRouteOwner {
5300 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
5301 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
5302 weights: root.htod(&vec![0.0; shape.max_pairs])?,
5303 active_pairs: 0,
5304 });
5305 }
5306 (
5307 root.ctx().ordinal(),
5308 owners,
5309 root.uninit(slot_values)?,
5310 root.uninit(slot_values)?,
5311 root.uninit(shape.max_pairs)?,
5312 root.uninit(output_values)?,
5313 )
5314 };
5315 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
5316 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
5317 for engine in self.ranks.iter().skip(1) {
5318 let _main = engine.gpu.enter_main()?;
5319 peer_devices.push(engine.ctx().ordinal());
5320 peer_outputs.push(engine.uninit(output_values)?);
5321 }
5322 let mut combine = PreparedPeerWeightedRouteCombine {
5323 root_device,
5324 owners,
5325 peer_staging,
5326 slots,
5327 weights,
5328 output,
5329 peer_devices,
5330 peer_outputs,
5331 width: plan.input_width,
5332 experts_per_token: STEP_GROUPED_FP8_TOP_K,
5333 max_tokens: plan.max_tokens,
5334 max_pairs: shape.max_pairs,
5335 tokens: 0,
5336 pairs: 0,
5337 projection_generation: 0,
5338 output_generation: None,
5339 broadcast_generation: None,
5340 ready: false,
5341 };
5342 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
5343 Ok(combine)
5344 }
5345
5346 pub fn refresh_step_grouped_expert_parallel_combine(
5347 &self,
5348 plan: &PreparedStepGroupedExpertParallelGate,
5349 combine: &mut PreparedPeerWeightedRouteCombine,
5350 route_weights: &[f32],
5351 ) -> Result<(), Box<dyn std::error::Error>> {
5352 let output_capacity = combine
5353 .max_tokens
5354 .checked_mul(combine.width)
5355 .ok_or("Step owner-grouped combine output capacity overflow")?;
5356 if !plan.ready
5357 || combine.owners.len() != plan.owners.len()
5358 || combine.peer_devices.len() + 1 != self.ranks.len()
5359 || combine.peer_outputs.len() + 1 != self.ranks.len()
5360 || combine.width != plan.input_width
5361 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
5362 || combine.max_tokens != plan.max_tokens
5363 || combine.max_pairs != plan.max_pairs
5364 || combine.output.len() < output_capacity
5365 || combine
5366 .peer_outputs
5367 .iter()
5368 .any(|output| output.len() < output_capacity)
5369 {
5370 return Err("Step owner-grouped combine/projection geometry changed".into());
5371 }
5372 if self
5373 .ranks
5374 .iter()
5375 .skip(1)
5376 .zip(&combine.peer_devices)
5377 .any(|(engine, &device)| engine.ctx().ordinal() != device)
5378 {
5379 return Err("Step owner-grouped combine peer devices changed".into());
5380 }
5381 let owner_pairs = plan
5382 .owners
5383 .iter()
5384 .map(|owner| owner.global_pairs.as_slice())
5385 .collect::<Vec<_>>();
5386 let shape = validate_weighted_route_combine(
5387 combine.width,
5388 combine.experts_per_token,
5389 combine.max_tokens,
5390 plan.tokens,
5391 &owner_pairs,
5392 route_weights,
5393 )?;
5394 if shape.max_pairs != combine.max_pairs {
5395 return Err("Step owner-grouped combine capacity changed during refresh".into());
5396 }
5397 let metadata = owner_pairs
5398 .iter()
5399 .map(|pairs| {
5400 let token_rows = pairs
5401 .iter()
5402 .map(|&pair| (pair / combine.experts_per_token) as i32)
5403 .collect::<Vec<_>>();
5404 let slots = pairs
5405 .iter()
5406 .map(|&pair| (pair % combine.experts_per_token) as i32)
5407 .collect::<Vec<_>>();
5408 let weights = pairs
5409 .iter()
5410 .map(|&pair| route_weights[pair])
5411 .collect::<Vec<_>>();
5412 (token_rows, slots, weights)
5413 })
5414 .collect::<Vec<_>>();
5415
5416 combine.ready = false;
5417 combine.output_generation = None;
5418 combine.broadcast_generation = None;
5419 let root = self
5420 .ranks
5421 .first()
5422 .ok_or("Step owner-grouped combine has no root rank")?;
5423 let _main = root.gpu.enter_main()?;
5424 if root.ctx().ordinal() != combine.root_device {
5425 return Err(format!(
5426 "Step owner-grouped combine root device changed {} != {}",
5427 root.ctx().ordinal(),
5428 combine.root_device
5429 )
5430 .into());
5431 }
5432 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
5433 if token_rows.is_empty() {
5434 owner.active_pairs = 0;
5435 continue;
5436 }
5437 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
5438 root.htod_i32_into(&mut owner.slots, &slots)?;
5439 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
5440 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
5441 owner.active_pairs = token_rows.len();
5442 }
5443 combine.tokens = plan.tokens;
5444 combine.pairs = shape.pairs;
5445 combine.projection_generation = plan.generation;
5446 combine.ready = true;
5447 Ok(())
5448 }
5449
5450 pub fn execute_step_grouped_expert_parallel_combine(
5451 &self,
5452 plan: &PreparedStepGroupedExpertParallelGate,
5453 combine: &mut PreparedPeerWeightedRouteCombine,
5454 ) -> Result<(), Box<dyn std::error::Error>> {
5455 if !plan.ready
5456 || plan.executed_generation != Some(plan.generation)
5457 || !combine.ready
5458 || combine.tokens != plan.tokens
5459 || combine.pairs != plan.pairs
5460 || combine.width != plan.input_width
5461 || combine.owners.len() != plan.owners.len()
5462 || combine.projection_generation != plan.generation
5463 {
5464 return Err("Step owner-grouped combine is stale or its geometry changed".into());
5465 }
5466 combine.output_generation = None;
5467 combine.broadcast_generation = None;
5468 for owner in &plan.owners {
5469 if owner.rank == 0 || owner.global_pairs.is_empty() {
5470 continue;
5471 }
5472 let engine = &self.ranks[owner.rank];
5473 let _main = engine.gpu.enter_main()?;
5474 engine.stream().synchronize()?;
5475 }
5476 let root = self
5477 .ranks
5478 .first()
5479 .ok_or("Step owner-grouped combine has no root rank")?;
5480 let _main = root.gpu.enter_main()?;
5481 if root.ctx().ordinal() != combine.root_device {
5482 return Err("Step owner-grouped combine is not resident on the root device".into());
5483 }
5484 for (index, owner) in plan.owners.iter().enumerate() {
5485 let metadata = &combine.owners[index];
5486 if owner.global_pairs.len() != metadata.active_pairs {
5487 return Err(format!(
5488 "Step owner-grouped combine owner {index} rows {} != metadata {}",
5489 owner.global_pairs.len(),
5490 metadata.active_pairs
5491 )
5492 .into());
5493 }
5494 if metadata.active_pairs == 0 {
5495 continue;
5496 }
5497 let values = metadata
5498 .active_pairs
5499 .checked_mul(combine.width)
5500 .ok_or("Step owner-grouped combine peer value count overflow")?;
5501 if owner.rank == 0 {
5502 root.scatter_slot(
5503 owner.down_workspace.output(),
5504 &metadata.token_rows,
5505 &metadata.slots,
5506 &metadata.weights,
5507 &mut combine.slots,
5508 &mut combine.weights,
5509 combine.width,
5510 combine.experts_per_token,
5511 metadata.active_pairs,
5512 )?;
5513 } else {
5514 let source = owner.down_workspace.output().slice(0..values);
5515 let mut destination = combine.peer_staging.slice_mut(0..values);
5516 root.stream().memcpy_dtod(&source, &mut destination)?;
5517 root.scatter_slot(
5518 &combine.peer_staging,
5519 &metadata.token_rows,
5520 &metadata.slots,
5521 &metadata.weights,
5522 &mut combine.slots,
5523 &mut combine.weights,
5524 combine.width,
5525 combine.experts_per_token,
5526 metadata.active_pairs,
5527 )?;
5528 }
5529 }
5530 root.reduce_slots_host(
5531 &combine.slots,
5532 &combine.weights,
5533 &mut combine.output,
5534 combine.width,
5535 combine.experts_per_token,
5536 combine.tokens,
5537 )?;
5538 combine.output_generation = Some(plan.generation);
5539 Ok(())
5540 }
5541
5542 pub fn collect_step_grouped_expert_parallel_combine(
5543 &self,
5544 plan: &PreparedStepGroupedExpertParallelGate,
5545 combine: &PreparedPeerWeightedRouteCombine,
5546 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5547 if !plan.ready
5548 || combine.output_generation != Some(plan.generation)
5549 || combine.projection_generation != plan.generation
5550 {
5551 return Err("Step owner-grouped combine output is stale or has not executed".into());
5552 }
5553 let root = self
5554 .ranks
5555 .first()
5556 .ok_or("Step owner-grouped combine has no root rank")?;
5557 let _main = root.gpu.enter_main()?;
5558 if root.ctx().ordinal() != combine.root_device {
5559 return Err("Step owner-grouped combine is not resident on the root device".into());
5560 }
5561 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
5562 }
5563
5564 pub fn copy_step_grouped_expert_parallel_combine_root(
5569 &self,
5570 plan: &PreparedStepGroupedExpertParallelGate,
5571 combine: &PreparedPeerWeightedRouteCombine,
5572 destination: &Engine,
5573 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5574 if !plan.ready
5575 || combine.output_generation != Some(plan.generation)
5576 || combine.projection_generation != plan.generation
5577 {
5578 return Err("Step owner-grouped combine output is stale or has not executed".into());
5579 }
5580 let root = self
5581 .ranks
5582 .first()
5583 .ok_or("Step owner-grouped combine has no root rank")?;
5584 if root.ctx().ordinal() != combine.root_device
5585 || destination.ctx().ordinal() != combine.root_device
5586 {
5587 return Err(format!(
5588 "Step owner-grouped combine root/destination devices {}/{} != {}",
5589 root.ctx().ordinal(),
5590 destination.ctx().ordinal(),
5591 combine.root_device,
5592 )
5593 .into());
5594 }
5595 let values = combine
5596 .tokens
5597 .checked_mul(combine.width)
5598 .ok_or("Step owner-grouped combine copy size overflow")?;
5599 {
5600 let _main = root.gpu.enter_main()?;
5601 root.stream().synchronize()?;
5602 }
5603 let _main = destination.gpu.enter_main()?;
5604 let mut output = destination.uninit(values)?;
5605 destination
5606 .stream()
5607 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
5608 Ok(output)
5609 }
5610
5611 pub fn broadcast_step_grouped_expert_parallel_combine(
5612 &self,
5613 plan: &PreparedStepGroupedExpertParallelGate,
5614 combine: &mut PreparedPeerWeightedRouteCombine,
5615 ) -> Result<(), Box<dyn std::error::Error>> {
5616 if !plan.ready
5617 || combine.output_generation != Some(plan.generation)
5618 || combine.projection_generation != plan.generation
5619 || combine.peer_devices.len() + 1 != self.ranks.len()
5620 || combine.peer_outputs.len() + 1 != self.ranks.len()
5621 {
5622 return Err("Step owner-grouped combine output cannot be broadcast".into());
5623 }
5624 combine.broadcast_generation = None;
5625 let values = combine
5626 .tokens
5627 .checked_mul(combine.width)
5628 .ok_or("Step owner-grouped combine broadcast size overflow")?;
5629 {
5630 let root = self
5631 .ranks
5632 .first()
5633 .ok_or("Step owner-grouped combine has no root rank")?;
5634 let _main = root.gpu.enter_main()?;
5635 if root.ctx().ordinal() != combine.root_device {
5636 return Err("Step owner-grouped combine root device changed".into());
5637 }
5638 root.stream().synchronize()?;
5639 }
5640 let source = &combine.output;
5641 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
5642 let engine = &self.ranks[index + 1];
5643 let _main = engine.gpu.enter_main()?;
5644 if engine.ctx().ordinal() != combine.peer_devices[index] {
5645 return Err(format!(
5646 "Step owner-grouped combine peer {} device changed",
5647 index + 1
5648 )
5649 .into());
5650 }
5651 let mut destination = destination_buffer.slice_mut(0..values);
5652 engine
5653 .stream()
5654 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
5655 }
5656 combine.broadcast_generation = Some(plan.generation);
5657 Ok(())
5658 }
5659
5660 pub fn collect_step_grouped_expert_parallel_broadcast(
5661 &self,
5662 plan: &PreparedStepGroupedExpertParallelGate,
5663 combine: &PreparedPeerWeightedRouteCombine,
5664 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
5665 if !plan.ready
5666 || combine.output_generation != Some(plan.generation)
5667 || combine.broadcast_generation != Some(plan.generation)
5668 || combine.peer_outputs.len() + 1 != self.ranks.len()
5669 {
5670 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
5671 }
5672 let values = combine
5673 .tokens
5674 .checked_mul(combine.width)
5675 .ok_or("Step owner-grouped combine collection size overflow")?;
5676 let mut outputs = Vec::with_capacity(self.ranks.len());
5677 {
5678 let root = &self.ranks[0];
5679 let _main = root.gpu.enter_main()?;
5680 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
5681 }
5682 for (index, output) in combine.peer_outputs.iter().enumerate() {
5683 let engine = &self.ranks[index + 1];
5684 let _main = engine.gpu.enter_main()?;
5685 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
5686 }
5687 Ok(outputs)
5688 }
5689
5690 pub fn finish_step_grouped_expert_parallel_layer(
5692 &self,
5693 plan: &PreparedStepGroupedExpertParallelGate,
5694 combine: &PreparedPeerWeightedRouteCombine,
5695 shared: &ResidentReplicatedDeviceRows,
5696 residual: &ResidentReplicatedDeviceRows,
5697 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
5698 validate_replicated_device_rows(&self.ranks, shared)?;
5699 validate_replicated_device_rows(&self.ranks, residual)?;
5700 if !plan.ready
5701 || plan.executed_generation != Some(plan.generation)
5702 || combine.output_generation != Some(plan.generation)
5703 || combine.broadcast_generation != Some(plan.generation)
5704 || combine.projection_generation != plan.generation
5705 || combine.peer_outputs.len() + 1 != self.ranks.len()
5706 || shared.tokens != combine.tokens
5707 || residual.tokens != combine.tokens
5708 || shared.width != combine.width
5709 || residual.width != combine.width
5710 {
5711 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
5712 }
5713 let values = combine
5714 .tokens
5715 .checked_mul(combine.width)
5716 .ok_or("Step full-layer output size overflow")?;
5717 let mut ranks = Vec::with_capacity(self.ranks.len());
5718 for rank in 0..self.ranks.len() {
5719 let engine = &self.ranks[rank];
5720 let _main = engine.gpu.enter_main()?;
5721 let routed = if rank == 0 {
5722 &combine.output
5723 } else {
5724 &combine.peer_outputs[rank - 1]
5725 };
5726 let mut ffn = engine.uninit(values)?;
5727 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
5728 let mut output = engine.uninit(values)?;
5729 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5730 ranks.push(output);
5731 }
5732 Ok(ResidentReplicatedDeviceRows {
5733 ranks,
5734 tokens: combine.tokens,
5735 width: combine.width,
5736 })
5737 }
5738
5739 pub fn run_step_grouped_expert_parallel_combine(
5740 &self,
5741 plan: &PreparedStepGroupedExpertParallelGate,
5742 combine: &mut PreparedPeerWeightedRouteCombine,
5743 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5744 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5745 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5746 }
5747
5748 pub fn upload_tensor_parallel(
5749 &self,
5750 gate: E4m3ExpertBank<'_>,
5751 up: E4m3ExpertBank<'_>,
5752 down: E4m3ExpertBank<'_>,
5753 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5754 gate.validate()?;
5755 up.validate()?;
5756 down.validate()?;
5757 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5758 return Err("TP gate/up/down expert counts differ".into());
5759 }
5760 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5761 return Err("TP gate/up dimensions differ".into());
5762 }
5763 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5764 return Err(format!(
5765 "TP down {}x{} does not invert gate/up {}x{}",
5766 down.out_features, down.in_features, gate.out_features, gate.in_features
5767 )
5768 .into());
5769 }
5770 let tp = self.ranks.len();
5771 validate_column_bank_shape(gate, tp)?;
5772 validate_column_bank_shape(up, tp)?;
5773 validate_row_bank_shape(down, tp)?;
5774
5775 let mut gate_ranks = Vec::with_capacity(tp);
5776 let mut up_ranks = Vec::with_capacity(tp);
5777 let mut down_ranks = Vec::with_capacity(tp);
5778 for (rank, engine) in self.ranks.iter().enumerate() {
5779 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5780 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5781 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5782 }
5783 Ok(ResidentTensorParallel {
5784 bank: ResidentTpExpertBank {
5785 gate: gate_ranks,
5786 up: up_ranks,
5787 down: down_ranks,
5788 expert_count: gate.expert_count,
5789 input_width: gate.in_features,
5790 expert_width: gate.out_features,
5791 },
5792 })
5793 }
5794
5795 pub fn run_tensor_parallel_routes(
5796 &self,
5797 experts: &ResidentTensorParallel,
5798 input: &[f32],
5799 tokens: usize,
5800 selected: &[usize],
5801 route_weights: &[f32],
5802 experts_per_token: usize,
5803 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5804 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5805 validate_activations(input, tokens, experts.bank.input_width)?;
5806 let pairs = tokens
5807 .checked_mul(experts_per_token)
5808 .ok_or("TP route count overflow")?;
5809 if selected.len() != pairs || route_weights.len() != pairs {
5810 return Err(format!(
5811 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5812 {experts_per_token} ({pairs})",
5813 selected.len(),
5814 route_weights.len(),
5815 )
5816 .into());
5817 }
5818 if !route_weights.iter().all(|weight| weight.is_finite()) {
5819 return Err("TP route weights contain a non-finite value".into());
5820 }
5821
5822 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5823 for token in 0..tokens {
5824 let input_row =
5825 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5826 for slot in 0..experts_per_token {
5827 let pair = token * experts_per_token + slot;
5828 let expert = selected[pair];
5829 if expert >= experts.bank.expert_count {
5830 return Err(format!(
5831 "TP selected expert {expert} outside 0..{}",
5832 experts.bank.expert_count
5833 )
5834 .into());
5835 }
5836 let down = if self.native_p2p {
5837 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5838 } else {
5839 let gate =
5840 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5841 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5842 let activated: Vec<f32> = gate
5843 .iter()
5844 .zip(&up)
5845 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5846 .collect();
5847 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5848 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5849 };
5850 let weight = route_weights[pair];
5851 for (sum, value) in output
5852 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5853 .iter_mut()
5854 .zip(down)
5855 {
5856 *sum += weight * value;
5857 }
5858 }
5859 }
5860 Ok(output)
5861 }
5862
5863 fn run_column_bank_expert(
5864 &self,
5865 ranks: &[ResidentE4m3ExpertBankRank],
5866 expert: usize,
5867 input: &[f32],
5868 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5869 let local_out = ranks
5870 .first()
5871 .ok_or("TP column bank has no ranks")?
5872 .out_features;
5873 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5874 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5875 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5876 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5877 }
5878 Ok(gathered)
5879 }
5880
5881 fn run_row_bank_expert(
5882 &self,
5883 ranks: &[ResidentE4m3ExpertBankRank],
5884 expert: usize,
5885 input: &[f32],
5886 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5887 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5888 if input.len() != local_in * ranks.len() {
5889 return Err(format!(
5890 "TP row input {} != {} ranks x {local_in}",
5891 input.len(),
5892 ranks.len()
5893 )
5894 .into());
5895 }
5896 let out_features = ranks[0].out_features;
5897 let mut reduced = vec![0.0f32; out_features];
5898 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5899 let blocks = bank
5900 .k_blocks
5901 .ok_or("TP row bank is not packed in native K-block order")?;
5902 if blocks * FP8_BLOCK != local_in {
5903 return Err(format!(
5904 "TP row bank has {blocks} blocks but local input width is {local_in}"
5905 )
5906 .into());
5907 }
5908 for block in 0..blocks {
5909 let global_start = rank * local_in + block * FP8_BLOCK;
5910 let partial = run_resident_bank_expert_block(
5911 engine,
5912 bank,
5913 expert,
5914 block,
5915 &input[global_start..global_start + FP8_BLOCK],
5916 )?;
5917 for (sum, value) in reduced.iter_mut().zip(partial) {
5918 *sum += value;
5919 }
5920 }
5921 }
5922 Ok(reduced)
5923 }
5924
5925 fn run_tensor_parallel_expert_native(
5926 &self,
5927 bank: &ResidentTpExpertBank,
5928 expert: usize,
5929 input: &[f32],
5930 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5931 if !self.native_p2p || self.ranks.len() < 2 {
5932 return Err("native TP expert execution requires at least two P2P ranks".into());
5933 }
5934 let local_out = bank
5935 .gate
5936 .first()
5937 .ok_or("native TP gate bank has no ranks")?
5938 .out_features;
5939 if local_out * self.ranks.len() != bank.expert_width {
5940 return Err(format!(
5941 "native TP gate shards {}x{local_out} != expert width {}",
5942 self.ranks.len(),
5943 bank.expert_width
5944 )
5945 .into());
5946 }
5947
5948 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5951 let root_input = {
5952 let root = &self.ranks[0];
5953 let _main = root.gpu.enter_main()?;
5954 root.htod(input)?
5955 };
5956 rank_inputs.push(root_input);
5957 for engine in &self.ranks[1..] {
5958 let peer_input = {
5959 let _main = engine.gpu.enter_main()?;
5960 let mut peer_input = engine.uninit(input.len())?;
5961 engine
5962 .stream()
5963 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5964 peer_input
5965 };
5966 rank_inputs.push(peer_input);
5967 }
5968
5969 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5970 let mut up_shards = Vec::with_capacity(self.ranks.len());
5971 #[allow(clippy::needless_range_loop)]
5972 for rank in 0..self.ranks.len() {
5974 gate_shards.push(run_resident_bank_expert_device(
5975 &self.ranks[rank],
5976 &bank.gate[rank],
5977 expert,
5978 &rank_inputs[rank],
5979 1,
5980 )?);
5981 up_shards.push(run_resident_bank_expert_device(
5982 &self.ranks[rank],
5983 &bank.up[rank],
5984 expert,
5985 &rank_inputs[rank],
5986 1,
5987 )?);
5988 }
5989
5990 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5994 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5995 let activated = gate
5996 .iter()
5997 .zip(&up)
5998 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5999 .collect::<Vec<_>>();
6000 debug_assert_eq!(activated.len(), bank.expert_width);
6001
6002 let root_activated = {
6003 let root = &self.ranks[0];
6004 let _main = root.gpu.enter_main()?;
6005 root.htod(&activated)?
6006 };
6007 let mut rank_activated = Vec::with_capacity(self.ranks.len());
6008 for (rank, engine) in self.ranks.iter().enumerate() {
6009 let start = rank * local_out;
6010 let source = root_activated.slice(start..start + local_out);
6011 let local = {
6012 let _main = engine.gpu.enter_main()?;
6013 let mut local = engine.uninit(local_out)?;
6014 engine.stream().memcpy_dtod(&source, &mut local)?;
6015 local
6016 };
6017 rank_activated.push(local);
6018 }
6019
6020 let out_features = bank
6021 .down
6022 .first()
6023 .ok_or("native TP down bank has no ranks")?
6024 .out_features;
6025 let mut reduced = {
6026 let root = &self.ranks[0];
6027 let _main = root.gpu.enter_main()?;
6028 root.htod(&vec![0.0f32; out_features])?
6029 };
6030 let mut remote_partial_keepalive = Vec::new();
6031 #[allow(clippy::needless_range_loop)]
6032 for rank in 0..self.ranks.len() {
6034 let down = &bank.down[rank];
6035 let blocks = down
6036 .k_blocks
6037 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
6038 if blocks * FP8_BLOCK != local_out {
6039 return Err(format!(
6040 "native TP rank {rank} has {blocks} blocks but local activation width is \
6041 {local_out}"
6042 )
6043 .into());
6044 }
6045 for block in 0..blocks {
6046 let start = block * FP8_BLOCK;
6047 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
6048 let partial = run_resident_bank_expert_block_device(
6049 &self.ranks[rank],
6050 down,
6051 expert,
6052 block,
6053 &input_block,
6054 )?;
6055 let root_partial = if rank == 0 {
6056 partial
6057 } else {
6058 let root = &self.ranks[0];
6059 let _main = root.gpu.enter_main()?;
6060 let mut peer_partial = root.uninit(out_features)?;
6061 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
6062 remote_partial_keepalive.push(partial);
6063 peer_partial
6064 };
6065 let next = {
6066 let root = &self.ranks[0];
6067 let _main = root.gpu.enter_main()?;
6068 let mut next = root.uninit(out_features)?;
6069 root.add(&reduced, &root_partial, &mut next, out_features)?;
6070 next
6071 };
6072 reduced = next;
6073 }
6074 }
6075 let output = {
6076 let root = &self.ranks[0];
6077 let _main = root.gpu.enter_main()?;
6078 root.dtoh(&reduced)?
6079 };
6080 drop(remote_partial_keepalive);
6081 Ok(output)
6082 }
6083
6084 pub fn gather_native_column_shards_device(
6086 &self,
6087 shards: &[CudaSlice<f32>],
6088 tokens: usize,
6089 local_out: usize,
6090 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6091 let shard_len = tokens
6092 .checked_mul(local_out)
6093 .ok_or("native TP gather shard size overflow")?;
6094 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
6095 return Err("native TP gather shard geometry mismatch".into());
6096 }
6097 for engine in &self.ranks[1..] {
6101 let _main = engine.gpu.enter_main()?;
6102 engine.stream().synchronize()?;
6103 }
6104 let root = &self.ranks[0];
6105 let _main = root.gpu.enter_main()?;
6106 let global_out = shards
6107 .len()
6108 .checked_mul(local_out)
6109 .ok_or("native TP gather output width overflow")?;
6110 let gathered_len = tokens
6111 .checked_mul(global_out)
6112 .ok_or("native TP gather output size overflow")?;
6113 let mut gathered = root.uninit(gathered_len)?;
6114 if self.bulk_p2p {
6115 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
6116 if shards.len() > 1 {
6117 let mut staging = root.uninit(shard_len)?;
6118 for (rank, shard) in shards.iter().enumerate().skip(1) {
6119 root.stream().memcpy_dtod(shard, &mut staging)?;
6120 root.place_rows_strided(
6121 &staging,
6122 &mut gathered,
6123 local_out,
6124 tokens,
6125 global_out,
6126 rank * local_out,
6127 )?;
6128 }
6129 }
6130 } else {
6131 for token in 0..tokens {
6132 for (rank, shard) in shards.iter().enumerate() {
6133 let source = shard.slice(token * local_out..(token + 1) * local_out);
6134 let start = token * global_out + rank * local_out;
6135 let mut destination = gathered.slice_mut(start..start + local_out);
6136 root.stream().memcpy_dtod(&source, &mut destination)?;
6137 }
6138 }
6139 }
6140 Ok(gathered)
6141 }
6142
6143 pub fn gather_native_column_shards(
6144 &self,
6145 shards: &[CudaSlice<f32>],
6146 tokens: usize,
6147 local_out: usize,
6148 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6149 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
6150 let root = &self.ranks[0];
6151 let _main = root.gpu.enter_main()?;
6152 root.dtoh(&gathered)
6153 }
6154
6155 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
6156 &self.decode_v2
6157 }
6158
6159 #[allow(clippy::manual_is_multiple_of)] pub(crate) fn decode_v2_ensure(
6169 &self,
6170 e: &Engine,
6171 q_m: &ResidentBf16ColumnParallel,
6172 k_m: &ResidentBf16ColumnParallel,
6173 v_m: &ResidentBf16ColumnParallel,
6174 o_m: &ResidentStepBf16RowParallel,
6175 heads: usize,
6176 ) -> Result<usize, Box<dyn std::error::Error>> {
6177 if self.ranks.len() > 1 && !self.native_p2p {
6178 return Err("step TP decode v2 requires native P2P ranks".into());
6179 }
6180 let ranks = self.ranks.len();
6181 let fused_door = step_tp_qkv_fused_enabled()?;
6185 let arm_ok = |weight: &ResidentBf16Weight| match weight {
6186 ResidentBf16Weight::F32(_) => true,
6187 ResidentBf16Weight::Bf16(_) => fused_door,
6188 };
6189 for matrix in [q_m, k_m, v_m] {
6190 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
6191 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
6192 return Err("step TP decode v2 QKV geometry mismatch".into());
6193 }
6194 for rank in &matrix.ranks {
6195 if !arm_ok(&rank.weight) {
6196 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
6197 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
6198 .into());
6199 }
6200 }
6201 }
6202 validate_step_bf16_row_residency(&self.ranks, o_m)?;
6203 for blocks in &o_m.ranks {
6204 for block in blocks {
6205 if !arm_ok(&block.weight) {
6206 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
6207 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
6208 .into());
6209 }
6210 }
6211 }
6212 if v_m.out_features != k_m.out_features
6213 || o_m.in_features != q_m.out_features
6214 || heads == 0
6215 || heads % ranks != 0
6216 {
6217 return Err("step TP decode v2 K/V/O geometry mismatch".into());
6218 }
6219 let local_q_dim = q_m.out_features / ranks;
6220 let local_kv_dim = k_m.out_features / ranks;
6221 let o_out = o_m.out_features;
6222 let o_block_cols = o_m.canonical_chunk_cols;
6223 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
6224 if blocks_per_rank == 0
6225 || o_m
6226 .ranks
6227 .iter()
6228 .any(|blocks| blocks.len() != blocks_per_rank)
6229 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
6230 {
6231 return Err("step TP decode v2 O canonical block grid mismatch".into());
6232 }
6233
6234 let mut guard = self
6235 .decode_v2
6236 .lock()
6237 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6238 if let Some(index) = guard.iter().position(|ws| {
6239 ws.local_q_dim == local_q_dim
6240 && ws.local_kv_dim == local_kv_dim
6241 && ws.heads == heads
6242 && ws.o_out == o_out
6243 && ws.o_block_cols == o_block_cols
6244 && ws.blocks_per_rank == blocks_per_rank
6245 && ws.e_device == e.ctx().ordinal()
6246 && ws.q.len() == ranks
6247 }) {
6248 return Ok(index);
6249 }
6250
6251 let mut q_raw = Vec::with_capacity(ranks);
6252 let mut k_raw = Vec::with_capacity(ranks);
6253 let mut v_raw = Vec::with_capacity(ranks);
6254 let mut q = Vec::with_capacity(ranks);
6255 let mut k = Vec::with_capacity(ranks);
6256 let mut pos = Vec::with_capacity(ranks);
6257 let mut gate = Vec::with_capacity(ranks);
6258 let mut attn_out = Vec::with_capacity(ranks);
6259 let mut gated = Vec::with_capacity(ranks);
6260 let mut fuse_ctr = Vec::with_capacity(ranks);
6261 let mut o_partials = Vec::with_capacity(ranks);
6262 let mut ev_rank = Vec::with_capacity(ranks);
6263 let direct_join = oproj_direct_on();
6264 for (rank, engine) in self.ranks.iter().enumerate() {
6265 let _main = engine.gpu.enter_main()?;
6266 q_raw.push(engine.uninit(local_q_dim)?);
6267 k_raw.push(engine.uninit(local_kv_dim)?);
6268 v_raw.push(engine.uninit(local_kv_dim)?);
6269 q.push(engine.uninit(local_q_dim)?);
6270 k.push(engine.uninit(local_kv_dim)?);
6271 pos.push(engine.htod_i32(&[0])?);
6272 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
6273 gate.push(engine.uninit(heads / ranks)?);
6274 attn_out.push(engine.uninit(local_q_dim)?);
6275 gated.push(engine.uninit(local_q_dim)?);
6276 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
6277 for _ in 0..blocks_per_rank {
6278 if direct_join && rank != 0 {
6281 let root = &self.ranks[0];
6282 let _root_main = root.gpu.enter_main()?;
6283 rank_partials.push(root.uninit(o_out)?);
6284 } else {
6285 rank_partials.push(engine.uninit(o_out)?);
6286 }
6287 }
6288 o_partials.push(rank_partials);
6289 ev_rank.push(engine.ctx().new_event(None)?);
6290 }
6291 use cudarc::driver::DevicePtr;
6292 let mut raw_o_partials = Vec::with_capacity(ranks);
6293 let mut raw_k = Vec::with_capacity(ranks);
6294 let mut raw_v_raw = Vec::with_capacity(ranks);
6295 for rank in 0..ranks {
6296 let engine = &self.ranks[rank];
6297 {
6298 let _main = engine.gpu.enter_main()?;
6299 let stream = engine.stream();
6300 let (k_ptr, _k_guard) = k[rank].device_ptr(&stream);
6301 let (v_ptr, _v_guard) = v_raw[rank].device_ptr(&stream);
6302 raw_k.push(k_ptr);
6303 raw_v_raw.push(v_ptr);
6304 }
6305 let partial_engine = if direct_join && rank != 0 {
6306 &self.ranks[0]
6307 } else {
6308 engine
6309 };
6310 let _main = partial_engine.gpu.enter_main()?;
6311 let stream = partial_engine.stream();
6312 let mut rank_raw = Vec::with_capacity(blocks_per_rank);
6313 for partial in &o_partials[rank] {
6314 let (ptr, _guard) = partial.device_ptr(&stream);
6315 rank_raw.push(ptr);
6316 }
6317 raw_o_partials.push(rank_raw);
6318 }
6319 let root = &self.ranks[0];
6320 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
6321 let _main = root.gpu.enter_main()?;
6322 (
6323 root.uninit(o_out)?,
6324 root.uninit(o_out)?,
6325 root.uninit(o_out)?,
6326 root.htod(&vec![0.0f32; o_out])?,
6327 root.uninit(ranks * local_kv_dim)?,
6328 root.uninit(ranks * local_kv_dim)?,
6329 root.ctx().new_event(None)?,
6330 root.ctx().new_event(None)?,
6331 )
6332 };
6333 let (raw_peer_partial, raw_k_shadow, raw_v_shadow) = {
6334 let _main = root.gpu.enter_main()?;
6335 let stream = root.stream();
6336 let (peer, _peer_guard) = peer_partial.device_ptr(&stream);
6337 let (k, _k_guard) = k_shadow.device_ptr(&stream);
6338 let (v, _v_guard) = v_shadow.device_ptr(&stream);
6339 (peer, k, v)
6340 };
6341 let (gate_e, ev_entry) = {
6342 let _main = e.gpu.enter_main()?;
6343 (e.uninit(heads)?, e.ctx().new_event(None)?)
6344 };
6345 let raw_attn_in = Vec::new();
6346 let raw_pos = Vec::new();
6347 guard.push(StepTpDecodeV2Ws {
6348 tcol_q: Vec::new(),
6349 tcol_k: Vec::new(),
6350 tcol_v: Vec::new(),
6351 tcol_g: Vec::new(),
6352 tcol_in: Vec::new(),
6353 tcol_cap: 0,
6354 w8_aq: Vec::new(),
6355 w8_ad: Vec::new(),
6356 w8_in: 0,
6357 w8o_aq: Vec::new(),
6358 w8o_ad: Vec::new(),
6359 w8o_in: 0,
6360 w8t_aq: Vec::new(),
6361 w8t_ad: Vec::new(),
6362 w8t_in: 0,
6363 w8t_oaq: Vec::new(),
6364 w8t_oad: Vec::new(),
6365 w8t_oin: 0,
6366 w8t_cap: 0,
6367 fa2_q: Vec::new(),
6368 fa2_gate: Vec::new(),
6369 fa2_gated: Vec::new(),
6370 fa2_cap: 0,
6371 rope_k_t: Vec::new(),
6372 rope_ctr_t: Vec::new(),
6373 rope_pos_t: Vec::new(),
6374 rows_tabs: Vec::new(),
6375 rows_tab_t: Vec::new(),
6376 rows_tab_shadow: Vec::new(),
6377 tcol_gated: Vec::new(),
6378 tcol_opart: Vec::new(),
6379 tcol_opeer: None,
6380 tcol_omix: None,
6381 tcol_ocap: 0,
6382 q_raw,
6383 k_raw,
6384 v_raw,
6385 q,
6386 k,
6387 pos,
6388 fuse_ctr,
6389 gate,
6390 attn_out,
6391 gated,
6392 o_partials,
6393 raw_o_partials,
6394 raw_k,
6395 raw_v_raw,
6396 ev_rank,
6397 peer_partial,
6398 reduce_a,
6399 reduce_b,
6400 zeros,
6401 k_shadow,
6402 v_shadow,
6403 ev_refresh,
6404 ev_oproj,
6405 gate_e,
6406 attn_in: Vec::new(),
6407 h_stage: None,
6408 pos_stage: None,
6409 raw_h_stage: 0,
6410 raw_pos_stage: 0,
6411 raw_attn_in,
6412 raw_pos,
6413 raw_o_partial1: 0,
6414 raw_peer_partial,
6415 raw_k1: 0,
6416 raw_v1: 0,
6417 raw_k_shadow,
6418 raw_v_shadow,
6419 raw_mixed_stage_e: 0,
6420 raw_reduce_a: 0,
6421 raw_shadow_stage_e: (0, 0),
6422 ev_entry,
6423 e_device: e.ctx().ordinal(),
6424 local_q_dim,
6425 local_kv_dim,
6426 heads,
6427 o_out,
6428 o_block_cols,
6429 blocks_per_rank,
6430 });
6431 eprintln!(
6432 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
6433 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
6434 residency=persistent ordering=evented performance_claim=false"
6435 );
6436 Ok(guard.len() - 1)
6437 }
6438
6439 #[allow(clippy::too_many_arguments)]
6447 #[allow(clippy::too_many_arguments)]
6452 pub fn decode_v2_input_qkv_tcol(
6453 &self,
6454 ws_index: usize,
6455 e: &Engine,
6456 h_t: &CudaSlice<f32>,
6457 t: usize,
6458 q_m: &ResidentBf16ColumnParallel,
6459 k_m: &ResidentBf16ColumnParallel,
6460 v_m: &ResidentBf16ColumnParallel,
6461 gate_shards: Option<StepTpGateShards<'_>>,
6462 ) -> Result<(), Box<dyn std::error::Error>> {
6463 let ranks = self.ranks.len();
6464 let mut guard = self
6465 .decode_v2
6466 .lock()
6467 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6468 let ws = guard
6469 .get_mut(ws_index)
6470 .ok_or("step TP decode v2 workspace index out of range")?;
6471 let in_f = q_m.in_features;
6472 if h_t.len() < t * in_f || t == 0 || t > 32 {
6473 return Err("decode_v2_input_qkv_tcol geometry".into());
6474 }
6475 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6477 ws.tcol_q.clear();
6478 ws.tcol_k.clear();
6479 ws.tcol_v.clear();
6480 ws.tcol_g.clear();
6481 ws.tcol_in.clear();
6482 for engine in &self.ranks {
6483 let _m = engine.gpu.enter_main()?;
6484 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
6485 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
6486 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
6487 ws.tcol_g
6488 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
6489 ws.tcol_in.push(engine.uninit(32 * in_f)?);
6490 }
6491 ws.tcol_cap = 32;
6492 }
6493 use cudarc::driver::DevicePtr;
6495 let raw_src = {
6496 let _main = e.gpu.enter_main()?;
6497 let stream = e.stream();
6498 let (p, _g) = h_t.device_ptr(&stream);
6499 ws.ev_entry.record(&stream)?;
6500 p
6501 };
6502 for rank in 0..ranks {
6503 let engine = &self.ranks[rank];
6504 let _main = engine.gpu.enter_main()?;
6505 engine.stream().wait(&ws.ev_entry)?;
6506 let raw_dst = {
6507 let stream = engine.stream();
6508 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
6509 p
6510 };
6511 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
6512 let out_g = match &gate_shards {
6513 Some(_) => ws.heads / ranks,
6514 None => 0,
6515 };
6516 match (
6517 &q_m.ranks[rank].weight,
6518 &k_m.ranks[rank].weight,
6519 &v_m.ranks[rank].weight,
6520 ) {
6521 (
6522 ResidentBf16Weight::Bf16(wq),
6523 ResidentBf16Weight::Bf16(wk),
6524 ResidentBf16Weight::Bf16(wv),
6525 ) => {
6526 let wg = match &gate_shards {
6527 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
6528 Some(StepTpGateShards::F32(_)) => {
6529 return Err(
6530 "tcol verify: gate shard class does not match bf16 QKV".into()
6531 );
6532 }
6533 None => wq,
6534 };
6535 let StepTpDecodeV2Ws {
6536 tcol_q,
6537 tcol_k,
6538 tcol_v,
6539 tcol_g,
6540 tcol_in,
6541 local_q_dim,
6542 local_kv_dim,
6543 w8t_aq,
6544 w8t_ad,
6545 w8t_in,
6546 w8t_cap,
6547 ..
6548 } = &mut *ws;
6549 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6552 let refk = *REFK
6553 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
6554 if refk {
6555 let lq = *local_q_dim;
6556 let lkv = *local_kv_dim;
6557 let mut hrow = engine.uninit(in_f)?;
6558 let mut qr = engine.uninit(lq)?;
6559 let mut kr = engine.uninit(lkv)?;
6560 let mut vr = engine.uninit(lkv)?;
6561 let mut gr = engine.uninit(out_g.max(1))?;
6562 for c in 0..t {
6563 {
6564 let mut dst = hrow.slice_mut(0..in_f);
6565 engine.stream().memcpy_dtod(
6566 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
6567 &mut dst,
6568 )?;
6569 }
6570 engine.matvec_bf16_qkvg_into(
6571 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
6572 lq, lkv, out_g,
6573 )?;
6574 let stream = engine.stream();
6575 {
6576 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
6577 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
6578 }
6579 {
6580 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
6581 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
6582 }
6583 {
6584 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
6585 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
6586 }
6587 if out_g > 0 {
6588 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
6589 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
6590 }
6591 }
6592 } else if crate::step_tp_w8_on()
6593 && q_m.ranks[rank].q8.is_some()
6594 && k_m.ranks[rank].q8.is_some()
6595 && v_m.ranks[rank].q8.is_some()
6596 && in_f.is_multiple_of(32)
6597 {
6598 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
6604 w8t_aq.clear();
6605 w8t_ad.clear();
6606 for e_rank in &self.ranks {
6607 let _m = e_rank.gpu.enter_main()?;
6608 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6609 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6610 }
6611 *w8t_in = in_f;
6612 *w8t_cap = 32;
6613 }
6614 engine.quantize_q8_1_into(
6615 &tcol_in[rank],
6616 t,
6617 in_f,
6618 &mut w8t_aq[rank],
6619 &mut w8t_ad[rank],
6620 )?;
6621 engine.qmatvec_q8_0_qkv_rp_t_into(
6622 q_m.ranks[rank].q8.as_ref().unwrap(),
6623 k_m.ranks[rank].q8.as_ref().unwrap(),
6624 v_m.ranks[rank].q8.as_ref().unwrap(),
6625 &w8t_aq[rank],
6626 &w8t_ad[rank],
6627 &mut tcol_q[rank],
6628 &mut tcol_k[rank],
6629 &mut tcol_v[rank],
6630 in_f,
6631 *local_q_dim,
6632 *local_kv_dim,
6633 t,
6634 )?;
6635 if out_g > 0 {
6636 engine.matvec_bf16_rows_into(
6637 wg,
6638 &tcol_in[rank],
6639 &mut tcol_g[rank],
6640 in_f,
6641 out_g,
6642 t,
6643 )?;
6644 }
6645 } else {
6646 engine.matvec_bf16_qkvg_tcol_into(
6647 wq,
6648 wk,
6649 wv,
6650 wg,
6651 &tcol_in[rank],
6652 &mut tcol_q[rank],
6653 &mut tcol_k[rank],
6654 &mut tcol_v[rank],
6655 &mut tcol_g[rank],
6656 in_f,
6657 *local_q_dim,
6658 *local_kv_dim,
6659 out_g,
6660 t,
6661 )?;
6662 }
6663 }
6664 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
6665 }
6666 }
6667 Ok(())
6668 }
6669
6670 pub(crate) fn decode_v2_oproj_tcol_eligible(
6674 &self,
6675 ws: &StepTpDecodeV2Ws,
6676 o_m: &ResidentStepBf16RowParallel,
6677 ) -> bool {
6678 self.ranks.len() == 2
6679 && ws.blocks_per_rank == 4
6680 && step_tp_qkv_fused_enabled().unwrap_or(false)
6681 && no_local_shadow_on()
6682 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
6683 && o_m
6684 .ranks
6685 .iter()
6686 .flatten()
6687 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
6688 }
6689
6690 pub(crate) fn decode_v2_stash_fa2(
6695 &self,
6696 ws: &mut StepTpDecodeV2Ws,
6697 e: &Engine,
6698 col: usize,
6699 ) -> Result<(), Box<dyn std::error::Error>> {
6700 let ranks = self.ranks.len();
6701 if col >= 32 {
6702 return Err("decode_v2_stash_fa2 column out of range".into());
6703 }
6704 let lq = ws.local_q_dim;
6705 let lg = (ws.heads / ranks).max(1);
6706 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6707 ws.fa2_q.clear();
6708 ws.fa2_gate.clear();
6709 ws.fa2_gated.clear();
6710 ws.rope_k_t.clear();
6711 ws.rope_ctr_t.clear();
6712 ws.rope_pos_t.clear();
6713 ws.rows_tab_t.clear();
6714 for engine in &self.ranks {
6715 let _m = engine.gpu.enter_main()?;
6716 ws.fa2_q.push(engine.uninit(32 * lq)?);
6717 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6718 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6719 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
6720 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6721 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6722 ws.rows_tab_t
6723 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6724 }
6725 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6726 ws.fa2_cap = 32;
6727 }
6728 for rank in 0..ranks {
6729 let engine = &self.ranks[rank];
6730 let _main = engine.gpu.enter_main()?;
6731 {
6732 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
6733 engine
6734 .stream()
6735 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
6736 }
6737 {
6738 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
6739 engine
6740 .stream()
6741 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
6742 }
6743 ws.ev_rank[rank].record(&engine.stream())?;
6744 }
6745 {
6746 let _main = e.gpu.enter_main()?;
6747 for ev in ws.ev_rank.iter() {
6748 e.stream().wait(ev)?;
6749 }
6750 }
6751 Ok(())
6752 }
6753
6754 #[allow(clippy::too_many_arguments)]
6761 #[allow(dead_code)] pub(crate) fn decode_v2_spec_fa2_join(
6763 &self,
6764 ws_index: usize,
6765 e: &Engine,
6766 o_m: &ResidentStepBf16RowParallel,
6767 kv: &ResidentTpKvCache,
6768 head_dim: usize,
6769 window: usize,
6770 bucket_max: usize,
6771 scale: f32,
6772 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6773 let ranks = self.ranks.len();
6774 static ONCE: std::sync::Once = std::sync::Once::new();
6776 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
6777 {
6778 let mut guard = self
6779 .decode_v2
6780 .lock()
6781 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6782 let ws = guard
6783 .get_mut(ws_index)
6784 .ok_or("step TP decode v2 workspace index out of range")?;
6785 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6786 return Err("spec fa2 join without stashed columns".into());
6787 }
6788 let lq = ws.local_q_dim;
6789 let local_heads = (ws.heads / ranks).max(1);
6790 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6791 let capacity = kv.physical_capacity();
6792 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6793 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6795 ws.tcol_gated.clear();
6796 ws.tcol_opart.clear();
6797 for engine in &self.ranks {
6798 let _m = engine.gpu.enter_main()?;
6799 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6800 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6801 }
6802 let root = &self.ranks[0];
6803 let _m = root.gpu.enter_main()?;
6804 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6805 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6806 ws.tcol_ocap = 32;
6807 }
6808 for rank in 0..ranks {
6809 let engine = &self.ranks[rank];
6810 let _main = engine.gpu.enter_main()?;
6811 let rank_cache = kv
6812 .rank(rank)
6813 .ok_or("spec fa2 join lost its KV cache rank")?;
6814 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6815 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6816 {
6817 let StepTpDecodeV2Ws {
6818 fa2_q,
6819 fa2_gate,
6820 fa2_gated,
6821 ..
6822 } = &mut *ws;
6823 engine.fa_decode_dcw2(
6824 &fa2_q[rank],
6825 &k_ring,
6826 &v_ring,
6827 &mut fa2_gated[rank],
6828 head_dim,
6829 local_heads,
6830 local_kv_heads,
6831 rank_cache.len_d(),
6832 rank_cache.base_d(),
6833 window,
6834 bucket_max,
6835 scale,
6836 k_tok_bytes,
6837 v_tok_bytes,
6838 &fa2_gate[rank],
6839 )?;
6840 }
6841 let StepTpDecodeV2Ws {
6844 fa2_gated,
6845 tcol_gated,
6846 ..
6847 } = &mut *ws;
6848 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6849 engine
6850 .stream()
6851 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6852 }
6853 }
6854 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6855 }
6856
6857 #[allow(clippy::too_many_arguments)]
6867 pub(crate) fn decode_v2_rope_fa_rows(
6868 &self,
6869 ws_index: usize,
6870 e: &Engine,
6871 o_m: &ResidentStepBf16RowParallel,
6872 session_parts: &[Vec<[u64; 4]>],
6873 tab_keys: &[u64],
6874 positions: &[i32],
6875 stage_pos: bool,
6876 same_session: bool,
6877 q_norms: &[CudaSlice<f32>],
6878 k_norms: &[CudaSlice<f32>],
6879 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6880 t: usize,
6881 head_dim: usize,
6882 n_rot: usize,
6883 window: usize,
6884 max_ns: usize,
6885 scale: f32,
6886 k_tok_bytes: usize,
6887 v_tok_bytes: usize,
6888 eps: f32,
6889 rope_base: f32,
6890 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6891 use cudarc::driver::DevicePtr;
6892 let ranks = self.ranks.len();
6893 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6894 return Err("rope fa rows geometry".into());
6895 }
6896 {
6897 let mut guard = self
6898 .decode_v2
6899 .lock()
6900 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6901 let ws = guard
6902 .get_mut(ws_index)
6903 .ok_or("step TP decode v2 workspace index out of range")?;
6904 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6905 return Err("rope fa rows without tcol slabs".into());
6906 }
6907 let lq = ws.local_q_dim;
6908 let lkv = ws.local_kv_dim;
6909 let lg = (ws.heads / ranks).max(1);
6910 let local_heads = (ws.heads / ranks).max(1);
6911 let local_kv_heads = (lkv / head_dim).max(1);
6912 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6914 ws.fa2_q.clear();
6915 ws.fa2_gate.clear();
6916 ws.fa2_gated.clear();
6917 ws.rope_k_t.clear();
6918 ws.rope_ctr_t.clear();
6919 ws.rope_pos_t.clear();
6920 ws.rows_tab_t.clear();
6921 for engine in &self.ranks {
6922 let _m = engine.gpu.enter_main()?;
6923 ws.fa2_q.push(engine.uninit(32 * lq)?);
6924 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6925 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6926 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6927 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6928 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6929 ws.rows_tab_t
6930 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6931 }
6932 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6933 ws.fa2_cap = 32;
6934 }
6935 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6936 ws.tcol_gated.clear();
6937 ws.tcol_opart.clear();
6938 for engine in &self.ranks {
6939 let _m = engine.gpu.enter_main()?;
6940 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6941 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6942 }
6943 let root = &self.ranks[0];
6944 let _m = root.gpu.enter_main()?;
6945 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6946 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6947 ws.tcol_ocap = 32;
6948 }
6949 for rank in 0..ranks {
6950 let engine = &self.ranks[rank];
6951 let _main = engine.gpu.enter_main()?;
6952 if stage_pos {
6953 let host: Vec<i32> = positions[..t].to_vec();
6954 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6955 engine.stream().memcpy_htod(&host, &mut view)?;
6956 }
6957 let ctr_base = {
6976 let s = engine.stream();
6977 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6978 p
6979 };
6980 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6981 if rows_tab_stale_scan() {
6987 if ws.rows_tab_shadow.len() != ranks {
6988 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6989 }
6990 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6991 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank])
6992 && prev != &host
6993 {
6994 let words = ["k", "v", "len", "base", "ctr", "back"];
6995 let moved: Vec<String> = (0..host.len())
6996 .filter(|&i| prev.get(i) != Some(&host[i]))
6997 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6998 .collect();
6999 let stale =
7000 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
7001 eprintln!(
7002 "[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",
7003 tab_keys[rank],
7004 moved.join(",")
7005 );
7006 }
7007 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
7008 }
7009 let legacy_memo = !rows_tab_restage_on();
7010 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
7011 let tab = engine.stream().clone_htod(&host)?;
7012 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
7013 }
7014 if !legacy_memo {
7015 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
7016 engine.stream().memcpy_htod(&host, &mut view)?;
7017 }
7018 let StepTpDecodeV2Ws {
7019 tcol_q,
7020 tcol_k,
7021 tcol_v,
7022 tcol_g,
7023 fa2_q,
7024 fa2_gated,
7025 rope_k_t,
7026 rope_pos_t,
7027 rows_tabs,
7028 rows_tab_t,
7029 ..
7030 } = &mut *ws;
7031 let tab = if legacy_memo {
7032 rows_tabs[rank]
7033 .get(&tab_keys[rank])
7034 .ok_or("rows tab memo lost its entry")?
7035 } else {
7036 &rows_tab_t[rank]
7037 };
7038 engine.qk_norm_rope_append_inc_dcw_rows(
7039 &tcol_q[rank],
7040 &tcol_k[rank],
7041 &tcol_v[rank],
7042 &q_norms[rank],
7043 &k_norms[rank],
7044 &mut fa2_q[rank],
7045 &mut rope_k_t[rank],
7046 tab,
7047 &rope_pos_t[rank],
7048 same_session,
7049 t,
7050 lkv,
7051 lkv,
7052 k_tok_bytes,
7053 v_tok_bytes,
7054 head_dim,
7055 n_rot,
7056 local_heads,
7057 local_kv_heads,
7058 eps,
7059 rope_base,
7060 1.0,
7061 rope_freqs[rank],
7062 )?;
7063 engine.fa_decode_dcw_rows(
7064 &fa2_q[rank],
7065 tab,
7066 &mut fa2_gated[rank],
7067 t,
7068 head_dim,
7069 local_heads,
7070 local_kv_heads,
7071 window,
7072 max_ns,
7073 scale,
7074 k_tok_bytes,
7075 v_tok_bytes,
7076 &tcol_g[rank],
7077 )?;
7078 let StepTpDecodeV2Ws {
7079 fa2_gated,
7080 tcol_gated,
7081 ..
7082 } = &mut *ws;
7083 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
7084 engine
7085 .stream()
7086 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
7087 }
7088 }
7089 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
7090 }
7091
7092 #[allow(clippy::too_many_arguments)]
7099 pub(crate) fn decode_v2_fa_rows_join(
7100 &self,
7101 ws_index: usize,
7102 e: &Engine,
7103 o_m: &ResidentStepBf16RowParallel,
7104 tabs: &[&crate::CudaSlice<u64>],
7105 t: usize,
7106 head_dim: usize,
7107 window: usize,
7108 max_ns: usize,
7109 scale: f32,
7110 k_tok_bytes: usize,
7111 v_tok_bytes: usize,
7112 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
7113 let ranks = self.ranks.len();
7114 if tabs.len() != ranks {
7115 return Err("fa rows join needs one table per rank".into());
7116 }
7117 {
7118 let mut guard = self
7119 .decode_v2
7120 .lock()
7121 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
7122 let ws = guard
7123 .get_mut(ws_index)
7124 .ok_or("step TP decode v2 workspace index out of range")?;
7125 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
7126 return Err("fa rows join without stashed rows".into());
7127 }
7128 let lq = ws.local_q_dim;
7129 let local_heads = (ws.heads / ranks).max(1);
7130 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
7131 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
7132 ws.tcol_gated.clear();
7133 ws.tcol_opart.clear();
7134 for engine in &self.ranks {
7135 let _m = engine.gpu.enter_main()?;
7136 ws.tcol_gated.push(engine.uninit(32 * lq)?);
7137 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
7138 }
7139 let root = &self.ranks[0];
7140 let _m = root.gpu.enter_main()?;
7141 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
7142 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
7143 ws.tcol_ocap = 32;
7144 }
7145 for rank in 0..ranks {
7146 let engine = &self.ranks[rank];
7147 let _main = engine.gpu.enter_main()?;
7148 {
7149 let StepTpDecodeV2Ws {
7150 fa2_q,
7151 fa2_gate,
7152 fa2_gated,
7153 ..
7154 } = &mut *ws;
7155 engine.fa_decode_dcw_rows(
7156 &fa2_q[rank],
7157 tabs[rank],
7158 &mut fa2_gated[rank],
7159 t,
7160 head_dim,
7161 local_heads,
7162 local_kv_heads,
7163 window,
7164 max_ns,
7165 scale,
7166 k_tok_bytes,
7167 v_tok_bytes,
7168 &fa2_gate[rank],
7169 )?;
7170 }
7171 let StepTpDecodeV2Ws {
7172 fa2_gated,
7173 tcol_gated,
7174 ..
7175 } = &mut *ws;
7176 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
7177 engine
7178 .stream()
7179 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
7180 }
7181 }
7182 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
7183 }
7184
7185 pub(crate) fn decode_v2_stash_gated(
7190 &self,
7191 ws: &mut StepTpDecodeV2Ws,
7192 e: &Engine,
7193 col: usize,
7194 ) -> Result<(), Box<dyn std::error::Error>> {
7195 let ranks = self.ranks.len();
7196 if col >= 32 {
7200 return Err("decode_v2_stash_gated column out of range".into());
7201 }
7202 let lq = ws.local_q_dim;
7203 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
7204 ws.tcol_gated.clear();
7205 ws.tcol_opart.clear();
7206 for engine in &self.ranks {
7207 let _m = engine.gpu.enter_main()?;
7208 ws.tcol_gated.push(engine.uninit(32 * lq)?);
7209 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
7210 }
7211 let root = &self.ranks[0];
7212 let _m = root.gpu.enter_main()?;
7213 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
7214 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
7215 ws.tcol_ocap = 32;
7216 }
7217 for rank in 0..ranks {
7218 let engine = &self.ranks[rank];
7219 let _main = engine.gpu.enter_main()?;
7220 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
7221 engine
7222 .stream()
7223 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
7224 ws.ev_rank[rank].record(&engine.stream())?;
7228 }
7229 {
7230 let _main = e.gpu.enter_main()?;
7231 for ev in ws.ev_rank.iter() {
7232 e.stream().wait(ev)?;
7233 }
7234 }
7235 Ok(())
7236 }
7237
7238 pub(crate) fn decode_v2_oproj_tcol(
7244 &self,
7245 ws_index: usize,
7246 e: &Engine,
7247 o_m: &ResidentStepBf16RowParallel,
7248 t: usize,
7249 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7250 let ranks = self.ranks.len();
7251 let mut guard = self
7252 .decode_v2
7253 .lock()
7254 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
7255 let ws = guard
7256 .get_mut(ws_index)
7257 .ok_or("step TP decode v2 workspace index out of range")?;
7258 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
7259 return Err("decode_v2_oproj_tcol geometry".into());
7260 }
7261 for rank in 0..ranks {
7262 let engine = &self.ranks[rank];
7263 let _main = engine.gpu.enter_main()?;
7264 let mut weights = Vec::with_capacity(4);
7265 for block in 0..4 {
7266 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7267 return Err("tcol o_proj requires bf16-resident O blocks".into());
7268 };
7269 weights.push(weight);
7270 }
7271 {
7272 let StepTpDecodeV2Ws {
7273 tcol_gated,
7274 tcol_opart,
7275 local_q_dim,
7276 o_block_cols,
7277 o_out,
7278 w8t_oaq,
7279 w8t_oad,
7280 w8t_oin,
7281 w8t_cap,
7282 ..
7283 } = &mut *ws;
7284 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7287 let refk = *REFK
7288 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
7289 if refk {
7290 let lq = *local_q_dim;
7291 let mut xr = engine.uninit(lq)?;
7292 let mut yr = engine.uninit(*o_out)?;
7293 for c in 0..t {
7294 {
7295 let mut dst = xr.slice_mut(0..lq);
7296 engine.stream().memcpy_dtod(
7297 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
7298 &mut dst,
7299 )?;
7300 }
7301 engine.matvec_bf16_b4_into(
7302 [weights[0], weights[1], weights[2], weights[3]],
7303 &xr,
7304 &mut yr,
7305 *o_block_cols,
7306 *o_out,
7307 )?;
7308 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
7309 engine
7310 .stream()
7311 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
7312 }
7313 } else if crate::step_tp_w8_on()
7314 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
7315 && (4 * *o_block_cols) % 32 == 0
7316 {
7317 let in_f = 4 * *o_block_cols;
7321 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
7322 w8t_oaq.clear();
7323 w8t_oad.clear();
7324 for e_rank in &self.ranks {
7325 let _m = e_rank.gpu.enter_main()?;
7326 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
7327 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
7328 }
7329 *w8t_oin = in_f;
7330 *w8t_cap = (*w8t_cap).max(32);
7331 }
7332 engine.quantize_q8_1_into(
7333 &tcol_gated[rank],
7334 t,
7335 in_f,
7336 &mut w8t_oaq[rank],
7337 &mut w8t_oad[rank],
7338 )?;
7339 engine.qmatvec_q8_0_b4_rp_t_into(
7340 [
7341 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7342 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7343 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7344 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7345 ],
7346 &w8t_oaq[rank],
7347 &w8t_oad[rank],
7348 &mut tcol_opart[rank],
7349 *o_block_cols,
7350 *o_out,
7351 t,
7352 )?;
7353 } else {
7354 engine.matvec_bf16_b4_tcol_into(
7355 [weights[0], weights[1], weights[2], weights[3]],
7356 &tcol_gated[rank],
7357 &mut tcol_opart[rank],
7358 *o_block_cols,
7359 *o_out,
7360 t,
7361 )?;
7362 }
7363 }
7364 if rank != 0 {
7365 ws.ev_rank[rank].record(&engine.stream())?;
7366 }
7367 }
7368 let root = &self.ranks[0];
7369 {
7370 let _main = root.gpu.enter_main()?;
7371 for ev in ws.ev_rank.iter().skip(1) {
7372 root.stream().wait(ev)?;
7373 }
7374 {
7375 let StepTpDecodeV2Ws {
7376 tcol_opart,
7377 tcol_opeer,
7378 tcol_omix,
7379 o_out,
7380 ..
7381 } = &mut *ws;
7382 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
7383 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
7384 {
7385 let mut dst = opeer.slice_mut(0..t * *o_out);
7386 root.stream()
7387 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
7388 }
7389 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
7392 }
7393 ws.ev_oproj.record(&root.stream())?;
7394 }
7395 let _main = e.gpu.enter_main()?;
7396 e.stream().wait(&ws.ev_oproj)?;
7397 let mut out = e.uninit(t * ws.o_out)?;
7398 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
7399 e.stream().memcpy_dtod(
7400 &omix.slice(0..t * ws.o_out),
7401 &mut out.slice_mut(0..t * ws.o_out),
7402 )?;
7403 Ok(out)
7404 }
7405
7406 #[allow(clippy::too_many_arguments)] pub(crate) fn decode_v2_input_qkv(
7408 &self,
7409 ws: &mut StepTpDecodeV2Ws,
7410 e: &Engine,
7411 h: &CudaSlice<f32>,
7412 pos_d: &CudaSlice<i32>,
7413 gate_raw: Option<&CudaSlice<f32>>,
7414 gate_shards: Option<StepTpGateShards<'_>>,
7415 decode_input: &mut ResidentReplicatedDeviceRows,
7416 q_m: &ResidentBf16ColumnParallel,
7417 k_m: &ResidentBf16ColumnParallel,
7418 v_m: &ResidentBf16ColumnParallel,
7419 q_norm: &[CudaSlice<f32>],
7420 k_norm: &[CudaSlice<f32>],
7421 head_dim: usize,
7422 n_rot: usize,
7423 rope_base: f32,
7424 rope_freqs: &[Option<&CudaSlice<f32>>],
7425 rms_eps: f32,
7426 has_gate: bool,
7427 defer_norm_rope: bool,
7428 tcol_col: Option<usize>,
7429 ) -> Result<(), Box<dyn std::error::Error>> {
7430 let ranks = self.ranks.len();
7431 validate_replicated_device_rows(&self.ranks, decode_input)?;
7432 let gate_sources = usize::from(gate_raw.is_some()) + usize::from(gate_shards.is_some());
7433 if decode_input.tokens != 1
7434 || decode_input.width != q_m.in_features
7435 || pos_d.len() != 1
7436 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
7437 || (has_gate && gate_sources != 1)
7438 || (!has_gate && gate_sources != 0)
7439 || gate_shards.as_ref().is_some_and(|shards| match shards {
7440 StepTpGateShards::F32(shards) => shards.len() != ranks,
7441 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
7442 })
7443 || q_norm.len() != ranks
7444 || k_norm.len() != ranks
7445 || rope_freqs.len() != ranks
7446 || e.ctx().ordinal() != ws.e_device
7447 {
7448 return Err("step TP decode v2 input geometry mismatch".into());
7449 }
7450
7451 let qkv_fused = step_tp_qkv_fused_enabled()?;
7452 if gate_shards.is_some() && !qkv_fused {
7453 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
7454 }
7455 let values = decode_input.width;
7456 if h.len() != values {
7457 return Err(format!(
7458 "step TP decode v2 hidden width {} != replicated width {values}",
7459 h.len()
7460 )
7461 .into());
7462 }
7463
7464 if qkv_fused {
7465 if ws.h_stage.is_none() {
7469 use cudarc::driver::DevicePtr;
7470 let _main = e.gpu.enter_main()?;
7471 let h_stage = e.uninit(values)?;
7472 let pos_stage = e.htod_i32(&[0])?;
7473 {
7474 let stream = e.stream();
7475 let (hp, _g0) = h_stage.device_ptr(&stream);
7476 let (pp, _g1) = pos_stage.device_ptr(&stream);
7477 ws.raw_h_stage = hp;
7478 ws.raw_pos_stage = pp;
7479 }
7480 ws.h_stage = Some(h_stage);
7481 ws.pos_stage = Some(pos_stage);
7482 for rank in 0..ranks {
7483 use cudarc::driver::DevicePtr;
7484 let engine = &self.ranks[rank];
7485 let _rmain = engine.gpu.enter_main()?;
7486 let attn_in = engine.uninit(values)?;
7487 let (dp, pp) = {
7488 let stream = engine.stream();
7489 let (dp, _g2) = attn_in.device_ptr(&stream);
7490 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
7491 (dp, pp)
7492 };
7493 ws.raw_attn_in.push(dp);
7494 ws.raw_pos.push(pp);
7495 ws.attn_in.push(attn_in);
7496 }
7497 {
7498 use cudarc::driver::DevicePtr;
7499 let root = &self.ranks[0];
7500 let _rmain = root.gpu.enter_main()?;
7501 let stream = root.stream();
7502 let (a, _g) = ws.peer_partial.device_ptr(&stream);
7503 let (b, _g) = ws.k_shadow.device_ptr(&stream);
7504 let (c, _g) = ws.v_shadow.device_ptr(&stream);
7505 ws.raw_peer_partial = a;
7506 ws.raw_k_shadow = b;
7507 ws.raw_v_shadow = c;
7508 }
7509 {
7510 use cudarc::driver::DevicePtr;
7511 let rank1 = &self.ranks[1];
7512 let _rmain = rank1.gpu.enter_main()?;
7513 let stream = rank1.stream();
7514 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
7515 let (b, _g) = ws.k[1].device_ptr(&stream);
7516 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
7517 ws.raw_o_partial1 = a;
7518 ws.raw_k1 = b;
7519 ws.raw_v1 = c;
7520 }
7521 }
7522 {
7523 let _main = e.gpu.enter_main()?;
7524 {
7525 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
7528 let mut dst = h_stage.slice_mut(0..values);
7529 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
7530 }
7531 {
7532 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
7533 let mut dst = pos_stage.slice_mut(0..1);
7534 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
7535 }
7536 ws.ev_entry.record(&e.stream())?;
7537 }
7538 for rank in 0..ranks {
7539 let engine = &self.ranks[rank];
7540 let _main = engine.gpu.enter_main()?;
7541 engine.stream().wait(&ws.ev_entry)?;
7542 }
7543 } else {
7544 {
7546 let _main = e.gpu.enter_main()?;
7547 if let Some(gate_raw) = gate_raw {
7548 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
7549 e.stream()
7550 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
7551 }
7552 ws.ev_entry.record(&e.stream())?;
7553 }
7554 {
7555 let root = &self.ranks[0];
7556 let _main = root.gpu.enter_main()?;
7557 root.stream().wait(&ws.ev_entry)?;
7558 let mut destination = decode_input.ranks[0].slice_mut(0..values);
7559 root.stream()
7560 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
7561 ws.ev_refresh.record(&root.stream())?;
7562 }
7563 for rank in 1..ranks {
7564 let engine = &self.ranks[rank];
7565 let _main = engine.gpu.enter_main()?;
7566 engine.stream().wait(&ws.ev_refresh)?;
7567 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
7568 let mut destination = peer_rows[0].slice_mut(0..values);
7569 engine
7570 .stream()
7571 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
7572 }
7573 }
7574 for rank in 0..ranks {
7575 self.decode_v2_input_qkv_rank(
7576 ws,
7577 pos_d,
7578 decode_input,
7579 q_m,
7580 k_m,
7581 v_m,
7582 q_norm,
7583 k_norm,
7584 head_dim,
7585 n_rot,
7586 rope_base,
7587 rope_freqs,
7588 rms_eps,
7589 gate_shards.as_ref(),
7590 has_gate,
7591 qkv_fused,
7592 defer_norm_rope,
7593 rank,
7594 tcol_col,
7595 )?;
7596 }
7597 Ok(())
7598 }
7599
7600 #[allow(clippy::too_many_arguments)]
7603 pub(crate) fn decode_v2_input_qkv_rank(
7604 &self,
7605 ws: &mut StepTpDecodeV2Ws,
7606 pos_d: &CudaSlice<i32>,
7607 decode_input: &mut ResidentReplicatedDeviceRows,
7608 q_m: &ResidentBf16ColumnParallel,
7609 k_m: &ResidentBf16ColumnParallel,
7610 v_m: &ResidentBf16ColumnParallel,
7611 q_norm: &[CudaSlice<f32>],
7612 k_norm: &[CudaSlice<f32>],
7613 head_dim: usize,
7614 n_rot: usize,
7615 rope_base: f32,
7616 rope_freqs: &[Option<&CudaSlice<f32>>],
7617 rms_eps: f32,
7618 gate_shards: Option<&StepTpGateShards<'_>>,
7619 has_gate: bool,
7620 qkv_fused: bool,
7621 defer_norm_rope: bool,
7622 rank: usize,
7623 tcol_col: Option<usize>,
7624 ) -> Result<(), Box<dyn std::error::Error>> {
7625 let ranks = self.ranks.len();
7626 let local_heads = ws.local_q_dim / head_dim;
7627 let local_kv_heads = ws.local_kv_dim / head_dim;
7628 let engine = &self.ranks[rank];
7629 let _main = engine.gpu.enter_main()?;
7630 let ws_e_device = ws.e_device;
7631 if qkv_fused && tcol_col.is_some() {
7636 #[allow(clippy::unnecessary_unwrap)]
7637 let c = tcol_col.expect("checked");
7639 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
7640 return Err("tcol select without precompute".into());
7641 }
7642 if engine.ctx().ordinal() != ws_e_device {
7646 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7647 }
7648 let StepTpDecodeV2Ws {
7649 tcol_q,
7650 tcol_k,
7651 tcol_v,
7652 tcol_g,
7653 q_raw,
7654 k_raw,
7655 v_raw,
7656 gate,
7657 local_q_dim,
7658 local_kv_dim,
7659 heads,
7660 ..
7661 } = &mut *ws;
7662 let lg = *heads / ranks;
7663 let stream = engine.stream();
7664 {
7665 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
7666 stream.memcpy_dtod(
7667 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
7668 &mut dst,
7669 )?;
7670 }
7671 {
7672 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
7673 stream.memcpy_dtod(
7674 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7675 &mut dst,
7676 )?;
7677 }
7678 {
7679 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
7680 stream.memcpy_dtod(
7681 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7682 &mut dst,
7683 )?;
7684 }
7685 if has_gate && lg > 0 {
7686 let mut dst = gate[rank].slice_mut(0..lg);
7687 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
7688 }
7689 if !defer_norm_rope {
7690 } else {
7694 return Ok(());
7695 }
7696 }
7697 if qkv_fused {
7698 let same_dev = engine.ctx().ordinal() == ws.e_device;
7703 if !same_dev {
7704 raw_copy_bytes(
7705 ws.raw_attn_in[rank],
7706 ws.raw_h_stage,
7707 q_m.in_features * 4,
7708 engine,
7709 )?;
7710 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7711 }
7712 let StepTpDecodeV2Ws {
7713 q_raw,
7714 k_raw,
7715 v_raw,
7716 gate,
7717 gate_e,
7718 attn_in,
7719 h_stage,
7720 heads,
7721 local_q_dim,
7722 local_kv_dim,
7723 w8_aq,
7724 w8_ad,
7725 w8_in,
7726 ..
7727 } = &mut *ws;
7728 let input_ref: &CudaSlice<f32> = if same_dev {
7729 h_stage
7730 .as_ref()
7731 .ok_or("step TP decode v2 stage not armed")?
7732 } else {
7733 &attn_in[rank]
7734 };
7735 match (
7736 &q_m.ranks[rank].weight,
7737 &k_m.ranks[rank].weight,
7738 &v_m.ranks[rank].weight,
7739 ) {
7740 (
7741 ResidentBf16Weight::F32(wq),
7742 ResidentBf16Weight::F32(wk),
7743 ResidentBf16Weight::F32(wv),
7744 ) => {
7745 let (wg, out_g) = match &gate_shards {
7746 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
7747 Some(StepTpGateShards::Bf16(_)) => {
7748 return Err("step TP decode v2 gate shard class does not \
7749 match the F32 projections"
7750 .into());
7751 }
7752 None => (&*gate_e, 0),
7754 };
7755 engine.matvec_f32_qkv_into(
7756 wq,
7757 wk,
7758 wv,
7759 wg,
7760 input_ref,
7761 &mut q_raw[rank],
7762 &mut k_raw[rank],
7763 &mut v_raw[rank],
7764 &mut gate[rank],
7765 q_m.in_features,
7766 *local_q_dim,
7767 *local_kv_dim,
7768 out_g,
7769 )?;
7770 }
7771 (
7772 ResidentBf16Weight::Bf16(wq),
7773 ResidentBf16Weight::Bf16(wk),
7774 ResidentBf16Weight::Bf16(wv),
7775 ) => {
7776 let (wg, out_g) = match &gate_shards {
7777 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
7778 Some(StepTpGateShards::F32(_)) => {
7779 return Err("step TP decode v2 gate shard class does not \
7780 match the bf16 projections"
7781 .into());
7782 }
7783 None => (wq, 0),
7784 };
7785 let in_f = q_m.in_features;
7792 let q8_ready = crate::step_tp_w8_on()
7793 && q_m.ranks[rank].q8.is_some()
7794 && k_m.ranks[rank].q8.is_some()
7795 && v_m.ranks[rank].q8.is_some();
7796 if q8_ready {
7797 if *w8_in != in_f || w8_aq.len() != ranks {
7798 w8_aq.clear();
7799 w8_ad.clear();
7800 for e_rank in &self.ranks {
7801 let _m = e_rank.gpu.enter_main()?;
7802 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7803 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7804 }
7805 *w8_in = in_f;
7806 }
7807 engine.quantize_q8_1_into(
7808 input_ref,
7809 1,
7810 in_f,
7811 &mut w8_aq[rank],
7812 &mut w8_ad[rank],
7813 )?;
7814 engine.qmatvec_q8_0_qkv_rp_into(
7819 q_m.ranks[rank].q8.as_ref().unwrap(),
7820 k_m.ranks[rank].q8.as_ref().unwrap(),
7821 v_m.ranks[rank].q8.as_ref().unwrap(),
7822 &w8_aq[rank],
7823 &w8_ad[rank],
7824 &mut q_raw[rank],
7825 &mut k_raw[rank],
7826 &mut v_raw[rank],
7827 in_f,
7828 *local_q_dim,
7829 *local_kv_dim,
7830 )?;
7831 if out_g > 0 {
7832 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7833 }
7834 } else {
7835 engine.matvec_bf16_qkvg_into(
7836 wq,
7837 wk,
7838 wv,
7839 wg,
7840 input_ref,
7841 &mut q_raw[rank],
7842 &mut k_raw[rank],
7843 &mut v_raw[rank],
7844 &mut gate[rank],
7845 q_m.in_features,
7846 *local_q_dim,
7847 *local_kv_dim,
7848 out_g,
7849 )?;
7850 }
7851 }
7852 _ => {
7853 return Err("step TP decode v2 QKV projections mix residency classes".into());
7854 }
7855 }
7856 } else {
7857 for (matrix, local_out, raw) in [
7858 (q_m, ws.local_q_dim, &mut ws.q_raw),
7859 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7860 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7861 ] {
7862 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7863 return Err("step TP decode v2 lost its F32 projection residency".into());
7864 };
7865 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7866 engine.linear_f32_resident_canonical_rows_t1_into(
7867 &decode_input.ranks[rank],
7868 values_w,
7869 &mut raw[rank],
7870 matrix.in_features,
7871 local_out,
7872 chunk_rows,
7873 )?;
7874 }
7875 }
7876 if qkv_fused && defer_norm_rope {
7877 } else if qkv_fused {
7879 let StepTpDecodeV2Ws {
7882 q_raw,
7883 k_raw,
7884 q,
7885 k,
7886 pos,
7887 pos_stage,
7888 ..
7889 } = &mut *ws;
7890 let same_dev = engine.ctx().ordinal() == ws_e_device;
7891 let pos_ref: &CudaSlice<i32> = if same_dev {
7892 pos_stage
7893 .as_ref()
7894 .ok_or("step TP decode v2 pos stage not armed")?
7895 } else {
7896 &pos[rank]
7897 };
7898 engine.qk_norm_rope_into(
7899 &q_raw[rank],
7900 &k_raw[rank],
7901 &q_norm[rank],
7902 &k_norm[rank],
7903 &mut q[rank],
7904 &mut k[rank],
7905 pos_ref,
7906 head_dim,
7907 n_rot,
7908 local_heads,
7909 local_kv_heads,
7910 rms_eps,
7911 rope_base,
7912 1.0,
7913 rope_freqs[rank],
7914 )?;
7915 } else {
7916 engine.rms_norm(
7917 &ws.q_raw[rank],
7918 &q_norm[rank],
7919 &mut ws.q[rank],
7920 head_dim,
7921 local_heads,
7922 rms_eps,
7923 )?;
7924 engine.rms_norm(
7925 &ws.k_raw[rank],
7926 &k_norm[rank],
7927 &mut ws.k[rank],
7928 head_dim,
7929 local_kv_heads,
7930 rms_eps,
7931 )?;
7932 {
7933 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7934 engine
7935 .stream()
7936 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7937 }
7938 engine.rope_neox2(
7939 &mut ws.q[rank],
7940 &mut ws.k[rank],
7941 &ws.pos[rank],
7942 head_dim,
7943 n_rot,
7944 local_heads,
7945 local_kv_heads,
7946 1,
7947 rope_base,
7948 1.0,
7949 rope_freqs[rank],
7950 )?;
7951 }
7952 if has_gate && gate_shards.is_none() {
7953 let gate_start = rank * (ws.heads / ranks);
7954 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7955 engine.stream().memcpy_dtod(
7956 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7957 &mut gate_dst,
7958 )?;
7959 }
7960 Ok(())
7961 }
7962
7963 pub(crate) fn decode_v2_finish_rank_partial(
7967 &self,
7968 ws: &mut StepTpDecodeV2Ws,
7969 o_m: &ResidentStepBf16RowParallel,
7970 o_fused: bool,
7971 rank: usize,
7972 ) -> Result<(), Box<dyn std::error::Error>> {
7973 let engine = &self.ranks[rank];
7974 let _main = engine.gpu.enter_main()?;
7975 if o_fused {
7976 let StepTpDecodeV2Ws {
7977 gated,
7978 o_partials,
7979 o_block_cols,
7980 o_out,
7981 w8o_aq,
7982 w8o_ad,
7983 w8o_in,
7984 ..
7985 } = &mut *ws;
7986 let all_f32 = o_m.ranks[rank]
7987 .iter()
7988 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7989 if all_f32 {
7990 let mut weights = Vec::with_capacity(4);
7991 for block in 0..4 {
7992 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7993 unreachable!("all_f32 checked above");
7994 };
7995 weights.push(weight);
7996 }
7997 engine.matvec_f32_b4_into(
7998 [weights[0], weights[1], weights[2], weights[3]],
7999 &gated[rank],
8000 &mut o_partials[rank][0],
8001 *o_block_cols,
8002 *o_out,
8003 )?;
8004 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
8005 let in_f = 4 * *o_block_cols;
8010 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
8011 w8o_aq.clear();
8012 w8o_ad.clear();
8013 for e_rank in &self.ranks {
8014 let _m = e_rank.gpu.enter_main()?;
8015 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
8016 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
8017 }
8018 *w8o_in = in_f;
8019 }
8020 engine.quantize_q8_1_into(
8021 &gated[rank],
8022 1,
8023 in_f,
8024 &mut w8o_aq[rank],
8025 &mut w8o_ad[rank],
8026 )?;
8027 engine.qmatvec_q8_0_b4_rp_into(
8028 [
8029 o_m.ranks[rank][0].q8.as_ref().unwrap(),
8030 o_m.ranks[rank][1].q8.as_ref().unwrap(),
8031 o_m.ranks[rank][2].q8.as_ref().unwrap(),
8032 o_m.ranks[rank][3].q8.as_ref().unwrap(),
8033 ],
8034 &w8o_aq[rank],
8035 &w8o_ad[rank],
8036 &mut o_partials[rank][0],
8037 *o_block_cols,
8038 *o_out,
8039 )?;
8040 } else {
8041 let mut weights = Vec::with_capacity(4);
8042 for block in 0..4 {
8043 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
8044 return Err("step TP decode v2 O projections mix residency classes".into());
8045 };
8046 weights.push(weight);
8047 }
8048 engine.matvec_bf16_b4_into(
8049 [weights[0], weights[1], weights[2], weights[3]],
8050 &gated[rank],
8051 &mut o_partials[rank][0],
8052 *o_block_cols,
8053 *o_out,
8054 )?;
8055 }
8056 } else {
8057 for block in 0..ws.blocks_per_rank {
8058 let x =
8059 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
8060 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
8061 match &o_m.ranks[rank][block].weight {
8062 ResidentBf16Weight::F32(weight) => {
8063 let w = weight.slice(0..weight.len());
8064 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
8065 }
8066 ResidentBf16Weight::Bf16(weight) => {
8067 engine.matvec_bf16_views_into(
8068 weight,
8069 &x,
8070 &mut y,
8071 ws.o_block_cols,
8072 ws.o_out,
8073 )?;
8074 }
8075 }
8076 }
8077 }
8078 Ok(())
8079 }
8080
8081 pub(crate) fn decode_v2_finish(
8089 &self,
8090 ws: &mut StepTpDecodeV2Ws,
8091 e: &Engine,
8092 o_m: &ResidentStepBf16RowParallel,
8093 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8094 let ranks = self.ranks.len();
8095 if e.ctx().ordinal() != ws.e_device {
8096 return Err("step TP decode v2 finish engine changed".into());
8097 }
8098 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
8103
8104 for rank in 0..ranks {
8107 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
8108 if rank == 0 {
8109 continue;
8112 }
8113 let engine = &self.ranks[rank];
8114 let _main = engine.gpu.enter_main()?;
8115 ws.ev_rank[rank].record(&engine.stream())?;
8116 }
8117
8118 let root = &self.ranks[0];
8120 #[allow(unused_assignments)]
8121 let mut final_in_a = false;
8122 {
8123 let _main = root.gpu.enter_main()?;
8124 for ev in ws.ev_rank.iter().skip(1) {
8125 root.stream().wait(ev)?;
8126 }
8127 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
8128 ws.ev_oproj.record(&root.stream())?;
8134 let _main = e.gpu.enter_main()?;
8135 e.stream().wait(&ws.ev_oproj)?;
8136 let mut output = e.uninit(ws.o_out)?;
8137 if oproj_tail_on() && oproj_tail_eligible() {
8138 use cudarc::driver::DevicePtr;
8141 let stream = e.stream();
8142 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
8143 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
8144 set_oproj_tail((p0, p1));
8145 return Ok(output);
8146 }
8147 e.add(
8148 &ws.o_partials[0][0],
8149 &ws.o_partials[1][0],
8150 &mut output,
8151 ws.o_out,
8152 )?;
8153 return Ok(output);
8154 }
8155 if o_fused {
8156 self.decode_v2_finish_root_fused(ws)?;
8157 ws.ev_oproj.record(&root.stream())?;
8158 let _main = e.gpu.enter_main()?;
8159 e.stream().wait(&ws.ev_oproj)?;
8160 let mut output = e.uninit(ws.o_out)?;
8161 e.stream().memcpy_dtod(
8162 &ws.reduce_a.slice(0..ws.o_out),
8163 &mut output.slice_mut(0..ws.o_out),
8164 )?;
8165 return Ok(output);
8166 }
8167 let mut first = true;
8168 let mut current_is_a = false;
8169 for rank in 0..ranks {
8170 for block in 0..ws.blocks_per_rank {
8171 let use_peer = rank != 0;
8172 if use_peer {
8173 raw_copy_bytes(
8174 ws.raw_peer_partial,
8175 ws.raw_o_partials[rank][block],
8176 ws.o_out * std::mem::size_of::<f32>(),
8177 root,
8178 )?;
8179 }
8180 match (first, current_is_a, use_peer) {
8182 (true, _, true) => {
8183 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
8184 }
8185 (true, _, false) => root.add(
8186 &ws.zeros,
8187 &ws.o_partials[0][block],
8188 &mut ws.reduce_a,
8189 ws.o_out,
8190 )?,
8191 (false, true, true) => {
8192 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
8193 }
8194 (false, true, false) => root.add(
8195 &ws.reduce_a,
8196 &ws.o_partials[0][block],
8197 &mut ws.reduce_b,
8198 ws.o_out,
8199 )?,
8200 (false, false, true) => {
8201 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
8202 }
8203 (false, false, false) => root.add(
8204 &ws.reduce_b,
8205 &ws.o_partials[0][block],
8206 &mut ws.reduce_a,
8207 ws.o_out,
8208 )?,
8209 }
8210 current_is_a = first || !current_is_a;
8211 first = false;
8212 }
8213 }
8214 final_in_a = current_is_a;
8215
8216 if !no_local_shadow_on() {
8217 let bytes = ws.local_kv_dim * std::mem::size_of::<f32>();
8218 for rank in 0..ranks {
8219 let offset = rank * bytes;
8220 raw_copy_bytes(ws.raw_k_shadow + offset as u64, ws.raw_k[rank], bytes, root)?;
8221 raw_copy_bytes(
8222 ws.raw_v_shadow + offset as u64,
8223 ws.raw_v_raw[rank],
8224 bytes,
8225 root,
8226 )?;
8227 }
8228 }
8229 ws.ev_oproj.record(&root.stream())?;
8230 }
8231
8232 let _main = e.gpu.enter_main()?;
8236 e.stream().wait(&ws.ev_oproj)?;
8237 let mut output = e.uninit(ws.o_out)?;
8238 let source = if final_in_a {
8239 &ws.reduce_a
8240 } else {
8241 &ws.reduce_b
8242 };
8243 e.stream().memcpy_dtod(
8244 &source.slice(0..ws.o_out),
8245 &mut output.slice_mut(0..ws.o_out),
8246 )?;
8247 Ok(output)
8248 }
8249
8250 #[allow(clippy::too_many_arguments)] pub fn run_routed_experts(
8252 &self,
8253 experts: &ResidentExpertParallel,
8254 input: &[f32],
8255 tokens: usize,
8256 selected: &[usize],
8257 route_weights: &[f32],
8258 experts_per_token: usize,
8259 activation_limit: Option<f32>,
8260 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8261 validate_step_expert_activation_limit(activation_limit)?;
8262 validate_ep_residency(&self.ranks, experts)?;
8263 validate_activations(input, tokens, experts.input_width)?;
8264 let pairs = tokens
8265 .checked_mul(experts_per_token)
8266 .ok_or("EP route count overflow")?;
8267 if selected.len() != pairs || route_weights.len() != pairs {
8268 return Err(format!(
8269 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
8270 {experts_per_token} ({pairs})",
8271 selected.len(),
8272 route_weights.len(),
8273 )
8274 .into());
8275 }
8276 if !route_weights.iter().all(|weight| weight.is_finite()) {
8277 return Err("EP route weights contain a non-finite value".into());
8278 }
8279 if self.native_p2p {
8280 return self.run_routed_experts_native(
8281 experts,
8282 input,
8283 tokens,
8284 selected,
8285 route_weights,
8286 experts_per_token,
8287 activation_limit,
8288 );
8289 }
8290
8291 let mut output = vec![0.0f32; tokens * experts.input_width];
8292 let per_rank = experts.expert_count / experts.ranks.len();
8293 for token in 0..tokens {
8294 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8295 for slot in 0..experts_per_token {
8296 let pair = token * experts_per_token + slot;
8297 let expert = selected[pair];
8298 if expert >= experts.expert_count {
8299 return Err(format!(
8300 "EP selected expert {expert} outside 0..{}",
8301 experts.expert_count
8302 )
8303 .into());
8304 }
8305 let owner = expert / per_rank;
8306 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8307 let rank = &experts.ranks[owner];
8308 let engine = &self.ranks[owner];
8309 let gate =
8310 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
8311 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
8312 let activated: Vec<f32> = gate
8313 .iter()
8314 .zip(&up)
8315 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8316 .collect();
8317 debug_assert_eq!(activated.len(), experts.expert_width);
8318 let down =
8319 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
8320 let weight = route_weights[pair];
8321 for (sum, value) in output
8322 [token * experts.input_width..(token + 1) * experts.input_width]
8323 .iter_mut()
8324 .zip(down)
8325 {
8326 *sum += weight * value;
8327 }
8328 }
8329 }
8330 Ok(output)
8331 }
8332
8333 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native(
8335 &self,
8336 experts: &ResidentExpertParallel,
8337 input: &[f32],
8338 tokens: usize,
8339 selected: &[usize],
8340 route_weights: &[f32],
8341 experts_per_token: usize,
8342 activation_limit: Option<f32>,
8343 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8344 if !self.native_p2p || self.ranks.len() < 2 {
8345 return Err("native EP execution requires at least two P2P ranks".into());
8346 }
8347 if self.ep_device_arithmetic {
8348 return self.run_routed_experts_native_device(
8349 experts,
8350 input,
8351 tokens,
8352 selected,
8353 route_weights,
8354 experts_per_token,
8355 activation_limit,
8356 );
8357 }
8358 let mut output = vec![0.0f32; tokens * experts.input_width];
8359 let per_rank = experts.expert_count / experts.ranks.len();
8360 for token in 0..tokens {
8361 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8362 let mut rank_inputs = (0..self.ranks.len())
8363 .map(|_| None)
8364 .collect::<Vec<Option<CudaSlice<f32>>>>();
8365 rank_inputs[0] = Some({
8366 let root = &self.ranks[0];
8367 let _main = root.gpu.enter_main()?;
8368 root.htod(input_row)?
8369 });
8370
8371 for slot in 0..experts_per_token {
8372 let pair = token * experts_per_token + slot;
8373 let expert = selected[pair];
8374 if expert >= experts.expert_count {
8375 return Err(format!(
8376 "EP selected expert {expert} outside 0..{}",
8377 experts.expert_count
8378 )
8379 .into());
8380 }
8381 let owner = expert / per_rank;
8382 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8383 if rank_inputs[owner].is_none() {
8384 let peer_input = {
8385 let root_input = rank_inputs[0]
8386 .as_ref()
8387 .ok_or("native EP lost its root input")?;
8388 let engine = &self.ranks[owner];
8389 let _main = engine.gpu.enter_main()?;
8390 let mut peer_input = engine.uninit(experts.input_width)?;
8391 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8392 peer_input
8393 };
8394 rank_inputs[owner] = Some(peer_input);
8395 }
8396
8397 let rank = &experts.ranks[owner];
8398 let engine = &self.ranks[owner];
8399 let owner_input = rank_inputs[owner]
8400 .as_ref()
8401 .ok_or("native EP owner input is absent after dispatch")?;
8402 let gate = run_resident_bank_expert_device(
8403 engine,
8404 &rank.gate,
8405 local_expert,
8406 owner_input,
8407 1,
8408 )?;
8409 let up = run_resident_bank_expert_device(
8410 engine,
8411 &rank.up,
8412 local_expert,
8413 owner_input,
8414 1,
8415 )?;
8416 let (gate, up) = {
8417 let _main = engine.gpu.enter_main()?;
8418 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
8419 };
8420 let activated = gate
8421 .iter()
8422 .zip(&up)
8423 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8424 .collect::<Vec<_>>();
8425 debug_assert_eq!(activated.len(), experts.expert_width);
8426 let activated = {
8427 let _main = engine.gpu.enter_main()?;
8428 engine.htod(&activated)?
8429 };
8430 let down = run_resident_bank_expert_device(
8431 engine,
8432 &rank.down,
8433 local_expert,
8434 &activated,
8435 1,
8436 )?;
8437 let down = if owner == 0 {
8438 let _main = engine.gpu.enter_main()?;
8439 engine.dtoh(&down)?
8440 } else {
8441 let root = &self.ranks[0];
8442 let _main = root.gpu.enter_main()?;
8443 let mut root_down = root.uninit(experts.input_width)?;
8444 root.stream().memcpy_dtod(&down, &mut root_down)?;
8445 root.dtoh(&root_down)?
8446 };
8447 let weight = route_weights[pair];
8448 for (sum, value) in output
8449 [token * experts.input_width..(token + 1) * experts.input_width]
8450 .iter_mut()
8451 .zip(down)
8452 {
8453 *sum += weight * value;
8454 }
8455 }
8456 }
8457 Ok(output)
8458 }
8459
8460 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native_device(
8462 &self,
8463 experts: &ResidentExpertParallel,
8464 input: &[f32],
8465 tokens: usize,
8466 selected: &[usize],
8467 route_weights: &[f32],
8468 experts_per_token: usize,
8469 activation_limit: Option<f32>,
8470 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8471 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
8472 return Err(
8473 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
8474 );
8475 }
8476 let mut output = Vec::with_capacity(tokens * experts.input_width);
8477 let per_rank = experts.expert_count / experts.ranks.len();
8478 let root = &self.ranks[0];
8479 for token in 0..tokens {
8480 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8481 let mut rank_inputs = (0..self.ranks.len())
8482 .map(|_| None)
8483 .collect::<Vec<Option<CudaSlice<f32>>>>();
8484 rank_inputs[0] = Some({
8485 let _main = root.gpu.enter_main()?;
8486 root.htod(input_row)?
8487 });
8488 let mut root_output = {
8489 let _main = root.gpu.enter_main()?;
8490 root.zeros(experts.input_width)?
8491 };
8492 let mut remote_down_keepalive = Vec::new();
8493
8494 for slot in 0..experts_per_token {
8495 let pair = token * experts_per_token + slot;
8496 let expert = selected[pair];
8497 if expert >= experts.expert_count {
8498 return Err(format!(
8499 "EP selected expert {expert} outside 0..{}",
8500 experts.expert_count
8501 )
8502 .into());
8503 }
8504 let owner = expert / per_rank;
8505 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8506 if rank_inputs[owner].is_none() {
8507 let peer_input = {
8508 let root_input = rank_inputs[0]
8509 .as_ref()
8510 .ok_or("native EP lost its root input")?;
8511 let engine = &self.ranks[owner];
8512 let _main = engine.gpu.enter_main()?;
8513 let mut peer_input = engine.uninit(experts.input_width)?;
8514 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8515 peer_input
8516 };
8517 rank_inputs[owner] = Some(peer_input);
8518 }
8519
8520 let rank = &experts.ranks[owner];
8521 let engine = &self.ranks[owner];
8522 let owner_input = rank_inputs[owner]
8523 .as_ref()
8524 .ok_or("native EP owner input is absent after dispatch")?;
8525 let gate = run_resident_bank_expert_device(
8526 engine,
8527 &rank.gate,
8528 local_expert,
8529 owner_input,
8530 1,
8531 )?;
8532 let up = run_resident_bank_expert_device(
8533 engine,
8534 &rank.up,
8535 local_expert,
8536 owner_input,
8537 1,
8538 )?;
8539 let activated = {
8540 let _main = engine.gpu.enter_main()?;
8541 let mut activated = engine.uninit(experts.expert_width)?;
8542 if let Some(limit) = activation_limit {
8543 engine.silu_clamped_mul_host_expf(
8544 &gate,
8545 &up,
8546 limit,
8547 &mut activated,
8548 experts.expert_width,
8549 )?;
8550 } else {
8551 engine.silu_mul_host_expf(
8552 &gate,
8553 &up,
8554 &mut activated,
8555 experts.expert_width,
8556 )?;
8557 }
8558 activated
8559 };
8560 let down = run_resident_bank_expert_device(
8561 engine,
8562 &rank.down,
8563 local_expert,
8564 &activated,
8565 1,
8566 )?;
8567 let root_down = if owner == 0 {
8568 down
8569 } else {
8570 let _main = root.gpu.enter_main()?;
8571 let mut root_down = root.uninit(experts.input_width)?;
8572 root.stream().memcpy_dtod(&down, &mut root_down)?;
8573 remote_down_keepalive.push(down);
8577 root_down
8578 };
8579 let _main = root.gpu.enter_main()?;
8580 let mut destination = root_output.slice_mut(0..experts.input_width);
8581 root.axpy_host_into(
8582 &root_down.slice(0..root_down.len()),
8583 route_weights[pair],
8584 &mut destination,
8585 experts.input_width,
8586 )?;
8587 }
8588
8589 let _main = root.gpu.enter_main()?;
8590 let root_output = root.dtoh(&root_output)?;
8591 drop(remote_down_keepalive);
8592 output.extend(root_output);
8593 }
8594 Ok(output)
8595 }
8596}
8597
8598#[allow(clippy::manual_is_multiple_of)] fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8600 if matrix.out_features % tp != 0 {
8601 return Err(format!(
8602 "column-parallel out_features {} is not divisible by TP={tp}",
8603 matrix.out_features
8604 ));
8605 }
8606 let local_out = matrix.out_features / tp;
8607 if !local_out.is_multiple_of(FP8_BLOCK) {
8608 return Err(format!(
8609 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
8610 E4M3 scale block"
8611 ));
8612 }
8613 Ok(())
8614}
8615
8616#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
8618 if !matches!(tp, 1 | 2 | 4 | 8) {
8619 return Err(format!(
8620 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8621 ));
8622 }
8623 if out_features == 0 || !out_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8624 return Err(format!(
8625 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
8626 ));
8627 }
8628 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
8629 let local_out = out_features / tp;
8630 if local_out % canonical_rows != 0 {
8631 return Err(format!(
8632 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
8633 {canonical_rows}-row chunks"
8634 ));
8635 }
8636 Ok(canonical_rows)
8637}
8638
8639#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
8641 if !matches!(tp, 1 | 2 | 4 | 8) {
8642 return Err(format!(
8643 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8644 ));
8645 }
8646 if in_features == 0 || !in_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8647 return Err(format!(
8648 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
8649 ));
8650 }
8651 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
8652 let local_in = in_features / tp;
8653 if local_in % canonical_cols != 0 {
8654 return Err(format!(
8655 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
8656 {canonical_cols}-column chunks"
8657 ));
8658 }
8659 Ok(canonical_cols)
8660}
8661
8662#[allow(clippy::manual_is_multiple_of)] fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8664 if matrix.in_features % tp != 0 {
8665 return Err(format!(
8666 "row-parallel in_features {} is not divisible by TP={tp}",
8667 matrix.in_features
8668 ));
8669 }
8670 let local_in = matrix.in_features / tp;
8671 if !local_in.is_multiple_of(FP8_BLOCK) {
8672 return Err(format!(
8673 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
8674 E4M3 scale block"
8675 ));
8676 }
8677 Ok(())
8678}
8679
8680fn upload_rank(
8681 engine: &Engine,
8682 matrix: E4m3BlockMatrix<'_>,
8683) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
8684 let _main = engine.gpu.enter_main()?;
8685 matrix.validate()?;
8686 Ok(ResidentE4m3Rank {
8687 codes: engine.htod_bytes(matrix.codes)?,
8688 scales: engine.htod(matrix.scales)?,
8689 out_features: matrix.out_features,
8690 in_features: matrix.in_features,
8691 })
8692}
8693
8694fn upload_bf16_rank(
8695 engine: &Engine,
8696 matrix: Bf16Matrix<'_>,
8697 f32_mirror: bool,
8698) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
8699 let _main = engine.gpu.enter_main()?;
8700 matrix.validate()?;
8701 let bytes = engine.htod_bytes(matrix.bytes)?;
8702 let weight = if f32_mirror {
8703 let values = matrix
8704 .out_features
8705 .checked_mul(matrix.in_features)
8706 .ok_or("resident BF16 mirror element count overflow")?;
8707 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
8708 } else {
8709 ResidentBf16Weight::Bf16(bytes)
8710 };
8711 let q8 = if crate::step_tp_w8_on() && matrix.in_features.is_multiple_of(32) {
8715 if let ResidentBf16Weight::Bf16(bytes) = &weight {
8716 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
8723 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
8724 engine.encode_q8_0_from_bf16(
8725 bytes,
8726 &mut interleaved,
8727 matrix.in_features,
8728 matrix.out_features,
8729 )?;
8730 let mirror =
8731 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
8732 Some(mirror)
8733 } else {
8734 None
8735 }
8736 } else {
8737 None
8738 };
8739 Ok(ResidentBf16Rank {
8740 weight,
8741 out_features: matrix.out_features,
8742 in_features: matrix.in_features,
8743 q8,
8744 })
8745}
8746
8747fn upload_expert_bank_rank(
8748 engine: &Engine,
8749 bank: E4m3ExpertBank<'_>,
8750 expert_range: Range<usize>,
8751) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8752 let _main = engine.gpu.enter_main()?;
8753 bank.validate()?;
8754 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
8755 return Err(format!(
8756 "invalid EP expert range {expert_range:?} for {} experts",
8757 bank.expert_count
8758 )
8759 .into());
8760 }
8761 let code_stride = bank.out_features * bank.in_features;
8762 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
8763 Ok(ResidentE4m3ExpertBankRank {
8764 codes: engine.htod_bytes(
8765 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
8766 )?,
8767 scales: engine.htod(
8768 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
8769 )?,
8770 expert_range,
8771 out_features: bank.out_features,
8772 in_features: bank.in_features,
8773 code_stride,
8774 scale_stride,
8775 k_blocks: None,
8776 })
8777}
8778
8779#[allow(clippy::manual_is_multiple_of)] fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8781 if bank.out_features % tp != 0 {
8782 return Err(format!(
8783 "TP expert output width {} is not divisible by TP={tp}",
8784 bank.out_features
8785 ));
8786 }
8787 let local_out = bank.out_features / tp;
8788 if !local_out.is_multiple_of(FP8_BLOCK) {
8789 return Err(format!(
8790 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
8791 ));
8792 }
8793 Ok(())
8794}
8795
8796#[allow(clippy::manual_is_multiple_of)] fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8798 if bank.in_features % tp != 0 {
8799 return Err(format!(
8800 "TP expert input width {} is not divisible by TP={tp}",
8801 bank.in_features
8802 ));
8803 }
8804 let local_in = bank.in_features / tp;
8805 if !local_in.is_multiple_of(FP8_BLOCK) {
8806 return Err(format!(
8807 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
8808 ));
8809 }
8810 Ok(())
8811}
8812
8813fn upload_column_bank_rank(
8814 engine: &Engine,
8815 bank: E4m3ExpertBank<'_>,
8816 tp: usize,
8817 rank: usize,
8818) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8819 let _main = engine.gpu.enter_main()?;
8820 let packed = pack_column_bank_rank(bank, tp, rank)?;
8821 Ok(ResidentE4m3ExpertBankRank {
8822 codes: engine.htod_bytes(&packed.codes)?,
8823 scales: engine.htod(&packed.scales)?,
8824 expert_range: packed.expert_range,
8825 out_features: packed.out_features,
8826 in_features: packed.in_features,
8827 code_stride: packed.code_stride,
8828 scale_stride: packed.scale_stride,
8829 k_blocks: packed.k_blocks,
8830 })
8831}
8832
8833fn pack_column_bank_rank(
8834 bank: E4m3ExpertBank<'_>,
8835 tp: usize,
8836 rank: usize,
8837) -> Result<PackedE4m3ExpertBankRank, String> {
8838 bank.validate()?;
8839 validate_column_bank_shape(bank, tp)?;
8840 if rank >= tp {
8841 return Err(format!("TP rank {rank} outside 0..{tp}"));
8842 }
8843 let local_out = bank.out_features / tp;
8844 let full_code_stride = bank.out_features * bank.in_features;
8845 let local_code_stride = local_out * bank.in_features;
8846 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8847 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8848 let local_scale_rows = local_out / FP8_BLOCK;
8849 let local_scale_stride = local_scale_rows * scale_cols;
8850 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8851 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8852 let row_start = rank * local_out;
8853 let scale_row_start = rank * local_scale_rows;
8854 for expert in 0..bank.expert_count {
8855 let code_start = expert * full_code_stride + row_start * bank.in_features;
8856 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8857 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8858 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8859 }
8860 Ok(PackedE4m3ExpertBankRank {
8861 codes,
8862 scales,
8863 expert_range: 0..bank.expert_count,
8864 out_features: local_out,
8865 in_features: bank.in_features,
8866 code_stride: local_code_stride,
8867 scale_stride: local_scale_stride,
8868 k_blocks: None,
8869 })
8870}
8871
8872fn upload_row_bank_rank(
8873 engine: &Engine,
8874 bank: E4m3ExpertBank<'_>,
8875 tp: usize,
8876 rank: usize,
8877) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8878 let _main = engine.gpu.enter_main()?;
8879 let packed = pack_row_bank_rank(bank, tp, rank)?;
8880 Ok(ResidentE4m3ExpertBankRank {
8881 codes: engine.htod_bytes(&packed.codes)?,
8882 scales: engine.htod(&packed.scales)?,
8883 expert_range: packed.expert_range,
8884 out_features: packed.out_features,
8885 in_features: packed.in_features,
8886 code_stride: packed.code_stride,
8887 scale_stride: packed.scale_stride,
8888 k_blocks: packed.k_blocks,
8889 })
8890}
8891
8892fn pack_row_bank_rank(
8893 bank: E4m3ExpertBank<'_>,
8894 tp: usize,
8895 rank: usize,
8896) -> Result<PackedE4m3ExpertBankRank, String> {
8897 bank.validate()?;
8898 validate_row_bank_shape(bank, tp)?;
8899 if rank >= tp {
8900 return Err(format!("TP rank {rank} outside 0..{tp}"));
8901 }
8902 let local_in = bank.in_features / tp;
8903 let full_code_stride = bank.out_features * bank.in_features;
8904 let local_code_stride = bank.out_features * local_in;
8905 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8906 let local_scale_cols = local_in / FP8_BLOCK;
8907 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8908 let full_scale_stride = scale_rows * full_scale_cols;
8909 let local_scale_stride = scale_rows * local_scale_cols;
8910 let global_block_start = rank * local_scale_cols;
8911 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8912 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8913 for expert in 0..bank.expert_count {
8914 let expert_code_start = expert * full_code_stride;
8915 let expert_scale_start = expert * full_scale_stride;
8916 for local_block in 0..local_scale_cols {
8917 let global_block = global_block_start + local_block;
8918 let column_start = global_block * FP8_BLOCK;
8919 for row in 0..bank.out_features {
8920 let start = expert_code_start + row * bank.in_features + column_start;
8921 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8922 }
8923 for row in 0..scale_rows {
8924 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8925 }
8926 }
8927 }
8928 Ok(PackedE4m3ExpertBankRank {
8929 codes,
8930 scales,
8931 expert_range: 0..bank.expert_count,
8932 out_features: bank.out_features,
8933 in_features: local_in,
8934 code_stride: local_code_stride,
8935 scale_stride: local_scale_stride,
8936 k_blocks: Some(local_scale_cols),
8937 })
8938}
8939
8940fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8941 if engines.len() != ranks.len() {
8942 return Err(format!(
8943 "resident TP rank count {} != runtime rank count {}",
8944 ranks.len(),
8945 engines.len()
8946 ));
8947 }
8948 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8949 let device = engine.ctx().ordinal();
8950 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8951 return Err(format!(
8952 "resident TP rank {rank} is not owned by runtime device {device}"
8953 ));
8954 }
8955 }
8956 Ok(())
8957}
8958
8959fn validate_tp_bank_residency(
8960 engines: &[Engine],
8961 experts: &ResidentTpExpertBank,
8962) -> Result<(), String> {
8963 if engines.len() != experts.gate.len()
8964 || engines.len() != experts.up.len()
8965 || engines.len() != experts.down.len()
8966 {
8967 return Err(format!(
8968 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8969 experts.gate.len(),
8970 experts.up.len(),
8971 experts.down.len(),
8972 engines.len()
8973 ));
8974 }
8975 for (rank, engine) in engines.iter().enumerate() {
8976 let device = engine.ctx().ordinal();
8977 for (projection, bank) in [
8978 ("gate", &experts.gate[rank]),
8979 ("up", &experts.up[rank]),
8980 ("down", &experts.down[rank]),
8981 ] {
8982 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8983 return Err(format!(
8984 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8985 {device}"
8986 ));
8987 }
8988 }
8989 }
8990 Ok(())
8991}
8992
8993fn validate_ep_residency(
8994 engines: &[Engine],
8995 experts: &ResidentExpertParallel,
8996) -> Result<(), String> {
8997 if engines.len() != experts.ranks.len() {
8998 return Err(format!(
8999 "resident EP rank count {} != runtime rank count {}",
9000 experts.ranks.len(),
9001 engines.len()
9002 ));
9003 }
9004 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
9005 let device = engine.ctx().ordinal();
9006 for (projection, bank) in [
9007 ("gate", &resident.gate),
9008 ("up", &resident.up),
9009 ("down", &resident.down),
9010 ] {
9011 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
9012 return Err(format!(
9013 "resident EP rank {rank} {projection} bank is not owned by runtime device \
9014 {device}"
9015 ));
9016 }
9017 }
9018 }
9019 Ok(())
9020}
9021
9022fn run_rank(
9023 engine: &Engine,
9024 matrix: E4m3BlockMatrix<'_>,
9025 activations: &[f32],
9026 tokens: usize,
9027) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9028 let _main = engine.gpu.enter_main()?;
9029 let codes = engine.htod_bytes(matrix.codes)?;
9030 let scales = engine.htod(matrix.scales)?;
9031 let activations = engine.htod(activations)?;
9032 let output = engine.qmatvec_mmq_fp8_blk(
9033 &codes,
9034 &scales,
9035 &activations,
9036 tokens,
9037 matrix.in_features,
9038 matrix.out_features,
9039 )?;
9040 engine.dtoh(&output)
9041}
9042
9043fn run_resident_rank(
9044 engine: &Engine,
9045 matrix: &ResidentE4m3Rank,
9046 activations: &[f32],
9047 tokens: usize,
9048) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9049 let _main = engine.gpu.enter_main()?;
9050 let activations = engine.htod(activations)?;
9051 let output = engine.qmatvec_mmq_fp8_blk(
9052 &matrix.codes,
9053 &matrix.scales,
9054 &activations,
9055 tokens,
9056 matrix.in_features,
9057 matrix.out_features,
9058 )?;
9059 engine.dtoh(&output)
9060}
9061
9062fn run_resident_bf16_rank(
9063 engine: &Engine,
9064 matrix: &ResidentBf16Rank,
9065 activations: &[f32],
9066 tokens: usize,
9067 canonical_chunk_rows: Option<usize>,
9068) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9069 let _main = engine.gpu.enter_main()?;
9070 let activations = engine.htod(activations)?;
9071 let output = run_resident_bf16_rank_device(
9072 engine,
9073 matrix,
9074 &activations,
9075 tokens,
9076 canonical_chunk_rows,
9077 false,
9078 )?;
9079 engine.dtoh(&output)
9080}
9081
9082fn run_resident_bf16_rank_device(
9083 engine: &Engine,
9084 matrix: &ResidentBf16Rank,
9085 activations: &CudaSlice<f32>,
9086 tokens: usize,
9087 canonical_chunk_rows: Option<usize>,
9088 strided_chunk_output: bool,
9089) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9090 let _main = engine.gpu.enter_main()?;
9091 if activations.ordinal() != engine.ctx().ordinal() {
9092 return Err(format!(
9093 "resident BF16 activation device {} != rank device {}",
9094 activations.ordinal(),
9095 engine.ctx().ordinal()
9096 )
9097 .into());
9098 }
9099 if activations.len() != tokens * matrix.in_features {
9100 return Err(format!(
9101 "resident BF16 activation count {} != {tokens}x{}",
9102 activations.len(),
9103 matrix.in_features
9104 )
9105 .into());
9106 }
9107 match (&matrix.weight, canonical_chunk_rows) {
9108 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
9109 .linear_bf16_resident_canonical_rows(
9110 activations,
9111 bytes,
9112 tokens,
9113 matrix.in_features,
9114 matrix.out_features,
9115 rows,
9116 ),
9117 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
9118 activations,
9119 bytes,
9120 tokens,
9121 matrix.in_features,
9122 matrix.out_features,
9123 ),
9124 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
9125 .linear_f32_resident_canonical_rows_strided(
9126 activations,
9127 values,
9128 tokens,
9129 matrix.in_features,
9130 matrix.out_features,
9131 rows,
9132 ),
9133 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
9134 activations,
9135 values,
9136 tokens,
9137 matrix.in_features,
9138 matrix.out_features,
9139 rows,
9140 ),
9141 (ResidentBf16Weight::F32(values), None) => engine.linear(
9142 activations,
9143 values,
9144 tokens,
9145 matrix.in_features,
9146 matrix.out_features,
9147 ),
9148 }
9149}
9150
9151fn validate_resident_bf16_ranks(
9152 engines: &[Engine],
9153 ranks: &[ResidentBf16Rank],
9154) -> Result<(), String> {
9155 if engines.len() != ranks.len() {
9156 return Err(format!(
9157 "resident BF16 TP rank count {} != runtime rank count {}",
9158 ranks.len(),
9159 engines.len(),
9160 ));
9161 }
9162 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
9163 let device = engine.ctx().ordinal();
9164 if matrix.weight.ordinal() != device {
9165 return Err(format!(
9166 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
9167 ));
9168 }
9169 }
9170 Ok(())
9171}
9172
9173fn validate_step_bf16_row_residency(
9174 engines: &[Engine],
9175 matrix: &ResidentStepBf16RowParallel,
9176) -> Result<(), String> {
9177 if engines.len() != matrix.ranks.len() {
9178 return Err(format!(
9179 "resident Step BF16 row rank count {} != runtime rank count {}",
9180 matrix.ranks.len(),
9181 engines.len(),
9182 ));
9183 }
9184 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
9185 if matrix.canonical_chunk_cols != canonical_cols {
9186 return Err(format!(
9187 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
9188 matrix.canonical_chunk_cols
9189 ));
9190 }
9191 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
9192 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
9193 if blocks.len() != blocks_per_rank {
9194 return Err(format!(
9195 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
9196 blocks.len()
9197 ));
9198 }
9199 let device = engine.ctx().ordinal();
9200 for (block, resident) in blocks.iter().enumerate() {
9201 if resident.weight.ordinal() != device
9202 || resident.in_features != canonical_cols
9203 || resident.out_features != matrix.out_features
9204 {
9205 return Err(format!(
9206 "resident Step BF16 row rank {rank} block {block} has inconsistent \
9207 device or geometry"
9208 ));
9209 }
9210 }
9211 }
9212 Ok(())
9213}
9214
9215fn validate_replicated_device_rows(
9216 engines: &[Engine],
9217 rows: &ResidentReplicatedDeviceRows,
9218) -> Result<(), String> {
9219 let rank_lengths = rows
9220 .ranks
9221 .iter()
9222 .map(|rank_rows| rank_rows.len())
9223 .collect::<Vec<_>>();
9224 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
9225 if rows
9226 .ranks
9227 .iter()
9228 .zip(engines)
9229 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
9230 {
9231 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
9232 }
9233 Ok(())
9234}
9235
9236fn replicated_device_row_values(
9237 tokens: usize,
9238 width: usize,
9239 expected_ranks: usize,
9240 rank_lengths: &[usize],
9241) -> Result<usize, String> {
9242 let values = tokens
9243 .checked_mul(width)
9244 .ok_or("replicated device row size overflow")?;
9245 if tokens == 0
9246 || width == 0
9247 || expected_ranks == 0
9248 || rank_lengths.len() != expected_ranks
9249 || rank_lengths.iter().any(|&rank_len| rank_len != values)
9250 {
9251 return Err(format!(
9252 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
9253 tokens,
9254 width,
9255 rank_lengths.len(),
9256 expected_ranks
9257 ));
9258 }
9259 Ok(values)
9260}
9261
9262fn replicated_device_row_source_values(
9263 tokens: usize,
9264 width: usize,
9265 source_len: usize,
9266 source_device: usize,
9267 root_device: usize,
9268) -> Result<usize, String> {
9269 let values = tokens
9270 .checked_mul(width)
9271 .ok_or("replicated device row size overflow")?;
9272 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
9273 return Err(format!(
9274 "replicated device row source has inconsistent geometry/device \
9275 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
9276 ));
9277 }
9278 Ok(values)
9279}
9280
9281#[allow(clippy::manual_is_multiple_of)] fn bf16_column_shard(
9283 matrix: Bf16Matrix<'_>,
9284 tp: usize,
9285 rank: usize,
9286) -> Result<Bf16Matrix<'_>, String> {
9287 matrix.validate()?;
9288 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
9289 return Err(format!(
9290 "invalid BF16 column shard out={} TP={tp} rank={rank}",
9291 matrix.out_features
9292 ));
9293 }
9294 let local_out = matrix.out_features / tp;
9295 let row_bytes = matrix.in_features * 2;
9296 let start = rank * local_out * row_bytes;
9297 Ok(Bf16Matrix {
9298 bytes: &matrix.bytes[start..start + local_out * row_bytes],
9299 out_features: local_out,
9300 in_features: matrix.in_features,
9301 })
9302}
9303
9304#[allow(clippy::manual_is_multiple_of)] fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
9306 matrix.validate()?;
9307 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
9308 return Err(format!(
9309 "invalid BF16 row shard in={} TP={tp} rank={rank}",
9310 matrix.in_features
9311 ));
9312 }
9313 let local_in = matrix.in_features / tp;
9314 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
9315 for row in 0..matrix.out_features {
9316 let start = (row * matrix.in_features + rank * local_in) * 2;
9317 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
9318 }
9319 Ok(bytes)
9320}
9321
9322fn bf16_row_block(
9323 matrix: Bf16Matrix<'_>,
9324 col_start: usize,
9325 block_cols: usize,
9326) -> Result<Vec<u8>, String> {
9327 matrix.validate()?;
9328 let col_end = col_start
9329 .checked_add(block_cols)
9330 .ok_or("BF16 row block column overflow")?;
9331 if block_cols == 0 || col_end > matrix.in_features {
9332 return Err(format!(
9333 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
9334 matrix.in_features
9335 ));
9336 }
9337 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
9338 for row in 0..matrix.out_features {
9339 let start = (row * matrix.in_features + col_start) * 2;
9340 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
9341 }
9342 Ok(bytes)
9343}
9344
9345fn run_resident_bank_expert(
9346 engine: &Engine,
9347 bank: &ResidentE4m3ExpertBankRank,
9348 local_expert: usize,
9349 activations: &[f32],
9350 tokens: usize,
9351) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9352 let _main = engine.gpu.enter_main()?;
9353 if bank.k_blocks.is_some() {
9354 return Err("block-major TP row bank requires canonical block execution".into());
9355 }
9356 let local_count = bank.expert_range.end - bank.expert_range.start;
9357 if local_expert >= local_count {
9358 return Err(format!(
9359 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
9360 bank.expert_range
9361 )
9362 .into());
9363 }
9364 validate_activations(activations, tokens, bank.in_features)?;
9365 let activations = engine.htod(activations)?;
9366 let weight = bank
9367 .codes
9368 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9369 let scales = bank
9370 .scales
9371 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9372 let input = activations.slice(0..activations.len());
9373 let output = engine.qmatvec_mmq_fp8_blk_view(
9374 &weight,
9375 &scales,
9376 &input,
9377 tokens,
9378 bank.in_features,
9379 bank.out_features,
9380 )?;
9381 engine.dtoh(&output)
9382}
9383
9384fn run_resident_bank_expert_block(
9385 engine: &Engine,
9386 bank: &ResidentE4m3ExpertBankRank,
9387 local_expert: usize,
9388 block: usize,
9389 activations: &[f32],
9390) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9391 let _main = engine.gpu.enter_main()?;
9392 let local_count = bank.expert_range.end - bank.expert_range.start;
9393 if local_expert >= local_count {
9394 return Err(format!(
9395 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9396 bank.expert_range
9397 )
9398 .into());
9399 }
9400 let blocks = bank
9401 .k_blocks
9402 .ok_or("TP row bank is not packed in native K-block order")?;
9403 if block >= blocks {
9404 return Err(format!("TP row block {block} outside 0..{blocks}").into());
9405 }
9406 validate_activations(activations, 1, FP8_BLOCK)?;
9407 let block_code_stride = bank.out_features * FP8_BLOCK;
9408 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9409 if bank.in_features != blocks * FP8_BLOCK
9410 || bank.code_stride != blocks * block_code_stride
9411 || bank.scale_stride != blocks * block_scale_stride
9412 {
9413 return Err("TP row bank block-major geometry is inconsistent".into());
9414 }
9415
9416 let expert_code_start = local_expert * bank.code_stride;
9417 let expert_scale_start = local_expert * bank.scale_stride;
9418 let weight = bank.codes.slice(
9419 expert_code_start + block * block_code_stride
9420 ..expert_code_start + (block + 1) * block_code_stride,
9421 );
9422 let scales = bank.scales.slice(
9423 expert_scale_start + block * block_scale_stride
9424 ..expert_scale_start + (block + 1) * block_scale_stride,
9425 );
9426 let activations = engine.htod(activations)?;
9427 let input = activations.slice(0..activations.len());
9428 let output = engine.qmatvec_mmq_fp8_blk_view(
9429 &weight,
9430 &scales,
9431 &input,
9432 1,
9433 FP8_BLOCK,
9434 bank.out_features,
9435 )?;
9436 engine.dtoh(&output)
9437}
9438
9439fn run_resident_bank_expert_device(
9440 engine: &Engine,
9441 bank: &ResidentE4m3ExpertBankRank,
9442 local_expert: usize,
9443 activations: &CudaSlice<f32>,
9444 tokens: usize,
9445) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9446 let _main = engine.gpu.enter_main()?;
9447 if bank.k_blocks.is_some() {
9448 return Err("block-major TP row bank requires canonical block execution".into());
9449 }
9450 let local_count = bank.expert_range.end - bank.expert_range.start;
9451 if local_expert >= local_count {
9452 return Err(format!(
9453 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9454 bank.expert_range
9455 )
9456 .into());
9457 }
9458 let expected = tokens
9459 .checked_mul(bank.in_features)
9460 .ok_or("native TP activation size overflow")?;
9461 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
9462 return Err(format!(
9463 "native TP activation len/device {}/{} != expected {expected}/{}",
9464 activations.len(),
9465 activations.ordinal(),
9466 engine.ctx().ordinal()
9467 )
9468 .into());
9469 }
9470 let weight = bank
9471 .codes
9472 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9473 let scales = bank
9474 .scales
9475 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9476 let input = activations.slice(0..activations.len());
9477 engine.qmatvec_mmq_fp8_blk_view(
9478 &weight,
9479 &scales,
9480 &input,
9481 tokens,
9482 bank.in_features,
9483 bank.out_features,
9484 )
9485}
9486
9487fn run_resident_bank_expert_block_device(
9488 engine: &Engine,
9489 bank: &ResidentE4m3ExpertBankRank,
9490 local_expert: usize,
9491 block: usize,
9492 activations: &cudarc::driver::CudaView<'_, f32>,
9493) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9494 let _main = engine.gpu.enter_main()?;
9495 let local_count = bank.expert_range.end - bank.expert_range.start;
9496 if local_expert >= local_count {
9497 return Err(format!(
9498 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9499 bank.expert_range
9500 )
9501 .into());
9502 }
9503 let blocks = bank
9504 .k_blocks
9505 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
9506 if block >= blocks {
9507 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
9508 }
9509 let activation_device = activations.stream().context().ordinal();
9510 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
9511 return Err(format!(
9512 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
9513 activations.len(),
9514 activation_device,
9515 engine.ctx().ordinal()
9516 )
9517 .into());
9518 }
9519 let block_code_stride = bank.out_features * FP8_BLOCK;
9520 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9521 if bank.in_features != blocks * FP8_BLOCK
9522 || bank.code_stride != blocks * block_code_stride
9523 || bank.scale_stride != blocks * block_scale_stride
9524 {
9525 return Err("native TP row bank block-major geometry is inconsistent".into());
9526 }
9527 let expert_code_start = local_expert * bank.code_stride;
9528 let expert_scale_start = local_expert * bank.scale_stride;
9529 let weight = bank.codes.slice(
9530 expert_code_start + block * block_code_stride
9531 ..expert_code_start + (block + 1) * block_code_stride,
9532 );
9533 let scales = bank.scales.slice(
9534 expert_scale_start + block * block_scale_stride
9535 ..expert_scale_start + (block + 1) * block_scale_stride,
9536 );
9537 engine.qmatvec_mmq_fp8_blk_view(
9538 &weight,
9539 &scales,
9540 activations,
9541 1,
9542 FP8_BLOCK,
9543 bank.out_features,
9544 )
9545}
9546
9547pub(crate) fn grant_peer_access(
9566 accessor: &Engine,
9567 owner: &Engine,
9568 label: &str,
9569) -> Result<(), Box<dyn std::error::Error>> {
9570 let (a_dev, o_dev) = (accessor.ctx().ordinal(), owner.ctx().ordinal());
9571 let mut can_access = 0;
9572 unsafe {
9573 cudarc::driver::sys::cuDeviceCanAccessPeer(
9574 &mut can_access,
9575 accessor.ctx().cu_device(),
9576 owner.ctx().cu_device(),
9577 )
9578 .result()?;
9579 }
9580 if can_access == 0 {
9581 return Err(
9582 format!("{label} requires P2P, but dev{a_dev} cannot access dev{o_dev}").into(),
9583 );
9584 }
9585 accessor.ctx().bind_to_thread()?;
9586 let rc = unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(owner.ctx().cu_ctx(), 0) };
9587 use cudarc::driver::sys::cudaError_enum as E;
9588 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
9589 return Err(format!(
9590 "{label} cuCtxEnablePeerAccess(dev{a_dev} -> dev{o_dev}) failed: {rc:?}"
9591 )
9592 .into());
9593 }
9594 let device = cudarc::driver::result::device::get(o_dev as i32)?;
9595 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
9596 unsafe {
9597 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
9598 }
9599 let desc = cudarc::driver::sys::CUmemAccessDesc {
9600 location: cudarc::driver::sys::CUmemLocation {
9601 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
9602 id: a_dev as i32,
9603 },
9604 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
9605 };
9606 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
9607 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
9608 return Err(format!(
9609 "{label} cuMemPoolSetAccess(dev{o_dev} pool -> dev{a_dev}) failed: {rc:?}"
9610 )
9611 .into());
9612 }
9613 Ok(())
9614}
9615
9616fn configure_native_p2p(
9617 ranks: &[Engine],
9618 devices: &[usize],
9619) -> Result<(), Box<dyn std::error::Error>> {
9620 if ranks.len() != devices.len() || ranks.len() < 2 {
9621 return Err("native TP P2P setup requires matching multi-rank devices".into());
9622 }
9623 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
9624 if engine.ctx().ordinal() != device {
9625 return Err(format!(
9626 "native TP rank {rank} context device {} != requested device {device}",
9627 engine.ctx().ordinal()
9628 )
9629 .into());
9630 }
9631 }
9632
9633 for src in 0..ranks.len() {
9634 for dst in 0..ranks.len() {
9635 if src == dst {
9636 continue;
9637 }
9638 grant_peer_access(&ranks[src], &ranks[dst], "native TP")?;
9639 }
9640 }
9641
9642 for src in 0..ranks.len() {
9643 for dst in 0..ranks.len() {
9644 if src == dst {
9645 continue;
9646 }
9647 for &words in NATIVE_P2P_PROBE_WORDS {
9648 let expected = (0..words)
9649 .map(|index| {
9650 (index as u32)
9651 .wrapping_mul(0x9e37_79b9)
9652 .wrapping_add(((src as u32) << 16) | dst as u32)
9653 })
9654 .collect::<Vec<_>>();
9655 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
9656 let source = ranks[src].htod_u32_v(&expected)?;
9657 let mut destination = ranks[dst].htod_u32_v(&poison)?;
9658 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
9659 let actual = ranks[dst].dtoh_u32(&destination)?;
9660 if actual != expected {
9661 let mismatches = actual
9662 .iter()
9663 .zip(&expected)
9664 .filter(|(actual, expected)| actual != expected)
9665 .count();
9666 return Err(format!(
9667 "native TP peer probe dev{}->dev{} failed at {} bytes: \
9668 {mismatches}/{} words differ",
9669 devices[src],
9670 devices[dst],
9671 words * std::mem::size_of::<u32>(),
9672 expected.len()
9673 )
9674 .into());
9675 }
9676 }
9677 }
9678 }
9679 ranks[0].ctx().bind_to_thread()?;
9680 eprintln!(
9681 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
9682 directions={} byte_ladder={:?} mismatches=0",
9683 ranks.len() * (ranks.len() - 1),
9684 NATIVE_P2P_PROBE_WORDS
9685 .iter()
9686 .map(|words| words * std::mem::size_of::<u32>())
9687 .collect::<Vec<_>>(),
9688 );
9689 Ok(())
9690}
9691
9692fn validate_activations(
9693 activations: &[f32],
9694 tokens: usize,
9695 in_features: usize,
9696) -> Result<(), String> {
9697 let expected = tokens
9698 .checked_mul(in_features)
9699 .ok_or_else(|| "activation size overflow".to_string())?;
9700 if activations.len() != expected {
9701 return Err(format!(
9702 "activation count {} != {tokens}x{in_features} ({expected})",
9703 activations.len()
9704 ));
9705 }
9706 if !activations.iter().all(|value| value.is_finite()) {
9707 return Err("activations contain a non-finite value".to_string());
9708 }
9709 Ok(())
9710}
9711
9712fn column_shard(
9713 matrix: E4m3BlockMatrix<'_>,
9714 tp: usize,
9715 rank: usize,
9716) -> Result<E4m3BlockMatrix<'_>, String> {
9717 let local_out = matrix.out_features / tp;
9718 let row_start = rank * local_out;
9719 let code_start = row_start * matrix.in_features;
9720 let code_end = code_start + local_out * matrix.in_features;
9721 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9722 let local_scale_rows = local_out / FP8_BLOCK;
9723 let scale_start = rank * local_scale_rows * scale_cols;
9724 let scale_end = scale_start + local_scale_rows * scale_cols;
9725 Ok(E4m3BlockMatrix {
9726 codes: &matrix.codes[code_start..code_end],
9727 scales: &matrix.scales[scale_start..scale_end],
9728 out_features: local_out,
9729 in_features: matrix.in_features,
9730 })
9731}
9732
9733fn row_shard(
9734 matrix: E4m3BlockMatrix<'_>,
9735 tp: usize,
9736 rank: usize,
9737) -> Result<(Vec<u8>, Vec<f32>), String> {
9738 let local_in = matrix.in_features / tp;
9739 let col_start = rank * local_in;
9740 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
9741 for row in 0..matrix.out_features {
9742 let start = row * matrix.in_features + col_start;
9743 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
9744 }
9745
9746 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
9747 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9748 let local_scale_cols = local_in / FP8_BLOCK;
9749 let scale_col_start = rank * local_scale_cols;
9750 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
9751 for row in 0..scale_rows {
9752 let start = row * scale_cols + scale_col_start;
9753 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
9754 }
9755 Ok((codes, scales))
9756}
9757
9758fn activation_shard(
9759 activations: &[f32],
9760 tokens: usize,
9761 in_features: usize,
9762 tp: usize,
9763 rank: usize,
9764) -> Vec<f32> {
9765 let local_in = in_features / tp;
9766 let col_start = rank * local_in;
9767 let mut shard = Vec::with_capacity(tokens * local_in);
9768 for token in 0..tokens {
9769 let start = token * in_features + col_start;
9770 shard.extend_from_slice(&activations[start..start + local_in]);
9771 }
9772 shard
9773}
9774
9775#[derive(Clone, Copy)]
9795pub struct Nvfp4BlockMatrix<'a> {
9796 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
9800 pub in_features: usize,
9801}
9802
9803impl Nvfp4BlockMatrix<'_> {
9804 pub fn validate(&self) -> Result<(), String> {
9805 if self.in_features == 0 || self.out_features == 0 {
9806 return Err("NVFP4 matrix has a zero dimension".to_string());
9807 }
9808 if !self.in_features.is_multiple_of(64) {
9809 return Err(format!(
9810 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
9811 self.in_features
9812 ));
9813 }
9814 if self.codes.len() != self.out_features * self.in_features / 2 {
9815 return Err(format!(
9816 "NVFP4 code bytes {} != {}x{}/2",
9817 self.codes.len(),
9818 self.out_features,
9819 self.in_features
9820 ));
9821 }
9822 if self.scales.len() != self.out_features * self.in_features / 16 {
9823 return Err(format!(
9824 "NVFP4 scale bytes {} != {}x{}/16",
9825 self.scales.len(),
9826 self.out_features,
9827 self.in_features
9828 ));
9829 }
9830 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9831 return Err(format!(
9832 "NVFP4 macro scale {} is not finite-positive",
9833 self.macro_scale
9834 ));
9835 }
9836 Ok(())
9837 }
9838}
9839
9840#[derive(Clone, Copy)]
9842pub struct Nvfp4ExpertBank<'a> {
9843 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9847 pub out_features: usize,
9848 pub in_features: usize,
9849}
9850
9851impl Nvfp4ExpertBank<'_> {
9852 pub fn validate(&self) -> Result<(), String> {
9853 if self.expert_count == 0 {
9854 return Err("NVFP4 expert bank is empty".to_string());
9855 }
9856 if self.macros.len() != self.expert_count {
9857 return Err(format!(
9858 "NVFP4 bank macros {} != expert count {}",
9859 self.macros.len(),
9860 self.expert_count
9861 ));
9862 }
9863 self.expert(0).map(|_| ())
9864 }
9865
9866 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9867 if expert >= self.expert_count {
9868 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9869 }
9870 let code_stride = self.out_features * self.in_features / 2;
9871 let scale_stride = self.out_features * self.in_features / 16;
9872 if self.codes.len() != self.expert_count * code_stride
9873 || self.scales.len() != self.expert_count * scale_stride
9874 {
9875 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9876 }
9877 let matrix = Nvfp4BlockMatrix {
9878 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9879 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9880 macro_scale: self.macros[expert],
9881 out_features: self.out_features,
9882 in_features: self.in_features,
9883 };
9884 matrix.validate()?;
9885 Ok(matrix)
9886 }
9887}
9888
9889pub struct ResidentNvfp4Rank {
9891 blocks: crate::CudaSlice<u8>,
9892 macro_scale: f32,
9893 out_features: usize,
9894 in_features: usize,
9895 row_bytes: usize,
9896}
9897
9898pub struct ResidentNvfp4ColumnParallel {
9899 ranks: Vec<ResidentNvfp4Rank>,
9900 pub out_features: usize,
9901 pub in_features: usize,
9902}
9903
9904pub struct ResidentNvfp4RowParallel {
9905 ranks: Vec<ResidentNvfp4Rank>,
9906 pub out_features: usize,
9907 pub in_features: usize,
9908}
9909
9910pub struct ResidentTpNvfp4Expert {
9911 gate: ResidentNvfp4ColumnParallel,
9912 up: ResidentNvfp4ColumnParallel,
9913 down: ResidentNvfp4RowParallel,
9914 pub input_width: usize,
9915 pub expert_width: usize,
9916}
9917
9918pub struct ResidentNvfp4ColumnBankRank {
9922 bank: crate::CudaSlice<u8>,
9926 expert_bytes: usize,
9927 local_out: usize,
9928 in_features: usize,
9929 row_bytes: usize,
9930 slot_major: bool,
9937}
9938
9939impl ResidentNvfp4ColumnBankRank {
9940 fn host_canonical_expert(
9945 &self,
9946 engine: &Engine,
9947 expert: usize,
9948 activations: &crate::CudaSlice<f32>,
9949 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9950 let w = self.expert(expert);
9951 if self.slot_major {
9952 engine.qmatvec_nvfp4_fast_v2(
9953 &w,
9954 activations,
9955 1,
9956 self.in_features,
9957 self.local_out,
9958 self.row_bytes,
9959 )
9960 } else {
9961 engine.qmatvec_nvfp4_fast(
9962 &w,
9963 activations,
9964 1,
9965 self.in_features,
9966 self.local_out,
9967 self.row_bytes,
9968 )
9969 }
9970 }
9971
9972 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9973 self.bank
9974 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9975 }
9976}
9977
9978pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9984
9985pub struct ResidentNvfp4RowBankRank {
9986 bank: crate::CudaSlice<u8>,
9988 expert_bytes: usize,
9989 device_rank: usize, out_features: usize,
9991 local_in: usize,
9992 row_bytes: usize,
9993 slot_major: bool,
9995}
9996
9997impl ResidentNvfp4RowBankRank {
9998 fn host_canonical_expert(
10001 &self,
10002 engine: &Engine,
10003 expert: usize,
10004 activations: &crate::CudaSlice<f32>,
10005 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10006 let w = self.expert(expert);
10007 if self.slot_major {
10008 engine.qmatvec_nvfp4_fast_v2(
10009 &w,
10010 activations,
10011 1,
10012 self.local_in,
10013 self.out_features,
10014 self.row_bytes,
10015 )
10016 } else {
10017 engine.qmatvec_nvfp4_fast(
10018 &w,
10019 activations,
10020 1,
10021 self.local_in,
10022 self.out_features,
10023 self.row_bytes,
10024 )
10025 }
10026 }
10027
10028 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
10029 self.bank
10030 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
10031 }
10032}
10033
10034impl ResidentNvfp4TensorParallel {
10035 pub(crate) fn device_workspace_handle(
10036 &self,
10037 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
10038 &self.device_workspace
10039 }
10040}
10041
10042pub struct ResidentNvfp4TensorParallel {
10043 gate: Vec<ResidentNvfp4ColumnBankRank>,
10044 up: Vec<ResidentNvfp4ColumnBankRank>,
10045 down: Vec<ResidentNvfp4RowBankRank>,
10046 macros_gate: Vec<f32>,
10047 macros_up: Vec<f32>,
10048 macros_down: Vec<f32>,
10049 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
10053 macros_up_dev: Vec<crate::CudaSlice<f32>>,
10054 macros_down_dev: Vec<crate::CudaSlice<f32>>,
10055 pub expert_count: usize,
10056 pub input_width: usize,
10057 pub expert_width: usize,
10058 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
10061 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
10065 pub(crate) ep2: bool,
10069}
10070
10071struct RoutesGraph {
10078 exec: cudarc::driver::sys::CUgraphExec,
10079 parent: cudarc::driver::sys::CUgraph,
10080 _children: Vec<cudarc::driver::CudaGraph>,
10081}
10082unsafe impl Send for RoutesGraph {}
10085
10086impl Drop for RoutesGraph {
10087 fn drop(&mut self) {
10088 unsafe {
10089 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
10090 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
10091 }
10092 }
10093}
10094
10095impl Nvfp4DeviceRoutesWorkspace {
10096 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
10097 self.in_stage_e.as_ref()
10098 }
10099 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
10100 self.in_stage_e.as_mut()
10101 }
10102 #[allow(dead_code)] pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
10104 self.out_stage_e.as_mut()
10105 }
10106 pub(crate) fn arm_stages(
10108 &mut self,
10109 e: &Engine,
10110 width: usize,
10111 n_sel: usize,
10112 ) -> Result<(), Box<dyn std::error::Error>> {
10113 let _main = e.gpu.enter_main()?;
10114 if self.in_stage_e.is_none() {
10115 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
10116 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
10117 }
10118 if self.dev_route_e.is_none() {
10119 self.dev_route_e = Some((
10120 e.htod_i32(&vec![0i32; n_sel])?,
10121 e.htod(&vec![0.0f32; n_sel])?,
10122 ));
10123 }
10124 Ok(())
10125 }
10126
10127 pub(crate) fn in_and_out_stages_mut(
10129 &mut self,
10130 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
10131 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
10132 (Some(input), Some(output)) => Some((input, output)),
10133 _ => None,
10134 }
10135 }
10136 pub(crate) fn dev_route_e_mut(
10137 &mut self,
10138 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
10139 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
10140 }
10141}
10142
10143pub struct Nvfp4DeviceRoutesWorkspace {
10144 gate_out: Vec<crate::CudaSlice<f32>>,
10147 up_out: Vec<crate::CudaSlice<f32>>,
10148 act_q: Vec<crate::CudaSlice<i8>>,
10149 act_d: Vec<crate::CudaSlice<f32>>,
10150 sel: Vec<crate::CudaSlice<i32>>,
10151 partial: Vec<crate::CudaSlice<f32>>,
10152 accumulator: Vec<crate::CudaSlice<f32>>,
10153 combine_w: Vec<crate::CudaSlice<f32>>,
10155 route_w: Vec<crate::CudaSlice<f32>>,
10158 in_q: Vec<crate::CudaSlice<i8>>,
10161 in_d: Vec<crate::CudaSlice<f32>>,
10162 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
10166 prestaged: bool,
10169 rank1_routed: bool,
10172 fence_flags_raw: u64,
10176 fence_ticket: u32,
10177 ev_input: Option<(CudaEvent, usize)>,
10179 in_stage_e: Option<crate::CudaSlice<f32>>,
10182 out_stage_e: Option<crate::CudaSlice<f32>>,
10183 routes_graph: Option<RoutesGraph>,
10184 raw_dev_route_e: Option<(u64, u64)>,
10186 raw_combine: Option<(u64, u64, u64, u64)>,
10187 raw_input: Vec<u64>,
10188 raw_sel: Vec<u64>,
10189 raw_route_w: Vec<u64>,
10190 remote: crate::CudaSlice<f32>,
10191 combined: crate::CudaSlice<f32>,
10192 n_sel: usize,
10193 input: Vec<crate::CudaSlice<f32>>,
10197 ev_rank: Vec<CudaEvent>,
10198 ev_done: Option<CudaEvent>,
10199 ev_entry: Option<(CudaEvent, usize)>,
10200}
10201
10202struct ResidentNvfp4EpRank {
10204 gate: crate::CudaSlice<u8>,
10205 up: crate::CudaSlice<u8>,
10206 down: crate::CudaSlice<u8>,
10207 gate_expert_bytes: usize,
10208 down_expert_bytes: usize,
10209 macros_gate: crate::CudaSlice<f32>,
10210 macros_up: crate::CudaSlice<f32>,
10211 macros_down: crate::CudaSlice<f32>,
10212 expert_range: Range<usize>,
10213}
10214
10215struct Nvfp4EpDeviceWorkspace {
10216 input: Vec<crate::CudaSlice<f32>>,
10217 input_bf16: Vec<crate::CudaSlice<u8>>,
10218 input_q8: Vec<crate::CudaSlice<i8>>,
10219 input_q8_scales: Vec<crate::CudaSlice<f32>>,
10220 sel: Vec<crate::CudaSlice<i32>>,
10221 token_rows: Vec<crate::CudaSlice<i32>>,
10222 global_pairs: Vec<crate::CudaSlice<i32>>,
10223 route_w: Vec<crate::CudaSlice<f32>>,
10224 gate_out: Vec<crate::CudaSlice<f32>>,
10225 up_out: Vec<crate::CudaSlice<f32>>,
10226 activation_bf16: Vec<crate::CudaSlice<u8>>,
10227 activation_q8: Vec<crate::CudaSlice<i8>>,
10228 activation_q8_scales: Vec<crate::CudaSlice<f32>>,
10229 slot_rows: crate::CudaSlice<f32>,
10230 slot_rows_raw: u64,
10231 route_weights: crate::CudaSlice<f32>,
10232 graph_input: crate::CudaSlice<f32>,
10233 graph_output: crate::CudaSlice<f32>,
10234 graph_routes: Option<(u64, u64)>,
10235 graphs: Vec<Option<RoutesGraph>>,
10236 ev_entry: CudaEvent,
10237 ev_entry_device: usize,
10238 ev_rank: Vec<CudaEvent>,
10239 phase_events: Option<Nvfp4EpPhaseEvents>,
10240 capacity_tokens: usize,
10241 experts_per_token: usize,
10242}
10243
10244struct Nvfp4EpPhaseEvents {
10245 head: Vec<CudaEvent>,
10246 copy_done: Vec<CudaEvent>,
10247 gate_up_done: Vec<CudaEvent>,
10248 activation_done: Vec<CudaEvent>,
10249 down_done: Vec<CudaEvent>,
10250}
10251
10252pub(crate) const NVFP4_EP_DEVICE_BATCH_CAP: usize = 128;
10253pub(crate) const NVFP4_EP_DEVICE_ROUTER_BATCH_CAP: usize = 32;
10254pub(crate) const NVFP4_EP_Q8_BATCH_CAP: usize = 32;
10255const NVFP4_EP_GRAPH_BATCH_CAP: usize = 1;
10256
10257fn nvfp4_ep_active_input_values(
10258 input_values: usize,
10259 tokens: usize,
10260 input_width: usize,
10261) -> Result<usize, String> {
10262 if !(1..=NVFP4_EP_DEVICE_BATCH_CAP).contains(&tokens) {
10263 return Err(format!(
10264 "W4A16 NVFP4 device EP batch {tokens} is outside 1..={NVFP4_EP_DEVICE_BATCH_CAP}"
10265 ));
10266 }
10267 let active_values = tokens
10268 .checked_mul(input_width)
10269 .ok_or("W4A16 NVFP4 device EP active input size overflows usize")?;
10270 if input_values < active_values {
10271 return Err(format!(
10272 "W4A16 NVFP4 device EP input {input_values} is smaller than active \
10273 tokens {tokens} x width {input_width} ({active_values})"
10274 ));
10275 }
10276 Ok(active_values)
10277}
10278
10279pub struct ResidentNvfp4ExpertParallel {
10280 ranks: Vec<ResidentNvfp4EpRank>,
10281 macros_gate: Vec<f32>,
10282 macros_up: Vec<f32>,
10283 macros_down: Vec<f32>,
10284 pub expert_count: usize,
10285 pub input_width: usize,
10286 pub expert_width: usize,
10287 gate_row_bytes: usize,
10288 down_row_bytes: usize,
10289 device_workspace: std::sync::Mutex<Option<Nvfp4EpDeviceWorkspace>>,
10290}
10291
10292fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
10293 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
10294 matrix.codes,
10295 matrix.scales,
10296 matrix.out_features,
10297 matrix.in_features,
10298 )
10299}
10300
10301fn nvfp4_row_bytes(in_features: usize) -> usize {
10302 in_features / 64 * 36 }
10304
10305pub(crate) fn fuse_rope_append_on() -> bool {
10311 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10312 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
10313}
10314
10315pub(crate) fn no_local_shadow_on() -> bool {
10316 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10317 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
10318}
10319
10320pub fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
10336 assert_eq!(
10342 in_features % 64,
10343 0,
10344 "v2 permute needs whole 64-element superblocks, got in_features={in_features}"
10345 );
10346 let row_bytes = nvfp4_row_bytes(in_features);
10347 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
10348 let n_slots = in_features / 32;
10349 let mut out = Vec::with_capacity(v1.len());
10350 for row in 0..out_features {
10351 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
10352 for g in 0..n_slots {
10353 let (sblk, h) = (g / 2, g % 2);
10354 let b = &r[sblk * 36..sblk * 36 + 36];
10355 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
10356 }
10357 for g in 0..n_slots {
10358 let (sblk, h) = (g / 2, g % 2);
10359 let b = &r[sblk * 36..sblk * 36 + 36];
10360 out.push(b[2 * h]);
10361 out.push(b[2 * h + 1]);
10362 }
10363 }
10364 out
10365}
10366
10367fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
10371 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
10372 let v1 = nvfp4_repack_matrix(matrix);
10373 if slot_major {
10374 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
10375 } else {
10376 v1
10377 }
10378}
10379
10380#[allow(clippy::manual_is_multiple_of)] fn nvfp4_column_shard<'a>(
10384 matrix: Nvfp4BlockMatrix<'a>,
10385 tp: usize,
10386 rank: usize,
10387) -> Result<Nvfp4BlockMatrix<'a>, String> {
10388 if matrix.out_features % tp != 0 {
10389 return Err(format!(
10390 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
10391 matrix.out_features
10392 ));
10393 }
10394 let local_out = matrix.out_features / tp;
10395 let code_row = matrix.in_features / 2;
10396 let scale_row = matrix.in_features / 16;
10397 Ok(Nvfp4BlockMatrix {
10398 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
10399 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
10400 macro_scale: matrix.macro_scale,
10401 out_features: local_out,
10402 in_features: matrix.in_features,
10403 })
10404}
10405
10406#[allow(clippy::manual_is_multiple_of)] fn nvfp4_row_shard(
10410 matrix: Nvfp4BlockMatrix<'_>,
10411 tp: usize,
10412 rank: usize,
10413) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
10414 if matrix.in_features % tp != 0 {
10415 return Err(format!(
10416 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
10417 matrix.in_features
10418 ));
10419 }
10420 let local_in = matrix.in_features / tp;
10421 if !local_in.is_multiple_of(64) {
10422 return Err(format!(
10423 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
10424 ));
10425 }
10426 let code_row = matrix.in_features / 2;
10427 let scale_row = matrix.in_features / 16;
10428 let local_code = local_in / 2;
10429 let local_scale = local_in / 16;
10430 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
10431 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
10432 for row in 0..matrix.out_features {
10433 let code_start = row * code_row + rank * local_code;
10434 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
10435 let scale_start = row * scale_row + rank * local_scale;
10436 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
10437 }
10438 Ok((codes, scales, local_in))
10439}
10440
10441fn run_rank_nvfp4(
10445 engine: &Engine,
10446 matrix: Nvfp4BlockMatrix<'_>,
10447 activations: &[f32],
10448 tokens: usize,
10449) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10450 matrix.validate()?;
10451 validate_activations(activations, tokens, matrix.in_features)?;
10452 let _main = engine.gpu.enter_main()?;
10453 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
10454 let activations = engine.htod(activations)?;
10455 let output = engine.qmatvec_nvfp4_fast(
10456 &blocks.slice(0..blocks.len()),
10457 &activations,
10458 tokens,
10459 matrix.in_features,
10460 matrix.out_features,
10461 nvfp4_row_bytes(matrix.in_features),
10462 )?;
10463 engine.dtoh(&output)
10464}
10465
10466fn upload_rank_nvfp4(
10467 engine: &Engine,
10468 matrix: Nvfp4BlockMatrix<'_>,
10469) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
10470 matrix.validate()?;
10471 let _main = engine.gpu.enter_main()?;
10472 Ok(ResidentNvfp4Rank {
10473 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
10474 macro_scale: matrix.macro_scale,
10475 out_features: matrix.out_features,
10476 in_features: matrix.in_features,
10477 row_bytes: nvfp4_row_bytes(matrix.in_features),
10478 })
10479}
10480
10481fn run_resident_rank_nvfp4(
10482 engine: &Engine,
10483 rank: &ResidentNvfp4Rank,
10484 activations: &[f32],
10485 tokens: usize,
10486) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10487 validate_activations(activations, tokens, rank.in_features)?;
10488 let _main = engine.gpu.enter_main()?;
10489 let activations = engine.htod(activations)?;
10490 let output = engine.qmatvec_nvfp4_fast(
10491 &rank.blocks.slice(0..rank.blocks.len()),
10492 &activations,
10493 tokens,
10494 rank.in_features,
10495 rank.out_features,
10496 rank.row_bytes,
10497 )?;
10498 engine.dtoh(&output)
10499}
10500
10501fn apply_macro(values: &mut [f32], macro_scale: f32) {
10502 for value in values.iter_mut() {
10503 *value *= macro_scale;
10504 }
10505}
10506
10507impl TpE4m3HostBounce {
10508 pub fn full_nvfp4(
10510 &self,
10511 matrix: Nvfp4BlockMatrix<'_>,
10512 activations: &[f32],
10513 tokens: usize,
10514 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10515 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
10516 apply_macro(&mut output, matrix.macro_scale);
10517 Ok(output)
10518 }
10519
10520 pub fn column_parallel_nvfp4(
10523 &self,
10524 matrix: Nvfp4BlockMatrix<'_>,
10525 activations: &[f32],
10526 tokens: usize,
10527 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
10528 matrix.validate()?;
10529 validate_activations(activations, tokens, matrix.in_features)?;
10530 let tp = self.ranks.len();
10531 let local_out = matrix.out_features / tp;
10532 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10533 let mut rank_outputs = Vec::with_capacity(tp);
10534 for (rank_index, rank) in self.ranks.iter().enumerate() {
10535 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
10536 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
10537 let row_start = rank_index * local_out;
10538 for token in 0..tokens {
10539 gathered[token * matrix.out_features + row_start
10540 ..token * matrix.out_features + row_start + local_out]
10541 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10542 }
10543 rank_outputs.push(output);
10544 }
10545 apply_macro(&mut gathered, matrix.macro_scale);
10546 Ok(ColumnParallelResult {
10547 gathered,
10548 rank_outputs,
10549 })
10550 }
10551
10552 pub fn row_parallel_nvfp4(
10555 &self,
10556 matrix: Nvfp4BlockMatrix<'_>,
10557 activations: &[f32],
10558 tokens: usize,
10559 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
10560 matrix.validate()?;
10561 validate_activations(activations, tokens, matrix.in_features)?;
10562 let tp = self.ranks.len();
10563 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10564 let mut rank_partials = Vec::with_capacity(tp);
10565 for (rank_index, rank) in self.ranks.iter().enumerate() {
10566 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
10567 let local_activations =
10568 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10569 let shard = Nvfp4BlockMatrix {
10570 codes: &codes,
10571 scales: &scales,
10572 macro_scale: matrix.macro_scale,
10573 out_features: matrix.out_features,
10574 in_features: local_in,
10575 };
10576 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
10577 for (sum, value) in reduced.iter_mut().zip(&partial) {
10578 *sum += *value;
10579 }
10580 rank_partials.push(partial);
10581 }
10582 apply_macro(&mut reduced, matrix.macro_scale);
10583 Ok(RowParallelResult {
10584 reduced,
10585 rank_partials,
10586 })
10587 }
10588
10589 pub fn upload_expert_nvfp4(
10590 &self,
10591 gate: Nvfp4BlockMatrix<'_>,
10592 up: Nvfp4BlockMatrix<'_>,
10593 down: Nvfp4BlockMatrix<'_>,
10594 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
10595 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10596 return Err("NVFP4 TP expert gate/up dimensions differ".into());
10597 }
10598 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10599 return Err(format!(
10600 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
10601 down.out_features, down.in_features, gate.out_features, gate.in_features
10602 )
10603 .into());
10604 }
10605 let tp = self.ranks.len();
10606 let mut gate_ranks = Vec::with_capacity(tp);
10607 let mut up_ranks = Vec::with_capacity(tp);
10608 let mut down_ranks = Vec::with_capacity(tp);
10609 for (rank_index, engine) in self.ranks.iter().enumerate() {
10610 gate_ranks.push(upload_rank_nvfp4(
10611 engine,
10612 nvfp4_column_shard(gate, tp, rank_index)?,
10613 )?);
10614 up_ranks.push(upload_rank_nvfp4(
10615 engine,
10616 nvfp4_column_shard(up, tp, rank_index)?,
10617 )?);
10618 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
10619 down_ranks.push(upload_rank_nvfp4(
10620 engine,
10621 Nvfp4BlockMatrix {
10622 codes: &codes,
10623 scales: &scales,
10624 macro_scale: down.macro_scale,
10625 out_features: down.out_features,
10626 in_features: local_in,
10627 },
10628 )?);
10629 }
10630 Ok(ResidentTpNvfp4Expert {
10631 gate: ResidentNvfp4ColumnParallel {
10632 ranks: gate_ranks,
10633 out_features: gate.out_features,
10634 in_features: gate.in_features,
10635 },
10636 up: ResidentNvfp4ColumnParallel {
10637 ranks: up_ranks,
10638 out_features: up.out_features,
10639 in_features: up.in_features,
10640 },
10641 down: ResidentNvfp4RowParallel {
10642 ranks: down_ranks,
10643 out_features: down.out_features,
10644 in_features: down.in_features,
10645 },
10646 input_width: gate.in_features,
10647 expert_width: gate.out_features,
10648 })
10649 }
10650
10651 fn column_parallel_resident_nvfp4(
10652 &self,
10653 matrix: &ResidentNvfp4ColumnParallel,
10654 activations: &[f32],
10655 tokens: usize,
10656 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10657 validate_activations(activations, tokens, matrix.in_features)?;
10658 let local_out = matrix.out_features / self.ranks.len();
10659 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10660 let mut macro_scale = None;
10661 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10662 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
10663 let row_start = rank_index * local_out;
10664 for token in 0..tokens {
10665 gathered[token * matrix.out_features + row_start
10666 ..token * matrix.out_features + row_start + local_out]
10667 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10668 }
10669 macro_scale = Some(shard.macro_scale);
10670 }
10671 apply_macro(
10672 &mut gathered,
10673 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
10674 );
10675 Ok(gathered)
10676 }
10677
10678 fn row_parallel_resident_nvfp4(
10679 &self,
10680 matrix: &ResidentNvfp4RowParallel,
10681 activations: &[f32],
10682 tokens: usize,
10683 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10684 validate_activations(activations, tokens, matrix.in_features)?;
10685 let tp = self.ranks.len();
10686 let local_in = matrix.in_features / tp;
10687 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10688 let mut macro_scale = None;
10689 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10690 if shard.in_features != local_in {
10691 return Err(format!(
10692 "NVFP4 resident row shard in_features {} != expected {local_in}",
10693 shard.in_features
10694 )
10695 .into());
10696 }
10697 let local_activations =
10698 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10699 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
10700 for (sum, value) in reduced.iter_mut().zip(&partial) {
10701 *sum += *value;
10702 }
10703 macro_scale = Some(shard.macro_scale);
10704 }
10705 apply_macro(
10706 &mut reduced,
10707 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
10708 );
10709 Ok(reduced)
10710 }
10711
10712 pub fn run_expert_nvfp4(
10713 &self,
10714 expert: &ResidentTpNvfp4Expert,
10715 input: &[f32],
10716 tokens: usize,
10717 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10718 validate_activations(input, tokens, expert.input_width)?;
10719 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
10720 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
10721 let activated: Vec<f32> = gate
10722 .iter()
10723 .zip(&up)
10724 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
10725 .collect();
10726 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
10727 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
10728 }
10729
10730 #[allow(clippy::manual_is_multiple_of)] pub fn upload_tensor_parallel_nvfp4(
10733 &self,
10734 gate: Nvfp4ExpertBank<'_>,
10735 up: Nvfp4ExpertBank<'_>,
10736 down: Nvfp4ExpertBank<'_>,
10737 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
10738 gate.validate()?;
10739 up.validate()?;
10740 down.validate()?;
10741 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10742 return Err("NVFP4 TP gate/up/down expert counts differ".into());
10743 }
10744 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10745 return Err("NVFP4 TP gate/up dimensions differ".into());
10746 }
10747 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10748 return Err(format!(
10749 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
10750 down.out_features, down.in_features, gate.out_features, gate.in_features
10751 )
10752 .into());
10753 }
10754 let tp = self.ranks.len();
10755 if gate.out_features % tp != 0 {
10756 return Err(format!(
10757 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
10758 gate.out_features
10759 )
10760 .into());
10761 }
10762 if !down.in_features.is_multiple_of(NVFP4_CANONICAL_ROW_SHARDS)
10763 || !(down.in_features / NVFP4_CANONICAL_ROW_SHARDS).is_multiple_of(64)
10764 {
10765 return Err(format!(
10766 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
10767 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
10768 down.in_features
10769 )
10770 .into());
10771 }
10772 if tp > NVFP4_CANONICAL_ROW_SHARDS {
10773 return Err(format!(
10774 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
10775 ({NVFP4_CANONICAL_ROW_SHARDS})"
10776 )
10777 .into());
10778 }
10779
10780 let ep2 = step_nvfp4_ep2_on() && tp == 2;
10781 let slot_major = ep2 || bank_slot_major_on();
10786 eprintln!(
10794 "[nvfp4-bank] layout={} source={} tp={tp} experts={} in_f={} out_f={}",
10795 if slot_major {
10796 "slot-major"
10797 } else {
10798 "block-nvfp4-v1"
10799 },
10800 if ep2 {
10805 "ep2-always"
10806 } else {
10807 bank_slot_major_source().1
10808 },
10809 gate.expert_count,
10810 gate.in_features,
10811 gate.out_features
10812 );
10813 let mut gate_ranks = Vec::with_capacity(tp);
10814 let mut up_ranks = Vec::with_capacity(tp);
10815 let mut macros_gate_dev = Vec::with_capacity(tp);
10816 let mut macros_up_dev = Vec::with_capacity(tp);
10817 let mut macros_down_dev = Vec::with_capacity(tp);
10818 for (rank_index, engine) in self.ranks.iter().enumerate() {
10819 let _main = engine.gpu.enter_main()?;
10820 let mut gate_host: Vec<u8> = Vec::new();
10826 let mut up_host: Vec<u8> = Vec::new();
10827 let mut owned = 0usize;
10828 for expert in 0..gate.expert_count {
10829 if ep2 {
10830 if expert % 2 != rank_index {
10831 continue;
10832 }
10833 owned += 1;
10834 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10835 gate.expert(expert)?,
10836 slot_major,
10837 ));
10838 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10839 up.expert(expert)?,
10840 slot_major,
10841 ));
10842 } else {
10843 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
10844 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, slot_major));
10845 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
10846 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, slot_major));
10847 }
10848 }
10849 let bank_experts = if ep2 { owned } else { gate.expert_count };
10850 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
10851 let up_expert_bytes = up_host.len() / bank_experts.max(1);
10852 let local_out = if ep2 {
10853 gate.out_features
10854 } else {
10855 gate.out_features / tp
10856 };
10857 gate_ranks.push(ResidentNvfp4ColumnBankRank {
10858 bank: engine.htod_bytes(&gate_host)?,
10859 expert_bytes: gate_expert_bytes,
10860 local_out,
10861 in_features: gate.in_features,
10862 row_bytes: nvfp4_row_bytes(gate.in_features),
10863 slot_major,
10864 });
10865 up_ranks.push(ResidentNvfp4ColumnBankRank {
10866 bank: engine.htod_bytes(&up_host)?,
10867 expert_bytes: up_expert_bytes,
10868 local_out,
10869 in_features: up.in_features,
10870 row_bytes: nvfp4_row_bytes(up.in_features),
10871 slot_major,
10872 });
10873 macros_gate_dev.push(engine.htod(gate.macros)?);
10874 macros_up_dev.push(engine.htod(up.macros)?);
10875 macros_down_dev.push(engine.htod(down.macros)?);
10876 }
10877 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
10881 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
10882 let device_rank = shard_index % tp;
10883 let engine = &self.ranks[device_rank];
10884 let _main = engine.gpu.enter_main()?;
10885 let mut down_host: Vec<u8> = Vec::new();
10886 let mut owned = 0usize;
10887 for expert in 0..down.expert_count {
10888 let down_matrix = down.expert(expert)?;
10889 if ep2 {
10890 if expert % 2 != device_rank {
10893 continue;
10894 }
10895 owned += 1;
10896 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix, slot_major));
10897 } else {
10898 let (codes, scales, local_in) =
10899 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
10900 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10901 Nvfp4BlockMatrix {
10902 codes: &codes,
10903 scales: &scales,
10904 macro_scale: down_matrix.macro_scale,
10905 out_features: down_matrix.out_features,
10906 in_features: local_in,
10907 },
10908 slot_major,
10909 ));
10910 }
10911 }
10912 let bank_experts = if ep2 { owned } else { down.expert_count };
10913 let down_expert_bytes = down_host.len() / bank_experts.max(1);
10914 let local_in = if ep2 {
10915 down.in_features
10916 } else {
10917 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
10918 };
10919 down_ranks.push(ResidentNvfp4RowBankRank {
10920 bank: engine.htod_bytes(&down_host)?,
10921 expert_bytes: down_expert_bytes,
10922 device_rank,
10923 out_features: down.out_features,
10924 local_in,
10925 row_bytes: nvfp4_row_bytes(local_in),
10926 slot_major,
10927 });
10928 }
10929 Ok(ResidentNvfp4TensorParallel {
10930 gate: gate_ranks,
10931 up: up_ranks,
10932 down: down_ranks,
10933 macros_gate: gate.macros.to_vec(),
10934 macros_up: up.macros.to_vec(),
10935 macros_down: down.macros.to_vec(),
10936 macros_gate_dev,
10937 macros_up_dev,
10938 macros_down_dev,
10939 expert_count: gate.expert_count,
10940 input_width: gate.in_features,
10941 expert_width: gate.out_features,
10942 device_workspace: std::sync::Mutex::new(None),
10943 prime_tables: std::sync::Mutex::new(Vec::new()),
10944 ep2,
10945 })
10946 }
10947
10948 fn run_full_bank_expert_nvfp4(
10952 &self,
10953 ranks: &[ResidentNvfp4ColumnBankRank],
10954 macros: &[f32],
10955 expert: usize,
10956 input: &[f32],
10957 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10958 let owner = expert & 1;
10959 let slot = expert >> 1;
10960 let bank = ranks
10961 .get(owner)
10962 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
10963 let engine = &self.ranks[owner];
10964 let _main = engine.gpu.enter_main()?;
10965 let activations = engine.htod(input)?;
10966 let output = bank.host_canonical_expert(engine, slot, &activations)?;
10967 let mut out = engine.dtoh(&output)?;
10968 apply_macro(&mut out, macros[expert]);
10969 Ok(out)
10970 }
10971
10972 fn run_full_down_expert_nvfp4(
10975 &self,
10976 shards: &[ResidentNvfp4RowBankRank],
10977 macros: &[f32],
10978 expert: usize,
10979 input: &[f32],
10980 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10981 let owner = expert & 1;
10982 let slot = expert >> 1;
10983 let shard = shards
10984 .get(owner)
10985 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
10986 let engine = &self.ranks[owner];
10987 let _main = engine.gpu.enter_main()?;
10988 let activations = engine.htod(input)?;
10989 let output = shard.host_canonical_expert(engine, slot, &activations)?;
10990 let mut out = engine.dtoh(&output)?;
10991 apply_macro(&mut out, macros[expert]);
10992 Ok(out)
10993 }
10994
10995 fn run_column_bank_expert_nvfp4(
10996 &self,
10997 ranks: &[ResidentNvfp4ColumnBankRank],
10998 macros: &[f32],
10999 expert: usize,
11000 input: &[f32],
11001 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11002 let local_out = ranks
11003 .first()
11004 .ok_or("NVFP4 TP column bank has no ranks")?
11005 .local_out;
11006 let mut gathered = vec![0.0f32; local_out * ranks.len()];
11007 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
11008 let _main = engine.gpu.enter_main()?;
11009 let activations = engine.htod(input)?;
11010 let output = bank.host_canonical_expert(engine, expert, &activations)?;
11011 let output = engine.dtoh(&output)?;
11012 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
11013 }
11014 apply_macro(&mut gathered, macros[expert]);
11015 Ok(gathered)
11016 }
11017
11018 fn run_row_bank_expert_nvfp4(
11022 &self,
11023 shards: &[ResidentNvfp4RowBankRank],
11024 macros: &[f32],
11025 expert: usize,
11026 input: &[f32],
11027 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11028 let out_features = shards
11029 .first()
11030 .ok_or("NVFP4 TP row bank has no canonical shards")?
11031 .out_features;
11032 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
11033 let mut reduced = vec![0.0f32; out_features];
11034 for (shard_index, shard) in shards.iter().enumerate() {
11035 let engine = self
11036 .ranks
11037 .get(shard.device_rank)
11038 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
11039 let _main = engine.gpu.enter_main()?;
11040 let local_activations =
11041 activation_shard(input, 1, in_features, shards.len(), shard_index);
11042 let activations = engine.htod(&local_activations)?;
11043 let output = shard.host_canonical_expert(engine, expert, &activations)?;
11044 let partial = engine.dtoh(&output)?;
11045 for (sum, value) in reduced.iter_mut().zip(&partial) {
11046 *sum += *value;
11047 }
11048 }
11049 apply_macro(&mut reduced, macros[expert]);
11050 Ok(reduced)
11051 }
11052
11053 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel_nvfp4(
11058 &self,
11059 gate: Nvfp4ExpertBank<'_>,
11060 up: Nvfp4ExpertBank<'_>,
11061 down: Nvfp4ExpertBank<'_>,
11062 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
11063 gate.validate()?;
11064 up.validate()?;
11065 down.validate()?;
11066 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
11067 return Err("NVFP4 EP gate/up/down expert counts differ".into());
11068 }
11069 if gate.in_features != up.in_features || gate.out_features != up.out_features {
11070 return Err("NVFP4 EP gate/up dimensions differ".into());
11071 }
11072 if down.in_features != gate.out_features || down.out_features != gate.in_features {
11073 return Err(format!(
11074 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
11075 down.out_features, down.in_features, gate.out_features, gate.in_features
11076 )
11077 .into());
11078 }
11079 let world = self.ranks.len();
11080 if gate.expert_count % world != 0 {
11081 return Err(format!(
11082 "NVFP4 EP expert count {} is not divisible by {world} ranks",
11083 gate.expert_count
11084 )
11085 .into());
11086 }
11087 let experts_per_rank = gate.expert_count / world;
11088 let mut ranks = Vec::with_capacity(world);
11089 for (rank_index, engine) in self.ranks.iter().enumerate() {
11090 let _main = engine.gpu.enter_main()?;
11091 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
11092 let mut gate_host = Vec::new();
11093 let mut up_host = Vec::new();
11094 let mut down_host = Vec::new();
11095 for expert in expert_range.clone() {
11096 gate_host.extend_from_slice(&nvfp4_repack_matrix(gate.expert(expert)?));
11097 up_host.extend_from_slice(&nvfp4_repack_matrix(up.expert(expert)?));
11098 down_host.extend_from_slice(&nvfp4_repack_matrix(down.expert(expert)?));
11099 }
11100 let gate_expert_bytes = gate_host.len() / experts_per_rank;
11101 let up_expert_bytes = up_host.len() / experts_per_rank;
11102 if gate_expert_bytes != up_expert_bytes {
11103 return Err("NVFP4 EP gate/up packed expert bytes differ".into());
11104 }
11105 let down_expert_bytes = down_host.len() / experts_per_rank;
11106 ranks.push(ResidentNvfp4EpRank {
11107 gate: engine.htod_bytes(&gate_host)?,
11108 up: engine.htod_bytes(&up_host)?,
11109 down: engine.htod_bytes(&down_host)?,
11110 gate_expert_bytes,
11111 down_expert_bytes,
11112 macros_gate: engine.htod(&gate.macros[expert_range.clone()])?,
11113 macros_up: engine.htod(&up.macros[expert_range.clone()])?,
11114 macros_down: engine.htod(&down.macros[expert_range.clone()])?,
11115 expert_range,
11116 });
11117 }
11118 Ok(ResidentNvfp4ExpertParallel {
11119 ranks,
11120 macros_gate: gate.macros.to_vec(),
11121 macros_up: up.macros.to_vec(),
11122 macros_down: down.macros.to_vec(),
11123 expert_count: gate.expert_count,
11124 input_width: gate.in_features,
11125 expert_width: gate.out_features,
11126 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
11127 down_row_bytes: nvfp4_row_bytes(down.in_features),
11128 device_workspace: std::sync::Mutex::new(None),
11129 })
11130 }
11131
11132 pub fn upload_expert_parallel_nvfp4_normalized(
11138 &self,
11139 gate: &crate::model::HostExps,
11140 up: &crate::model::HostExps,
11141 down: &crate::model::HostExps,
11142 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
11143 for (label, bank) in [("gate", gate), ("up", up), ("down", down)] {
11144 if bank.qtype != crate::QT_NVFP4 || !bank.is_uniform_layout() {
11145 return Err(format!(
11146 "NVFP4 EP normalized {label} bank requires one uniform NVFP4 layout, \
11147 got qtype={} uniform={}",
11148 bank.qtype,
11149 bank.is_uniform_layout()
11150 )
11151 .into());
11152 }
11153 if bank.n_expert == 0
11154 || bank.expert_stride != bank.out_f * bank.row_bytes
11155 || (0..bank.n_expert)
11156 .any(|expert| bank.expert_bytes(expert).len() != bank.expert_stride)
11157 {
11158 return Err(format!("NVFP4 EP normalized {label} bank geometry is invalid").into());
11159 }
11160 }
11161 if gate.n_expert != up.n_expert || gate.n_expert != down.n_expert {
11162 return Err("NVFP4 EP normalized gate/up/down expert counts differ".into());
11163 }
11164 if gate.in_f != up.in_f || gate.out_f != up.out_f {
11165 return Err("NVFP4 EP normalized gate/up dimensions differ".into());
11166 }
11167 if down.in_f != gate.out_f || down.out_f != gate.in_f {
11168 return Err(format!(
11169 "NVFP4 EP normalized down {}x{} does not invert gate/up {}x{}",
11170 down.out_f, down.in_f, gate.out_f, gate.in_f
11171 )
11172 .into());
11173 }
11174 let macros = |bank: &crate::model::HostExps| -> Result<Vec<f32>, String> {
11175 let values = bank
11176 .macros
11177 .clone()
11178 .unwrap_or_else(|| vec![1.0; bank.n_expert]);
11179 if values.len() != bank.n_expert
11180 || !values.iter().all(|value| value.is_finite() && *value > 0.0)
11181 {
11182 return Err("NVFP4 EP normalized macro row is not finite-positive".to_string());
11183 }
11184 Ok(values)
11185 };
11186 let macros_gate = macros(gate)?;
11187 let macros_up = macros(up)?;
11188 let macros_down = macros(down)?;
11189 let world = self.ranks.len();
11190 if !gate.n_expert.is_multiple_of(world) {
11191 return Err(format!(
11192 "NVFP4 EP normalized expert count {} is not divisible by {world} ranks",
11193 gate.n_expert
11194 )
11195 .into());
11196 }
11197 let experts_per_rank = gate.n_expert / world;
11198 let mut ranks = Vec::with_capacity(world);
11199 for (rank_index, engine) in self.ranks.iter().enumerate() {
11200 let _main = engine.gpu.enter_main()?;
11201 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
11202 let mut gate_host = Vec::with_capacity(experts_per_rank * gate.expert_stride);
11203 let mut up_host = Vec::with_capacity(experts_per_rank * up.expert_stride);
11204 let mut down_host = Vec::with_capacity(experts_per_rank * down.expert_stride);
11205 for expert in expert_range.clone() {
11206 gate_host.extend_from_slice(gate.expert_bytes(expert));
11207 up_host.extend_from_slice(up.expert_bytes(expert));
11208 down_host.extend_from_slice(down.expert_bytes(expert));
11209 }
11210 ranks.push(ResidentNvfp4EpRank {
11211 gate: engine.htod_bytes(&gate_host)?,
11212 up: engine.htod_bytes(&up_host)?,
11213 down: engine.htod_bytes(&down_host)?,
11214 gate_expert_bytes: gate.expert_stride,
11215 down_expert_bytes: down.expert_stride,
11216 macros_gate: engine.htod(¯os_gate[expert_range.clone()])?,
11217 macros_up: engine.htod(¯os_up[expert_range.clone()])?,
11218 macros_down: engine.htod(¯os_down[expert_range.clone()])?,
11219 expert_range,
11220 });
11221 }
11222 Ok(ResidentNvfp4ExpertParallel {
11223 ranks,
11224 macros_gate,
11225 macros_up,
11226 macros_down,
11227 expert_count: gate.n_expert,
11228 input_width: gate.in_f,
11229 expert_width: gate.out_f,
11230 gate_row_bytes: gate.row_bytes,
11231 down_row_bytes: down.row_bytes,
11232 device_workspace: std::sync::Mutex::new(None),
11233 })
11234 }
11235
11236 #[allow(clippy::too_many_arguments)]
11242 pub fn run_routed_experts_nvfp4(
11243 &self,
11244 experts: &ResidentNvfp4ExpertParallel,
11245 input: &[f32],
11246 tokens: usize,
11247 selected: &[usize],
11248 route_weights: &[f32],
11249 experts_per_token: usize,
11250 activation_limit: Option<f32>,
11251 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11252 validate_activations(input, tokens, experts.input_width)?;
11253 let pairs = tokens
11254 .checked_mul(experts_per_token)
11255 .ok_or("NVFP4 EP route count overflow")?;
11256 if selected.len() != pairs || route_weights.len() != pairs {
11257 return Err(format!(
11258 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
11259 {experts_per_token} ({pairs})",
11260 selected.len(),
11261 route_weights.len(),
11262 )
11263 .into());
11264 }
11265 if !route_weights.iter().all(|weight| weight.is_finite()) {
11266 return Err("NVFP4 EP route weights contain a non-finite value".into());
11267 }
11268 let experts_per_rank = experts.expert_count / experts.ranks.len();
11269 let mut output = vec![0.0f32; tokens * experts.input_width];
11270 for token in 0..tokens {
11271 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11272 for slot in 0..experts_per_token {
11273 let pair = token * experts_per_token + slot;
11274 let expert = selected[pair];
11275 if expert >= experts.expert_count {
11276 return Err(format!(
11277 "NVFP4 EP selected expert {expert} outside 0..{}",
11278 experts.expert_count
11279 )
11280 .into());
11281 }
11282 let owner = expert / experts_per_rank;
11283 let local = expert - owner * experts_per_rank;
11284 let rank = &experts.ranks[owner];
11285 let engine = &self.ranks[owner];
11286 let _main = engine.gpu.enter_main()?;
11287 let device_input = engine.htod(input_row)?;
11288 let gate_out = engine.qmatvec_nvfp4_fast(
11289 &rank.gate.slice(
11290 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
11291 ),
11292 &device_input,
11293 1,
11294 experts.input_width,
11295 experts.expert_width,
11296 experts.gate_row_bytes,
11297 )?;
11298 let up_out = engine.qmatvec_nvfp4_fast(
11299 &rank.up.slice(
11300 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
11301 ),
11302 &device_input,
11303 1,
11304 experts.input_width,
11305 experts.expert_width,
11306 experts.gate_row_bytes,
11307 )?;
11308 let mut gate_host = engine.dtoh(&gate_out)?;
11309 let mut up_host = engine.dtoh(&up_out)?;
11310 apply_macro(&mut gate_host, experts.macros_gate[expert]);
11311 apply_macro(&mut up_host, experts.macros_up[expert]);
11312 let activated: Vec<f32> = gate_host
11313 .iter()
11314 .zip(&up_host)
11315 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11316 .collect();
11317 let device_activated = engine.htod(&activated)?;
11318 let down_out = engine.qmatvec_nvfp4_fast(
11319 &rank.down.slice(
11320 local * rank.down_expert_bytes..(local + 1) * rank.down_expert_bytes,
11321 ),
11322 &device_activated,
11323 1,
11324 experts.expert_width,
11325 experts.input_width,
11326 experts.down_row_bytes,
11327 )?;
11328 let mut down_host = engine.dtoh(&down_out)?;
11329 apply_macro(&mut down_host, experts.macros_down[expert]);
11330 let weight = route_weights[pair];
11331 for (sum, value) in output
11332 [token * experts.input_width..(token + 1) * experts.input_width]
11333 .iter_mut()
11334 .zip(down_host)
11335 {
11336 *sum += weight * value;
11337 }
11338 }
11339 }
11340 Ok(output)
11341 }
11342
11343 #[allow(clippy::too_many_arguments)]
11352 pub fn run_routed_experts_nvfp4_w4a16_device_io(
11353 &self,
11354 experts: &ResidentNvfp4ExpertParallel,
11355 e: &Engine,
11356 input_dev: &crate::CudaSlice<f32>,
11357 tokens: usize,
11358 selected: &[usize],
11359 route_weights: &[f32],
11360 experts_per_token: usize,
11361 activation_limit: Option<f32>,
11362 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11363 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11367 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11368 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11369 let started = timing.then(std::time::Instant::now);
11370 if !self.native_p2p {
11371 return Err("W4A16 NVFP4 device EP requires native P2P".into());
11372 }
11373 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11374 return Err(format!(
11375 "W4A16 NVFP4 device EP root device {:?} != model engine device {}",
11376 self.devices.first(),
11377 e.ctx().ordinal()
11378 )
11379 .into());
11380 }
11381 let active_input_values =
11385 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11386 let pairs = tokens
11387 .checked_mul(experts_per_token)
11388 .ok_or("W4A16 NVFP4 device EP route count overflow")?;
11389 if selected.len() != pairs || route_weights.len() != pairs {
11390 return Err(format!(
11391 "W4A16 NVFP4 device EP routes selected={} weights={} != tokens {tokens} x \
11392 experts/token {experts_per_token} ({pairs})",
11393 selected.len(),
11394 route_weights.len(),
11395 )
11396 .into());
11397 }
11398 if !route_weights.iter().all(|weight| weight.is_finite()) {
11399 return Err("W4A16 NVFP4 device EP route weights contain a non-finite value".into());
11400 }
11401 let world = self.ranks.len();
11402 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11403 return Err(format!(
11404 "W4A16 NVFP4 device EP runtime ranks {world} != bank ranks {}",
11405 experts.ranks.len()
11406 )
11407 .into());
11408 }
11409 let owner_routes = partition_expert_owner_routes(
11410 experts.expert_count,
11411 world,
11412 tokens,
11413 experts_per_token,
11414 selected,
11415 )?;
11416
11417 let mut workspace_guard = experts
11418 .device_workspace
11419 .lock()
11420 .map_err(|_| "W4A16 NVFP4 device EP workspace lock is poisoned")?;
11421 if workspace_guard.is_none() {
11422 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11423 let capacity_pairs = capacity_tokens * experts_per_token;
11424 let mut input = Vec::with_capacity(world);
11425 let mut input_bf16 = Vec::with_capacity(world);
11426 let mut input_q8 = Vec::with_capacity(world);
11427 let mut input_q8_scales = Vec::with_capacity(world);
11428 let mut sel = Vec::with_capacity(world);
11429 let mut token_rows = Vec::with_capacity(world);
11430 let mut global_pairs = Vec::with_capacity(world);
11431 let mut route_w = Vec::with_capacity(world);
11432 let mut gate_out = Vec::with_capacity(world);
11433 let mut up_out = Vec::with_capacity(world);
11434 let mut activation_bf16 = Vec::with_capacity(world);
11435 let mut activation_q8 = Vec::with_capacity(world);
11436 let mut activation_q8_scales = Vec::with_capacity(world);
11437 let mut ev_rank = Vec::with_capacity(world);
11438 for engine in &self.ranks {
11439 let _main = engine.gpu.enter_main()?;
11440 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11441 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11442 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11443 input_q8_scales
11444 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11445 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11446 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11447 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11448 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11449 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11450 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11451 activation_bf16
11452 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11453 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11454 activation_q8_scales
11455 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11456 ev_rank.push(engine.ctx().new_event(None)?);
11457 }
11458 let _main = e.gpu.enter_main()?;
11459 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11460 let slot_rows_raw = {
11461 use cudarc::driver::DevicePtr;
11462 let stream = e.stream();
11463 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11464 pointer
11465 };
11466 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11467 input,
11468 input_bf16,
11469 input_q8,
11470 input_q8_scales,
11471 sel,
11472 token_rows,
11473 global_pairs,
11474 route_w,
11475 gate_out,
11476 up_out,
11477 activation_bf16,
11478 activation_q8,
11479 activation_q8_scales,
11480 slot_rows,
11481 slot_rows_raw,
11482 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11483 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11484 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11485 graph_routes: None,
11486 graphs: std::iter::repeat_with(|| None)
11487 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11488 .collect(),
11489 ev_entry: e.ctx().new_event(None)?,
11490 ev_entry_device: e.ctx().ordinal(),
11491 ev_rank,
11492 phase_events: None,
11493 capacity_tokens,
11494 experts_per_token,
11495 });
11496 }
11497 let workspace = workspace_guard
11498 .as_mut()
11499 .expect("W4A16 NVFP4 device EP workspace initialized above");
11500 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11501 return Err(format!(
11502 "W4A16 NVFP4 device 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 if workspace.ev_entry_device != e.ctx().ordinal() {
11509 return Err("W4A16 NVFP4 device EP model engine changed".into());
11510 }
11511
11512 {
11513 let _main = e.gpu.enter_main()?;
11514 let mut destination = workspace.route_weights.slice_mut(0..pairs);
11515 e.stream()
11516 .memcpy_htod(&route_weights[..pairs], &mut destination)?;
11517 workspace.ev_entry.record(&e.stream())?;
11518 }
11519 for (rank_index, engine) in self.ranks.iter().enumerate() {
11520 let _main = engine.gpu.enter_main()?;
11521 engine.stream().wait(&workspace.ev_entry)?;
11522 {
11523 let mut destination = workspace.input[rank_index].slice_mut(0..active_input_values);
11524 engine
11525 .stream()
11526 .memcpy_dtod(&input_dev.slice(0..active_input_values), &mut destination)?;
11527 }
11528 engine.f32_to_bf16_into(
11529 &workspace.input[rank_index],
11530 &mut workspace.input_bf16[rank_index],
11531 tokens * experts.input_width,
11532 )?;
11533 let owner = &owner_routes[rank_index];
11534 debug_assert_eq!(owner.rank, rank_index);
11535 let local_count = owner.selected.len();
11536 if local_count > 0 {
11537 let local_selected = owner
11538 .selected
11539 .iter()
11540 .map(|&expert| expert as i32)
11541 .collect::<Vec<_>>();
11542 let local_token_rows = owner
11543 .token_rows
11544 .iter()
11545 .map(|&token| token as i32)
11546 .collect::<Vec<_>>();
11547 let local_global_pairs = owner
11548 .global_pairs
11549 .iter()
11550 .map(|&pair| pair as i32)
11551 .collect::<Vec<_>>();
11552 {
11553 let mut destination = workspace.sel[rank_index].slice_mut(0..local_count);
11554 engine
11555 .stream()
11556 .memcpy_htod(&local_selected, &mut destination)?;
11557 }
11558 {
11559 let mut destination =
11560 workspace.token_rows[rank_index].slice_mut(0..local_count);
11561 engine
11562 .stream()
11563 .memcpy_htod(&local_token_rows, &mut destination)?;
11564 }
11565 {
11566 let mut destination =
11567 workspace.global_pairs[rank_index].slice_mut(0..local_count);
11568 engine
11569 .stream()
11570 .memcpy_htod(&local_global_pairs, &mut destination)?;
11571 }
11572 let rank = &experts.ranks[rank_index];
11573 engine.qmatvec_nvfp4_bf16_sel_dual_rows_into(
11574 &rank.gate,
11575 &rank.up,
11576 &workspace.sel[rank_index],
11577 &workspace.token_rows[rank_index],
11578 &workspace.input_bf16[rank_index],
11579 &mut workspace.gate_out[rank_index],
11580 &mut workspace.up_out[rank_index],
11581 local_count,
11582 experts.input_width,
11583 experts.expert_width,
11584 experts.gate_row_bytes,
11585 rank.gate_expert_bytes,
11586 tokens,
11587 )?;
11588 engine.silu_mul_scaled_host_expf_bf16_sel_into(
11589 &workspace.gate_out[rank_index],
11590 &workspace.up_out[rank_index],
11591 &rank.macros_gate,
11592 &rank.macros_up,
11593 &workspace.sel[rank_index],
11594 activation_limit,
11595 &mut workspace.activation_bf16[rank_index],
11596 experts.expert_width,
11597 local_count,
11598 )?;
11599 engine.qmatvec_nvfp4_bf16_sel_down_rows_raw(
11600 &rank.down,
11601 &workspace.sel[rank_index],
11602 &workspace.global_pairs[rank_index],
11603 &workspace.activation_bf16[rank_index],
11604 &rank.macros_down,
11605 workspace.slot_rows_raw,
11606 local_count,
11607 experts.expert_width,
11608 experts.input_width,
11609 experts.down_row_bytes,
11610 rank.down_expert_bytes,
11611 pairs,
11612 )?;
11613 }
11614 workspace.ev_rank[rank_index].record(&engine.stream())?;
11615 }
11616
11617 let output = {
11618 let _main = e.gpu.enter_main()?;
11619 for event in &workspace.ev_rank {
11620 e.stream().wait(event)?;
11621 }
11622 let mut output = e.uninit(tokens * experts.input_width)?;
11623 e.axpy_rows_seq_tokens_into(
11624 &workspace.slot_rows,
11625 &workspace.route_weights,
11626 &mut output,
11627 experts.input_width,
11628 experts_per_token,
11629 tokens,
11630 )?;
11631 output
11632 };
11633 if let Some(started) = started {
11634 use std::sync::atomic::Ordering;
11635 e.stream().synchronize()?;
11636 let elapsed = started.elapsed().as_nanos() as u64;
11637 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11638 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11639 if calls.is_multiple_of(430) {
11640 eprintln!(
11641 "[nvfp4-ep-w4a16-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11642 ns as f64 / 1.0e6,
11643 ns as f64 / calls as f64 / 1.0e3,
11644 );
11645 }
11646 }
11647 Ok(output)
11648 }
11649
11650 #[allow(clippy::too_many_arguments)]
11656 pub fn run_routed_experts_nvfp4_w4a16_device_routed(
11657 &self,
11658 experts: &ResidentNvfp4ExpertParallel,
11659 e: &Engine,
11660 input_dev: &crate::CudaSlice<f32>,
11661 selected_dev: &crate::CudaSlice<i32>,
11662 route_weights_dev: &crate::CudaSlice<f32>,
11663 tokens: usize,
11664 experts_per_token: usize,
11665 activation_limit: Option<f32>,
11666 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11667 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11668 experts,
11669 e,
11670 input_dev,
11671 selected_dev,
11672 route_weights_dev,
11673 tokens,
11674 experts_per_token,
11675 activation_limit,
11676 None,
11677 )
11678 }
11679
11680 #[allow(clippy::too_many_arguments)]
11684 pub fn run_routed_experts_nvfp4_w4a16_device_routed_prejoin(
11685 &self,
11686 experts: &ResidentNvfp4ExpertParallel,
11687 e: &Engine,
11688 input_dev: &crate::CudaSlice<f32>,
11689 selected_dev: &crate::CudaSlice<i32>,
11690 route_weights_dev: &crate::CudaSlice<f32>,
11691 tokens: usize,
11692 experts_per_token: usize,
11693 activation_limit: Option<f32>,
11694 mut pre_join: impl FnMut() -> Result<(), Box<dyn std::error::Error>>,
11695 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11696 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11697 experts,
11698 e,
11699 input_dev,
11700 selected_dev,
11701 route_weights_dev,
11702 tokens,
11703 experts_per_token,
11704 activation_limit,
11705 Some(&mut pre_join),
11706 )
11707 }
11708
11709 #[allow(clippy::too_many_arguments)]
11710 fn run_routed_experts_nvfp4_w4a16_device_routed_inner(
11711 &self,
11712 experts: &ResidentNvfp4ExpertParallel,
11713 e: &Engine,
11714 input_dev: &crate::CudaSlice<f32>,
11715 selected_dev: &crate::CudaSlice<i32>,
11716 route_weights_dev: &crate::CudaSlice<f32>,
11717 tokens: usize,
11718 experts_per_token: usize,
11719 activation_limit: Option<f32>,
11720 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11721 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11722 if !self.native_p2p {
11723 return Err("W4A16 device-routed EP requires native P2P".into());
11724 }
11725 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11726 return Err(format!(
11727 "W4A16 device-routed EP root device {:?} != model engine device {}",
11728 self.devices.first(),
11729 e.ctx().ordinal()
11730 )
11731 .into());
11732 }
11733 let active_input_values =
11734 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11735 let pairs = tokens
11736 .checked_mul(experts_per_token)
11737 .ok_or("W4A16 device-routed EP route count overflow")?;
11738 if selected_dev.len() < pairs || route_weights_dev.len() < pairs {
11739 return Err(format!(
11740 "W4A16 device-routed EP metadata selected={} weights={} < pairs={pairs}",
11741 selected_dev.len(),
11742 route_weights_dev.len(),
11743 )
11744 .into());
11745 }
11746 let world = self.ranks.len();
11747 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11748 return Err(format!(
11749 "W4A16 device-routed EP runtime ranks {world} != bank ranks {}",
11750 experts.ranks.len()
11751 )
11752 .into());
11753 }
11754
11755 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11756 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11757 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11758 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11759 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11760 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11761 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11762 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11763 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11764 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11765 let started = timing.then(std::time::Instant::now);
11766 let graph_enabled = parallel_ep_graph_enabled()?;
11767 let pair_down_enabled = parallel_ep_pair_down_enabled()?;
11768
11769 let mut workspace_guard = experts
11770 .device_workspace
11771 .lock()
11772 .map_err(|_| "W4A16 device-routed EP workspace lock is poisoned")?;
11773 if workspace_guard.is_none() {
11774 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11775 let capacity_pairs = capacity_tokens * experts_per_token;
11776 let mut input = Vec::with_capacity(world);
11777 let mut input_bf16 = Vec::with_capacity(world);
11778 let mut input_q8 = Vec::with_capacity(world);
11779 let mut input_q8_scales = Vec::with_capacity(world);
11780 let mut sel = Vec::with_capacity(world);
11781 let mut token_rows = Vec::with_capacity(world);
11782 let mut global_pairs = Vec::with_capacity(world);
11783 let mut route_w = Vec::with_capacity(world);
11784 let mut gate_out = Vec::with_capacity(world);
11785 let mut up_out = Vec::with_capacity(world);
11786 let mut activation_bf16 = Vec::with_capacity(world);
11787 let mut activation_q8 = Vec::with_capacity(world);
11788 let mut activation_q8_scales = Vec::with_capacity(world);
11789 let mut ev_rank = Vec::with_capacity(world);
11790 let mut phase_head = Vec::with_capacity(world);
11791 let mut phase_copy_done = Vec::with_capacity(world);
11792 let mut phase_gate_up_done = Vec::with_capacity(world);
11793 let mut phase_activation_done = Vec::with_capacity(world);
11794 let mut phase_down_done = Vec::with_capacity(world);
11795 for engine in &self.ranks {
11796 let _main = engine.gpu.enter_main()?;
11797 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11798 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11799 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11800 input_q8_scales
11801 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11802 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11803 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11804 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11805 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11806 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11807 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11808 activation_bf16
11809 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11810 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11811 activation_q8_scales
11812 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11813 ev_rank.push(engine.ctx().new_event(None)?);
11814 if timing {
11815 phase_head.push(
11816 engine.ctx().new_event(Some(
11817 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11818 ))?,
11819 );
11820 phase_copy_done.push(
11821 engine.ctx().new_event(Some(
11822 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11823 ))?,
11824 );
11825 phase_gate_up_done.push(
11826 engine.ctx().new_event(Some(
11827 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11828 ))?,
11829 );
11830 phase_activation_done.push(
11831 engine.ctx().new_event(Some(
11832 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11833 ))?,
11834 );
11835 phase_down_done.push(
11836 engine.ctx().new_event(Some(
11837 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11838 ))?,
11839 );
11840 }
11841 }
11842 let _main = e.gpu.enter_main()?;
11843 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11844 let slot_rows_raw = {
11845 use cudarc::driver::DevicePtr;
11846 let stream = e.stream();
11847 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11848 pointer
11849 };
11850 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11851 input,
11852 input_bf16,
11853 input_q8,
11854 input_q8_scales,
11855 sel,
11856 token_rows,
11857 global_pairs,
11858 route_w,
11859 gate_out,
11860 up_out,
11861 activation_bf16,
11862 activation_q8,
11863 activation_q8_scales,
11864 slot_rows,
11865 slot_rows_raw,
11866 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11867 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11868 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11869 graph_routes: None,
11870 graphs: std::iter::repeat_with(|| None)
11871 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11872 .collect(),
11873 ev_entry: e.ctx().new_event(None)?,
11874 ev_entry_device: e.ctx().ordinal(),
11875 ev_rank,
11876 phase_events: timing.then_some(Nvfp4EpPhaseEvents {
11877 head: phase_head,
11878 copy_done: phase_copy_done,
11879 gate_up_done: phase_gate_up_done,
11880 activation_done: phase_activation_done,
11881 down_done: phase_down_done,
11882 }),
11883 capacity_tokens,
11884 experts_per_token,
11885 });
11886 }
11887 let workspace = workspace_guard
11888 .as_mut()
11889 .expect("W4A16 device-routed EP workspace initialized above");
11890 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11891 return Err(format!(
11892 "W4A16 device-routed EP workspace tokens={} experts/token={} cannot serve \
11893 tokens={tokens} experts/token={experts_per_token}",
11894 workspace.capacity_tokens, workspace.experts_per_token,
11895 )
11896 .into());
11897 }
11898
11899 if tokens <= NVFP4_EP_Q8_BATCH_CAP && parallel_ep_q8_act_enabled()? {
11900 if graph_enabled {
11901 return Err("MEMRA_PARALLEL_EP_GRAPH=1 is exact W4A16-only; disable \
11902 MEMRA_PARALLEL_EP_Q8_ACT or the graph door"
11903 .into());
11904 }
11905 return self.run_routed_experts_nvfp4_w4a8_device_routed(
11906 experts,
11907 e,
11908 input_dev,
11909 selected_dev,
11910 route_weights_dev,
11911 workspace,
11912 tokens,
11913 experts_per_token,
11914 activation_limit,
11915 pre_join,
11916 );
11917 }
11918
11919 if graph_enabled && !timing && pre_join.is_none() && tokens <= NVFP4_EP_GRAPH_BATCH_CAP {
11920 use cudarc::driver::DevicePtr;
11921 let route_ptrs = {
11922 let stream = e.stream();
11923 let (sel_ptr, _sel_guard) = selected_dev.device_ptr(&stream);
11924 let (weight_ptr, _weight_guard) = route_weights_dev.device_ptr(&stream);
11925 (sel_ptr, weight_ptr)
11926 };
11927 if let Some(graph_exec) = workspace.graphs[tokens].as_ref().map(|graph| graph.exec) {
11928 if workspace.graph_routes != Some(route_ptrs) {
11929 return Err(format!(
11930 "W4A16 EP graph route buffers moved: built={:?} current={route_ptrs:?}",
11931 workspace.graph_routes,
11932 )
11933 .into());
11934 }
11935 let _main = e.gpu.enter_main()?;
11936 e.stream().memcpy_dtod(
11937 &input_dev.slice(0..active_input_values),
11938 &mut workspace.graph_input.slice_mut(0..active_input_values),
11939 )?;
11940 e.memset_zeros_view(
11941 &mut workspace
11942 .slot_rows
11943 .slice_mut(0..pairs * experts.input_width),
11944 )?;
11945 unsafe {
11946 let result = cudarc::driver::sys::cuGraphLaunch(
11947 graph_exec,
11948 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11949 );
11950 if result != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11951 return Err(format!("W4A16 EP graph launch: {result:?}").into());
11952 }
11953 }
11954 let mut output = e.uninit(active_input_values)?;
11955 e.stream().memcpy_dtod(
11956 &workspace.graph_output.slice(0..active_input_values),
11957 &mut output.slice_mut(0..active_input_values),
11958 )?;
11959 return Ok(output);
11960 }
11961 }
11962
11963 {
11964 let _main = e.gpu.enter_main()?;
11965 e.memset_zeros_view(
11966 &mut workspace
11967 .slot_rows
11968 .slice_mut(0..pairs * experts.input_width),
11969 )?;
11970 workspace.ev_entry.record(&e.stream())?;
11971 }
11972
11973 for (rank_index, engine) in self.ranks.iter().enumerate() {
11974 let _main = engine.gpu.enter_main()?;
11975 if let Some(events) = workspace.phase_events.as_ref() {
11976 events.head[rank_index].record(&engine.stream())?;
11977 }
11978 engine.stream().wait(&workspace.ev_entry)?;
11979 let Nvfp4EpDeviceWorkspace {
11980 input_bf16,
11981 sel,
11982 route_w,
11983 ..
11984 } = &mut *workspace;
11985 engine.nvfp4_ep_stage_inputs(
11986 input_dev,
11987 selected_dev,
11988 route_weights_dev,
11989 &mut input_bf16[rank_index],
11990 &mut sel[rank_index],
11991 &mut route_w[rank_index],
11992 active_input_values,
11993 pairs,
11994 false,
11995 )?;
11996 if let Some(events) = workspace.phase_events.as_ref() {
11997 events.copy_done[rank_index].record(&engine.stream())?;
11998 }
11999 let rank = &experts.ranks[rank_index];
12000 let owner_start = rank.expert_range.start;
12001 let owner_end = rank.expert_range.end;
12002 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12003 &rank.gate,
12004 &rank.up,
12005 &workspace.sel[rank_index],
12006 &workspace.input_bf16[rank_index],
12007 &mut workspace.gate_out[rank_index],
12008 &mut workspace.up_out[rank_index],
12009 pairs,
12010 experts_per_token,
12011 experts.input_width,
12012 experts.expert_width,
12013 owner_start,
12014 owner_end,
12015 experts.gate_row_bytes,
12016 rank.gate_expert_bytes,
12017 )?;
12018 if let Some(events) = workspace.phase_events.as_ref() {
12019 events.gate_up_done[rank_index].record(&engine.stream())?;
12020 }
12021 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12022 &workspace.gate_out[rank_index],
12023 &workspace.up_out[rank_index],
12024 &rank.macros_gate,
12025 &rank.macros_up,
12026 &workspace.sel[rank_index],
12027 owner_start,
12028 owner_end,
12029 activation_limit,
12030 &mut workspace.activation_bf16[rank_index],
12031 experts.expert_width,
12032 pairs,
12033 )?;
12034 if let Some(events) = workspace.phase_events.as_ref() {
12035 events.activation_done[rank_index].record(&engine.stream())?;
12036 }
12037 if tokens > 1 && pair_down_enabled {
12038 engine.qmatvec_nvfp4_bf16_ep_down_pairs_raw(
12039 &rank.down,
12040 &workspace.sel[rank_index],
12041 &workspace.activation_bf16[rank_index],
12042 &rank.macros_down,
12043 workspace.slot_rows_raw,
12044 pairs,
12045 experts.expert_width,
12046 experts.input_width,
12047 owner_start,
12048 owner_end,
12049 experts.down_row_bytes,
12050 rank.down_expert_bytes,
12051 )?;
12052 } else {
12053 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12054 &rank.down,
12055 &workspace.sel[rank_index],
12056 &workspace.activation_bf16[rank_index],
12057 &rank.macros_down,
12058 workspace.slot_rows_raw,
12059 pairs,
12060 experts.expert_width,
12061 experts.input_width,
12062 owner_start,
12063 owner_end,
12064 experts.down_row_bytes,
12065 rank.down_expert_bytes,
12066 )?;
12067 }
12068 if let Some(events) = workspace.phase_events.as_ref() {
12069 events.down_done[rank_index].record(&engine.stream())?;
12070 }
12071 workspace.ev_rank[rank_index].record(&engine.stream())?;
12072 }
12073
12074 if let Some(pre_join) = pre_join.as_mut() {
12075 pre_join()?;
12076 }
12077 let issue_ns_this = started
12078 .as_ref()
12079 .map(|started| started.elapsed().as_nanos() as u64);
12080 let join_started = timing.then(std::time::Instant::now);
12081 let output = {
12082 let _main = e.gpu.enter_main()?;
12083 for event in &workspace.ev_rank {
12084 e.stream().wait(event)?;
12085 }
12086 let mut output = e.uninit(tokens * experts.input_width)?;
12087 e.axpy_rows_seq_tokens_into(
12088 &workspace.slot_rows,
12089 route_weights_dev,
12090 &mut output,
12091 experts.input_width,
12092 experts_per_token,
12093 tokens,
12094 )?;
12095 output
12096 };
12097
12098 if let Some(started) = started {
12099 use std::sync::atomic::Ordering;
12100 e.stream().synchronize()?;
12101 let elapsed = started.elapsed().as_nanos() as u64;
12102 let join_ns_this = join_started
12103 .expect("timing join starts with total timing")
12104 .elapsed()
12105 .as_nanos() as u64;
12106 let mut phase_max_ms = [0.0f32; 5];
12107 if let Some(events) = workspace.phase_events.as_ref() {
12108 for rank_index in 0..world {
12109 let engine = &self.ranks[rank_index];
12110 let _main = engine.gpu.enter_main()?;
12111 phase_max_ms[0] = phase_max_ms[0]
12112 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12113 phase_max_ms[1] = phase_max_ms[1].max(
12114 events.copy_done[rank_index]
12115 .elapsed_ms(&events.gate_up_done[rank_index])?,
12116 );
12117 phase_max_ms[2] = phase_max_ms[2].max(
12118 events.gate_up_done[rank_index]
12119 .elapsed_ms(&events.activation_done[rank_index])?,
12120 );
12121 phase_max_ms[3] = phase_max_ms[3].max(
12122 events.activation_done[rank_index]
12123 .elapsed_ms(&events.down_done[rank_index])?,
12124 );
12125 phase_max_ms[4] = phase_max_ms[4]
12126 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12127 }
12128 }
12129 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12130 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12131 let issue_ns = ISSUE_NS.fetch_add(
12132 issue_ns_this.expect("timing issue starts with total timing"),
12133 Ordering::Relaxed,
12134 ) + issue_ns_this.expect("timing issue starts with total timing");
12135 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12136 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12137 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12138 let activation_ns =
12139 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12140 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12141 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12142 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12143 if calls.is_multiple_of(430) {
12144 eprintln!(
12145 "[nvfp4-ep-device-router-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12146 ns as f64 / 1.0e6,
12147 ns as f64 / calls as f64 / 1.0e3,
12148 );
12149 eprintln!(
12150 "[nvfp4-ep-device-router-phases] calls={calls} issue_us={:.1} \
12151 join_us={:.1} rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12152 activation_us={:.1} down_us={:.1}",
12153 issue_ns as f64 / calls as f64 / 1.0e3,
12154 join_ns as f64 / calls as f64 / 1.0e3,
12155 rank_span_ns as f64 / calls as f64 / 1.0e3,
12156 copy_ns as f64 / calls as f64 / 1.0e3,
12157 gate_up_ns as f64 / calls as f64 / 1.0e3,
12158 activation_ns as f64 / calls as f64 / 1.0e3,
12159 down_ns as f64 / calls as f64 / 1.0e3,
12160 );
12161 }
12162 }
12163 if graph_enabled
12164 && !timing
12165 && pre_join.is_none()
12166 && tokens <= NVFP4_EP_GRAPH_BATCH_CAP
12167 && workspace.graphs[tokens].is_none()
12168 {
12169 e.stream().synchronize()?;
12170 let graph = self.build_nvfp4_ep_routes_graph(
12171 experts,
12172 e,
12173 workspace,
12174 selected_dev,
12175 route_weights_dev,
12176 tokens,
12177 experts_per_token,
12178 activation_limit,
12179 )?;
12180 workspace.graphs[tokens] = Some(graph);
12181 eprintln!(
12182 "[parallel-ep-graph] captured devices={:?} tokens={tokens} \
12183 experts/token={experts_per_token} input=staged routes=fixed \
12184 device_arithmetic=unchanged performance_claim=false",
12185 self.devices,
12186 );
12187 }
12188 Ok(output)
12189 }
12190
12191 #[allow(clippy::too_many_arguments)]
12192 fn run_routed_experts_nvfp4_w4a8_device_routed(
12193 &self,
12194 experts: &ResidentNvfp4ExpertParallel,
12195 e: &Engine,
12196 input_dev: &crate::CudaSlice<f32>,
12197 selected_dev: &crate::CudaSlice<i32>,
12198 route_weights_dev: &crate::CudaSlice<f32>,
12199 workspace: &mut Nvfp4EpDeviceWorkspace,
12200 tokens: usize,
12201 experts_per_token: usize,
12202 activation_limit: Option<f32>,
12203 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
12204 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12205 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12206 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12207 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12208 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12209 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12210 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12211 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12212 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12213 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12214 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12215 let started = timing.then(std::time::Instant::now);
12216 let pairs = tokens
12217 .checked_mul(experts_per_token)
12218 .ok_or("W4A8 device-routed EP route count overflow")?;
12219 let input_values = tokens
12220 .checked_mul(experts.input_width)
12221 .ok_or("W4A8 device-routed EP input size overflow")?;
12222 let scope = parallel_ep_q8_scope()?.unwrap_or(ParallelEpQ8Scope::All);
12223 let gate_up_paired = parallel_ep_q8_gu_paired_enabled(true, Some(scope))?;
12224
12225 {
12226 let _main = e.gpu.enter_main()?;
12227 e.memset_zeros_view(
12228 &mut workspace
12229 .slot_rows
12230 .slice_mut(0..pairs * experts.input_width),
12231 )?;
12232 workspace.ev_entry.record(&e.stream())?;
12233 }
12234 for (rank_index, engine) in self.ranks.iter().enumerate() {
12235 let _main = engine.gpu.enter_main()?;
12236 if let Some(events) = workspace.phase_events.as_ref() {
12237 events.head[rank_index].record(&engine.stream())?;
12238 }
12239 engine.stream().wait(&workspace.ev_entry)?;
12240 let rank = &experts.ranks[rank_index];
12241 let owner_start = rank.expert_range.start;
12242 let owner_end = rank.expert_range.end;
12243 match scope {
12244 ParallelEpQ8Scope::All | ParallelEpQ8Scope::GateUp => {
12245 engine.quantize_q8_1_into(
12246 input_dev,
12247 tokens,
12248 experts.input_width,
12249 &mut workspace.input_q8[rank_index],
12250 &mut workspace.input_q8_scales[rank_index],
12251 )?;
12252 engine.moe_sel_w_mirror(
12253 selected_dev,
12254 route_weights_dev,
12255 &mut workspace.sel[rank_index],
12256 &mut workspace.route_w[rank_index],
12257 pairs,
12258 )?;
12259 if let Some(events) = workspace.phase_events.as_ref() {
12260 events.copy_done[rank_index].record(&engine.stream())?;
12261 }
12262 if gate_up_paired {
12263 engine.qmatvec_nvfp4_q8_ep_paired_slots_into(
12264 &rank.gate,
12265 &rank.up,
12266 &workspace.sel[rank_index],
12267 &workspace.input_q8[rank_index],
12268 &workspace.input_q8_scales[rank_index],
12269 &mut workspace.gate_out[rank_index],
12270 &mut workspace.up_out[rank_index],
12271 pairs,
12272 experts_per_token,
12273 experts.input_width,
12274 experts.expert_width,
12275 owner_start,
12276 owner_end,
12277 experts.gate_row_bytes,
12278 rank.gate_expert_bytes,
12279 )?;
12280 } else {
12281 engine.qmatvec_nvfp4_q8_ep_dual_slots_into(
12282 &rank.gate,
12283 &rank.up,
12284 &workspace.sel[rank_index],
12285 &workspace.input_q8[rank_index],
12286 &workspace.input_q8_scales[rank_index],
12287 &mut workspace.gate_out[rank_index],
12288 &mut workspace.up_out[rank_index],
12289 pairs,
12290 experts_per_token,
12291 experts.input_width,
12292 experts.expert_width,
12293 owner_start,
12294 owner_end,
12295 experts.gate_row_bytes,
12296 rank.gate_expert_bytes,
12297 )?;
12298 }
12299 }
12300 ParallelEpQ8Scope::Down => {
12301 engine.nvfp4_ep_stage_inputs(
12302 input_dev,
12303 selected_dev,
12304 route_weights_dev,
12305 &mut workspace.input_bf16[rank_index],
12306 &mut workspace.sel[rank_index],
12307 &mut workspace.route_w[rank_index],
12308 input_values,
12309 pairs,
12310 false,
12311 )?;
12312 if let Some(events) = workspace.phase_events.as_ref() {
12313 events.copy_done[rank_index].record(&engine.stream())?;
12314 }
12315 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12316 &rank.gate,
12317 &rank.up,
12318 &workspace.sel[rank_index],
12319 &workspace.input_bf16[rank_index],
12320 &mut workspace.gate_out[rank_index],
12321 &mut workspace.up_out[rank_index],
12322 pairs,
12323 experts_per_token,
12324 experts.input_width,
12325 experts.expert_width,
12326 owner_start,
12327 owner_end,
12328 experts.gate_row_bytes,
12329 rank.gate_expert_bytes,
12330 )?;
12331 }
12332 }
12333 if let Some(events) = workspace.phase_events.as_ref() {
12334 events.gate_up_done[rank_index].record(&engine.stream())?;
12335 }
12336 match scope {
12337 ParallelEpQ8Scope::All | ParallelEpQ8Scope::Down => {
12338 engine.silu_mul_scaled_host_expf_q8_ep_slots_into(
12339 &workspace.gate_out[rank_index],
12340 &workspace.up_out[rank_index],
12341 &rank.macros_gate,
12342 &rank.macros_up,
12343 &workspace.sel[rank_index],
12344 owner_start,
12345 owner_end,
12346 activation_limit,
12347 &mut workspace.activation_q8[rank_index],
12348 &mut workspace.activation_q8_scales[rank_index],
12349 experts.expert_width,
12350 pairs,
12351 )?;
12352 if let Some(events) = workspace.phase_events.as_ref() {
12353 events.activation_done[rank_index].record(&engine.stream())?;
12354 }
12355 engine.qmatvec_nvfp4_q8_ep_down_slots_raw(
12356 &rank.down,
12357 &workspace.sel[rank_index],
12358 &workspace.activation_q8[rank_index],
12359 &workspace.activation_q8_scales[rank_index],
12360 &rank.macros_down,
12361 workspace.slot_rows_raw,
12362 pairs,
12363 experts.expert_width,
12364 experts.input_width,
12365 owner_start,
12366 owner_end,
12367 experts.down_row_bytes,
12368 rank.down_expert_bytes,
12369 )?;
12370 }
12371 ParallelEpQ8Scope::GateUp => {
12372 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12373 &workspace.gate_out[rank_index],
12374 &workspace.up_out[rank_index],
12375 &rank.macros_gate,
12376 &rank.macros_up,
12377 &workspace.sel[rank_index],
12378 owner_start,
12379 owner_end,
12380 activation_limit,
12381 &mut workspace.activation_bf16[rank_index],
12382 experts.expert_width,
12383 pairs,
12384 )?;
12385 if let Some(events) = workspace.phase_events.as_ref() {
12386 events.activation_done[rank_index].record(&engine.stream())?;
12387 }
12388 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12389 &rank.down,
12390 &workspace.sel[rank_index],
12391 &workspace.activation_bf16[rank_index],
12392 &rank.macros_down,
12393 workspace.slot_rows_raw,
12394 pairs,
12395 experts.expert_width,
12396 experts.input_width,
12397 owner_start,
12398 owner_end,
12399 experts.down_row_bytes,
12400 rank.down_expert_bytes,
12401 )?;
12402 }
12403 }
12404 if let Some(events) = workspace.phase_events.as_ref() {
12405 events.down_done[rank_index].record(&engine.stream())?;
12406 }
12407 workspace.ev_rank[rank_index].record(&engine.stream())?;
12408 }
12409
12410 if let Some(pre_join) = pre_join.as_mut() {
12411 pre_join()?;
12412 }
12413 let issue_ns_this = started
12414 .as_ref()
12415 .map(|started| started.elapsed().as_nanos() as u64);
12416 let join_started = timing.then(std::time::Instant::now);
12417 let output = {
12418 let _main = e.gpu.enter_main()?;
12419 for event in &workspace.ev_rank {
12420 e.stream().wait(event)?;
12421 }
12422 let mut output = e.uninit(input_values)?;
12423 e.axpy_rows_seq_tokens_into(
12424 &workspace.slot_rows,
12425 route_weights_dev,
12426 &mut output,
12427 experts.input_width,
12428 experts_per_token,
12429 tokens,
12430 )?;
12431 output
12432 };
12433 if let Some(started) = started {
12434 use std::sync::atomic::Ordering;
12435 e.stream().synchronize()?;
12436 let elapsed = started.elapsed().as_nanos() as u64;
12437 let join_ns_this = join_started
12438 .expect("timing join starts with total timing")
12439 .elapsed()
12440 .as_nanos() as u64;
12441 let mut phase_max_ms = [0.0f32; 5];
12442 if let Some(events) = workspace.phase_events.as_ref() {
12443 for rank_index in 0..self.ranks.len() {
12444 let engine = &self.ranks[rank_index];
12445 let _main = engine.gpu.enter_main()?;
12446 phase_max_ms[0] = phase_max_ms[0]
12447 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12448 phase_max_ms[1] = phase_max_ms[1].max(
12449 events.copy_done[rank_index]
12450 .elapsed_ms(&events.gate_up_done[rank_index])?,
12451 );
12452 phase_max_ms[2] = phase_max_ms[2].max(
12453 events.gate_up_done[rank_index]
12454 .elapsed_ms(&events.activation_done[rank_index])?,
12455 );
12456 phase_max_ms[3] = phase_max_ms[3].max(
12457 events.activation_done[rank_index]
12458 .elapsed_ms(&events.down_done[rank_index])?,
12459 );
12460 phase_max_ms[4] = phase_max_ms[4]
12461 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12462 }
12463 }
12464 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12465 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12466 let issue_ns = ISSUE_NS.fetch_add(
12467 issue_ns_this.expect("timing issue starts with total timing"),
12468 Ordering::Relaxed,
12469 ) + issue_ns_this.expect("timing issue starts with total timing");
12470 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12471 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12472 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12473 let activation_ns =
12474 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12475 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12476 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12477 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12478 if calls.is_multiple_of(430) {
12479 eprintln!(
12480 "[nvfp4-ep-q8-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12481 ns as f64 / 1.0e6,
12482 ns as f64 / calls as f64 / 1.0e3,
12483 );
12484 eprintln!(
12485 "[nvfp4-ep-q8-phases] calls={calls} issue_us={:.1} join_us={:.1} \
12486 rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12487 activation_us={:.1} down_us={:.1}",
12488 issue_ns as f64 / calls as f64 / 1.0e3,
12489 join_ns as f64 / calls as f64 / 1.0e3,
12490 rank_span_ns as f64 / calls as f64 / 1.0e3,
12491 copy_ns as f64 / calls as f64 / 1.0e3,
12492 gate_up_ns as f64 / calls as f64 / 1.0e3,
12493 activation_ns as f64 / calls as f64 / 1.0e3,
12494 down_ns as f64 / calls as f64 / 1.0e3,
12495 );
12496 }
12497 }
12498 static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
12499 if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
12500 let (expert_input, post_activation, numeric_class) = match scope {
12501 ParallelEpQ8Scope::All => ("q8_1", "q8_1", "w4a8-internal"),
12502 ParallelEpQ8Scope::GateUp => ("q8_1", "bf16", "w4a8-gate-up-internal"),
12503 ParallelEpQ8Scope::Down => ("bf16", "q8_1", "w4a8-down-internal"),
12504 };
12505 eprintln!(
12506 "[parallel-ep-q8] devices={:?} tokens={tokens} scope={} \
12507 expert_input={expert_input} post_activation={post_activation} \
12508 gate_up_schedule={} \
12509 external_boundary=bf16 numeric_class={numeric_class} \
12510 host_expf=true accumulation=token-slot-order performance_claim=false",
12511 self.devices,
12512 scope.label(),
12513 if gate_up_paired {
12514 "paired-cta"
12515 } else {
12516 "separate-cta"
12517 },
12518 );
12519 }
12520 Ok(output)
12521 }
12522
12523 #[allow(clippy::too_many_arguments)]
12524 fn build_nvfp4_ep_routes_graph(
12525 &self,
12526 experts: &ResidentNvfp4ExpertParallel,
12527 e: &Engine,
12528 workspace: &mut Nvfp4EpDeviceWorkspace,
12529 selected_dev: &crate::CudaSlice<i32>,
12530 route_weights_dev: &crate::CudaSlice<f32>,
12531 tokens: usize,
12532 experts_per_token: usize,
12533 activation_limit: Option<f32>,
12534 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12535 use cudarc::driver::DevicePtr;
12536 use cudarc::driver::sys;
12537
12538 fn cu_try(result: sys::CUresult, context: &str) -> Result<(), Box<dyn std::error::Error>> {
12539 if result == sys::CUresult::CUDA_SUCCESS {
12540 Ok(())
12541 } else {
12542 Err(format!("{context}: {result:?}").into())
12543 }
12544 }
12545
12546 let world = self.ranks.len();
12547 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
12548 return Err(format!(
12549 "W4A16 EP graph world {world} != expert ranks {}",
12550 experts.ranks.len()
12551 )
12552 .into());
12553 }
12554 let width = experts.input_width;
12555 if !(1..=NVFP4_EP_GRAPH_BATCH_CAP).contains(&tokens) {
12556 return Err(format!(
12557 "W4A16 EP graph tokens {tokens} outside 1..={NVFP4_EP_GRAPH_BATCH_CAP}"
12558 )
12559 .into());
12560 }
12561 let pairs = tokens
12562 .checked_mul(experts_per_token)
12563 .ok_or("W4A16 EP graph pair count overflow")?;
12564 let input_values = tokens
12565 .checked_mul(width)
12566 .ok_or("W4A16 EP graph input size overflow")?;
12567 let root_stream = e.stream();
12568 let (input_ptr, _input_guard) = workspace.graph_input.device_ptr(&root_stream);
12569 let (selected_ptr, _selected_guard) = selected_dev.device_ptr(&root_stream);
12570 let (weights_ptr, _weights_guard) = route_weights_dev.device_ptr(&root_stream);
12571 let route_ptrs = (selected_ptr, weights_ptr);
12572
12573 let mut children = Vec::with_capacity(world + 1);
12574 for rank_index in 0..world {
12575 let engine = &self.ranks[rank_index];
12576 let rank = &experts.ranks[rank_index];
12577 let owner_start = rank.expert_range.start;
12578 let owner_end = rank.expert_range.end;
12579 let _main = engine.gpu.enter_main()?;
12580 let (child, _retained) = engine.capture_graph_retained(|_| {
12581 engine.nvfp4_ep_stage_inputs_raw(
12582 input_ptr,
12583 selected_ptr,
12584 weights_ptr,
12585 &mut workspace.input_bf16[rank_index],
12586 &mut workspace.sel[rank_index],
12587 &mut workspace.route_w[rank_index],
12588 input_values,
12589 pairs,
12590 false,
12591 )?;
12592 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12593 &rank.gate,
12594 &rank.up,
12595 &workspace.sel[rank_index],
12596 &workspace.input_bf16[rank_index],
12597 &mut workspace.gate_out[rank_index],
12598 &mut workspace.up_out[rank_index],
12599 pairs,
12600 experts_per_token,
12601 width,
12602 experts.expert_width,
12603 owner_start,
12604 owner_end,
12605 experts.gate_row_bytes,
12606 rank.gate_expert_bytes,
12607 )?;
12608 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12609 &workspace.gate_out[rank_index],
12610 &workspace.up_out[rank_index],
12611 &rank.macros_gate,
12612 &rank.macros_up,
12613 &workspace.sel[rank_index],
12614 owner_start,
12615 owner_end,
12616 activation_limit,
12617 &mut workspace.activation_bf16[rank_index],
12618 experts.expert_width,
12619 pairs,
12620 )?;
12621 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12622 &rank.down,
12623 &workspace.sel[rank_index],
12624 &workspace.activation_bf16[rank_index],
12625 &rank.macros_down,
12626 workspace.slot_rows_raw,
12627 pairs,
12628 experts.expert_width,
12629 width,
12630 owner_start,
12631 owner_end,
12632 experts.down_row_bytes,
12633 rank.down_expert_bytes,
12634 )?;
12635 Ok(())
12636 })?;
12637 children.push(child);
12638 }
12639
12640 {
12641 let _main = e.gpu.enter_main()?;
12642 let (child, _retained) = e.capture_graph_retained(|_| {
12643 e.axpy_rows_seq_tokens_into(
12644 &workspace.slot_rows,
12645 route_weights_dev,
12646 &mut workspace.graph_output,
12647 width,
12648 experts_per_token,
12649 tokens,
12650 )
12651 })?;
12652 children.push(child);
12653 }
12654
12655 let mut parent: sys::CUgraph = std::ptr::null_mut();
12656 unsafe {
12657 cu_try(sys::cuGraphCreate(&mut parent, 0), "W4A16 EP cuGraphCreate")?;
12658 }
12659 let mut rank_nodes = Vec::with_capacity(world);
12660 for (rank_index, child) in children.iter().take(world).enumerate() {
12661 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12662 unsafe {
12663 cu_try(
12664 sys::cuGraphAddChildGraphNode(
12665 &mut node,
12666 parent,
12667 std::ptr::null(),
12668 0,
12669 child.cu_graph(),
12670 ),
12671 &format!("W4A16 EP graph rank {rank_index}"),
12672 )?;
12673 }
12674 rank_nodes.push(node);
12675 }
12676 let mut combine_node: sys::CUgraphNode = std::ptr::null_mut();
12677 unsafe {
12678 cu_try(
12679 sys::cuGraphAddChildGraphNode(
12680 &mut combine_node,
12681 parent,
12682 rank_nodes.as_ptr(),
12683 rank_nodes.len(),
12684 children[world].cu_graph(),
12685 ),
12686 "W4A16 EP graph combine",
12687 )?;
12688 }
12689 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12690 unsafe {
12691 cu_try(
12692 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12693 "W4A16 EP graph instantiate",
12694 )?;
12695 }
12696 workspace.graph_routes = Some(route_ptrs);
12697 Ok(RoutesGraph {
12698 exec,
12699 parent,
12700 _children: children,
12701 })
12702 }
12703
12704 pub fn run_tensor_parallel_routes_nvfp4_device(
12718 &self,
12719 experts: &ResidentNvfp4TensorParallel,
12720 input: &[f32],
12721 selected: &[usize],
12722 route_weights: &[f32],
12723 experts_per_token: usize,
12724 activation_limit: Option<f32>,
12725 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12726 validate_activations(input, 1, experts.input_width)?;
12727 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12728 return Err(format!(
12729 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
12730 selected.len(),
12731 route_weights.len(),
12732 )
12733 .into());
12734 }
12735 if !route_weights.iter().all(|weight| weight.is_finite()) {
12736 return Err("NVFP4 device route weights contain a non-finite value".into());
12737 }
12738 let world = self.ranks.len();
12739 if world != NVFP4_CANONICAL_ROW_SHARDS {
12740 return Err(format!(
12741 "NVFP4 device routes require world == canonical shard grid \
12742 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12743 )
12744 .into());
12745 }
12746 let local_out = if experts.ep2 {
12747 experts.expert_width
12748 } else {
12749 experts.expert_width / world
12750 };
12751
12752 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12756 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12757 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12758 let started = timing.then(std::time::Instant::now);
12759
12760 let n_sel = experts_per_token;
12761 let mut workspace_guard = experts
12762 .device_workspace
12763 .lock()
12764 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12765 if workspace_guard.is_none() {
12766 let mut gate_out = Vec::with_capacity(world);
12767 let mut up_out = Vec::with_capacity(world);
12768 let mut act_q = Vec::with_capacity(world);
12769 let mut act_d = Vec::with_capacity(world);
12770 let mut sel = Vec::with_capacity(world);
12771 let mut partial = Vec::with_capacity(world);
12772 let mut accumulator = Vec::with_capacity(world);
12773 let mut combine_w = Vec::with_capacity(world);
12774 let mut route_w = Vec::with_capacity(world);
12775 let mut in_q = Vec::with_capacity(world);
12776 let mut in_d = Vec::with_capacity(world);
12777 let mut input = Vec::with_capacity(world);
12778 let mut ev_rank = Vec::with_capacity(world);
12779 let moe_direct = moe_direct_on();
12780 for (rank, engine) in self.ranks.iter().enumerate() {
12781 let _main = engine.gpu.enter_main()?;
12782 gate_out.push(engine.uninit(n_sel * local_out)?);
12783 up_out.push(engine.uninit(n_sel * local_out)?);
12784 act_q.push(engine.uninit_i8(n_sel * local_out)?);
12785 act_d.push(engine.uninit(n_sel * local_out / 32)?);
12786 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
12787 partial.push(engine.uninit(n_sel * experts.input_width)?);
12788 if moe_direct && rank != 0 {
12790 let root = &self.ranks[0];
12791 let _root_main = root.gpu.enter_main()?;
12792 accumulator.push(root.zeros(experts.input_width)?);
12793 } else {
12794 accumulator.push(engine.zeros(experts.input_width)?);
12795 }
12796 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12797 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12798 in_q.push(engine.uninit_i8(experts.input_width)?);
12799 in_d.push(engine.uninit(experts.input_width / 32)?);
12800 input.push(engine.uninit(experts.input_width)?);
12801 ev_rank.push(engine.ctx().new_event(None)?);
12802 }
12803 let root = &self.ranks[0];
12804 let _main = root.gpu.enter_main()?;
12805 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
12806 prestaged: false,
12807 rank1_routed: false,
12808 ev_input: None,
12809 fence_flags_raw: 0,
12810 fence_ticket: 0,
12811 gate_out,
12812 up_out,
12813 act_q,
12814 act_d,
12815 sel,
12816 partial,
12817 accumulator,
12818 combine_w,
12819 route_w,
12820 in_q,
12821 in_d,
12822 dev_route_e: None,
12823 in_stage_e: None,
12824 out_stage_e: None,
12825 routes_graph: None,
12826 raw_dev_route_e: None,
12827 raw_combine: None,
12828 raw_input: Vec::new(),
12829 raw_sel: Vec::new(),
12830 raw_route_w: Vec::new(),
12831 remote: root.uninit(experts.input_width)?,
12832 combined: root.uninit(experts.input_width)?,
12833 n_sel,
12834 input,
12835 ev_rank,
12836 ev_done: Some(root.ctx().new_event(None)?),
12837 ev_entry: None,
12838 });
12839 }
12840 let workspace = workspace_guard
12841 .as_mut()
12842 .expect("NVFP4 device routes workspace initialized above");
12843 if experts.ep2 {
12846 return Ok(vec![0.0f32; experts.input_width]);
12847 }
12848 if workspace.n_sel != n_sel {
12849 return Err(format!(
12850 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12851 workspace.n_sel
12852 )
12853 .into());
12854 }
12855 for &expert in selected {
12856 if expert >= experts.expert_count {
12857 return Err(format!(
12858 "NVFP4 device selected expert {expert} outside 0..{}",
12859 experts.expert_count
12860 )
12861 .into());
12862 }
12863 }
12864 let sel_i32 = selected
12865 .iter()
12866 .map(|&expert| expert as i32)
12867 .collect::<Vec<_>>();
12868
12869 for (rank_index, engine) in self.ranks.iter().enumerate() {
12876 let _main = engine.gpu.enter_main()?;
12877 let device_input = engine.htod(input)?;
12878 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
12879 engine.quantize_q8_1_into(
12880 &device_input,
12881 1,
12882 experts.input_width,
12883 &mut in_q[rank_index],
12884 &mut in_d[rank_index],
12885 )?;
12886 }
12888 self.nvfp4_routes_batched_sweeps(
12889 experts,
12890 workspace,
12891 selected,
12892 route_weights,
12893 &sel_i32,
12894 local_out,
12895 n_sel,
12896 activation_limit,
12897 false,
12898 )?;
12899
12900 let root = &self.ranks[0];
12903 for engine in &self.ranks[1..] {
12904 let _main = engine.gpu.enter_main()?;
12905 engine.stream().synchronize()?;
12906 }
12907 let _main = root.gpu.enter_main()?;
12908 root.stream()
12909 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12910 root.add(
12911 &workspace.accumulator[0],
12912 &workspace.remote,
12913 &mut workspace.combined,
12914 experts.input_width,
12915 )?;
12916 let output = root.dtoh(&workspace.combined)?;
12917 if let Some(started) = started {
12918 use std::sync::atomic::Ordering;
12919 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12920 + started.elapsed().as_nanos() as u64;
12921 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12922 if calls.is_multiple_of(430) {
12923 eprintln!(
12924 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12925 ns as f64 / 1.0e6,
12926 ns as f64 / calls as f64 / 1.0e3,
12927 );
12928 }
12929 }
12930 Ok(output)
12931 }
12932
12933 #[allow(clippy::too_many_arguments)]
12938 fn nvfp4_routes_batched_sweeps(
12939 &self,
12940 experts: &ResidentNvfp4TensorParallel,
12941 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12942 selected: &[usize],
12943 route_weights: &[f32],
12944 sel_i32: &[i32],
12945 local_out: usize,
12946 n_sel: usize,
12947 activation_limit: Option<f32>,
12948 device_routed: bool,
12949 ) -> Result<(), Box<dyn std::error::Error>> {
12950 for rank_index in 0..self.ranks.len() {
12951 self.nvfp4_routes_batched_sweeps_rank(
12952 experts,
12953 workspace,
12954 selected,
12955 route_weights,
12956 sel_i32,
12957 local_out,
12958 n_sel,
12959 activation_limit,
12960 device_routed,
12961 rank_index,
12962 )?;
12963 }
12964 Ok(())
12965 }
12966
12967 #[allow(clippy::too_many_arguments)]
12970 fn nvfp4_routes_batched_sweeps_rank(
12971 &self,
12972 experts: &ResidentNvfp4TensorParallel,
12973 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12974 selected: &[usize],
12975 route_weights: &[f32],
12976 sel_i32: &[i32],
12977 local_out: usize,
12978 n_sel: usize,
12979 activation_limit: Option<f32>,
12980 device_routed: bool,
12981 rank_index: usize,
12982 ) -> Result<(), Box<dyn std::error::Error>> {
12983 {
12984 let engine = &self.ranks[rank_index];
12985 let _main = engine.gpu.enter_main()?;
12986 if experts.ep2 {
12991 if !device_routed {
12992 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
12993 }
12994 let gate_bank = &experts.gate[rank_index];
12995 let up_bank = &experts.up[rank_index];
12996 if gate_bank.local_out != experts.expert_width
12997 || gate_bank.expert_bytes != up_bank.expert_bytes
12998 {
12999 return Err("NVFP4 EP2 bank geometry drifted".into());
13000 }
13001 {
13002 let Nvfp4DeviceRoutesWorkspace {
13003 sel,
13004 gate_out,
13005 up_out,
13006 in_q,
13007 in_d,
13008 ..
13009 } = &mut *workspace;
13010 engine.qmatvec_nvfp4_sel_gu_ep_into(
13011 &gate_bank.bank,
13012 &up_bank.bank,
13013 &sel[rank_index],
13014 &in_q[rank_index],
13015 &in_d[rank_index],
13016 &mut gate_out[rank_index],
13017 &mut up_out[rank_index],
13018 n_sel,
13019 gate_bank.in_features,
13020 gate_bank.local_out,
13021 gate_bank.row_bytes,
13022 gate_bank.expert_bytes,
13023 rank_index,
13024 )?;
13025 }
13026 {
13027 let Nvfp4DeviceRoutesWorkspace {
13028 gate_out,
13029 up_out,
13030 sel,
13031 act_q,
13032 act_d,
13033 ..
13034 } = &mut *workspace;
13035 engine.silu_mul_scaled_q8_1_sel_ep_into(
13036 &gate_out[rank_index],
13037 &up_out[rank_index],
13038 &experts.macros_gate_dev[rank_index],
13039 &experts.macros_up_dev[rank_index],
13040 &sel[rank_index],
13041 activation_limit,
13042 &mut act_q[rank_index],
13043 &mut act_d[rank_index],
13044 local_out,
13045 n_sel,
13046 rank_index,
13047 )?;
13048 }
13049 let shard = &experts.down[rank_index];
13050 if shard.device_rank != rank_index || shard.local_in != local_out {
13051 return Err("NVFP4 EP2 down bank placement drifted".into());
13052 }
13053 {
13054 let Nvfp4DeviceRoutesWorkspace {
13055 sel,
13056 act_q,
13057 act_d,
13058 route_w,
13059 accumulator,
13060 ..
13061 } = &mut *workspace;
13062 engine.qmatvec_nvfp4_sel_down8_ep_into(
13063 &shard.bank,
13064 &sel[rank_index],
13065 &act_q[rank_index],
13066 &act_d[rank_index],
13067 &route_w[rank_index],
13068 &experts.macros_down_dev[rank_index],
13069 &mut accumulator[rank_index],
13070 n_sel,
13071 shard.local_in,
13072 shard.out_features,
13073 shard.row_bytes,
13074 shard.expert_bytes,
13075 local_out,
13076 local_out / 32,
13077 rank_index,
13078 )?;
13079 }
13080 return Ok(());
13081 }
13082 if !device_routed {
13083 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
13084 let folded = (0..n_sel)
13087 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
13088 .collect::<Vec<_>>();
13089 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
13090 engine.stream().memcpy_htod(&folded, &mut view)?;
13091 }
13092 let gate_bank = &experts.gate[rank_index];
13093 let up_bank = &experts.up[rank_index];
13094 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
13095 let gu_fused = sel_gu_fused_on()
13103 && gate_bank.slot_major
13104 && up_bank.slot_major
13105 && gate_bank.in_features == up_bank.in_features
13106 && gate_bank.local_out == up_bank.local_out
13107 && gate_bank.row_bytes == up_bank.row_bytes
13108 && gate_bank.expert_bytes == up_bank.expert_bytes;
13109 {
13115 static SEEN_GU: std::sync::Mutex<Vec<(bool, bool, bool)>> =
13116 std::sync::Mutex::new(Vec::new());
13117 let combo = (gu_fused, sel_gu_fused_on(), gate_bank.slot_major);
13118 let mut seen = SEEN_GU.lock().unwrap();
13119 if !seen.contains(&combo) {
13120 seen.push(combo);
13121 eprintln!(
13122 "[nvfp4-sweep] gu_fused={} door={} slot_major={} geometry_match={} \
13123 in_f={} out_f={} n_sel={n_sel}",
13124 gu_fused,
13125 sel_gu_fused_on(),
13126 gate_bank.slot_major,
13127 gate_bank.in_features == up_bank.in_features
13128 && gate_bank.local_out == up_bank.local_out
13129 && gate_bank.row_bytes == up_bank.row_bytes
13130 && gate_bank.expert_bytes == up_bank.expert_bytes,
13131 gate_bank.in_features,
13132 gate_bank.local_out
13133 );
13134 }
13135 }
13136 if gu_fused {
13137 let Nvfp4DeviceRoutesWorkspace {
13138 sel,
13139 gate_out,
13140 up_out,
13141 in_q,
13142 in_d,
13143 ..
13144 } = &mut *workspace;
13145 engine.qmatvec_nvfp4_sel_gu_into(
13146 &gate_bank.bank,
13147 &up_bank.bank,
13148 &sel[rank_index],
13149 &in_q[rank_index],
13150 &in_d[rank_index],
13151 &mut gate_out[rank_index],
13152 &mut up_out[rank_index],
13153 n_sel,
13154 gate_bank.in_features,
13155 gate_bank.local_out,
13156 gate_bank.row_bytes,
13157 gate_bank.expert_bytes,
13158 gate_bank.slot_major,
13159 )?;
13160 } else {
13161 engine.qmatvec_nvfp4_sel_into(
13162 &gate_bank.bank,
13163 &workspace.sel[rank_index],
13164 aq,
13165 ad,
13166 &mut workspace.gate_out[rank_index],
13167 n_sel,
13168 gate_bank.in_features,
13169 gate_bank.local_out,
13170 gate_bank.row_bytes,
13171 gate_bank.expert_bytes,
13172 0,
13173 0,
13174 gate_bank.slot_major,
13175 )?;
13176 engine.qmatvec_nvfp4_sel_into(
13177 &up_bank.bank,
13178 &workspace.sel[rank_index],
13179 aq,
13180 ad,
13181 &mut workspace.up_out[rank_index],
13182 n_sel,
13183 up_bank.in_features,
13184 up_bank.local_out,
13185 up_bank.row_bytes,
13186 up_bank.expert_bytes,
13187 0,
13188 0,
13189 up_bank.slot_major,
13190 )?;
13191 }
13192 {
13196 let Nvfp4DeviceRoutesWorkspace {
13197 gate_out,
13198 up_out,
13199 sel,
13200 act_q,
13201 act_d,
13202 ..
13203 } = &mut *workspace;
13204 engine.silu_mul_scaled_q8_1_sel_into(
13205 &gate_out[rank_index],
13206 &up_out[rank_index],
13207 &experts.macros_gate_dev[rank_index],
13208 &experts.macros_up_dev[rank_index],
13209 &sel[rank_index],
13210 activation_limit,
13211 &mut act_q[rank_index],
13212 &mut act_d[rank_index],
13213 local_out,
13214 n_sel,
13215 )?;
13216 }
13217 let shard = &experts.down[rank_index];
13218 if shard.device_rank != rank_index || shard.local_in != local_out {
13219 return Err(
13220 "NVFP4 device routes: down canonical shard placement drifted from \
13221 the gate/up column split"
13222 .into(),
13223 );
13224 }
13225 let down8 =
13233 device_routed && sel_down8_on() && shard.slot_major && (shard.local_in >> 5) <= 32;
13234 {
13239 static SEEN_D8: std::sync::Mutex<Vec<(bool, bool, bool, bool)>> =
13240 std::sync::Mutex::new(Vec::new());
13241 let combo = (down8, sel_down8_on(), device_routed, shard.slot_major);
13242 let mut seen = SEEN_D8.lock().unwrap();
13243 if !seen.contains(&combo) {
13244 seen.push(combo);
13245 eprintln!(
13251 "[nvfp4-sweep] down8={} door={} door_source={} device_routed={} \
13252 slot_major={} nsb={} in_class={} n_sel={n_sel}",
13253 down8,
13254 sel_down8_on(),
13255 sel_down8_source().1,
13256 device_routed,
13257 shard.slot_major,
13258 shard.local_in >> 5,
13259 (shard.local_in >> 5) <= 32
13260 );
13261 }
13262 }
13263 if down8 {
13264 let Nvfp4DeviceRoutesWorkspace {
13265 sel,
13266 act_q,
13267 act_d,
13268 route_w,
13269 accumulator,
13270 ..
13271 } = &mut *workspace;
13272 engine.qmatvec_nvfp4_sel_down8_into(
13273 &shard.bank,
13274 &sel[rank_index],
13275 &act_q[rank_index],
13276 &act_d[rank_index],
13277 &route_w[rank_index],
13278 &experts.macros_down_dev[rank_index],
13279 &mut accumulator[rank_index],
13280 n_sel,
13281 shard.local_in,
13282 shard.out_features,
13283 shard.row_bytes,
13284 shard.expert_bytes,
13285 local_out,
13286 local_out / 32,
13287 shard.slot_major,
13288 )?;
13289 } else {
13290 let Nvfp4DeviceRoutesWorkspace {
13291 sel,
13292 act_q,
13293 act_d,
13294 partial,
13295 ..
13296 } = &mut *workspace;
13297 engine.qmatvec_nvfp4_sel_into(
13298 &shard.bank,
13299 &sel[rank_index],
13300 &act_q[rank_index],
13301 &act_d[rank_index],
13302 &mut partial[rank_index],
13303 n_sel,
13304 shard.local_in,
13305 shard.out_features,
13306 shard.row_bytes,
13307 shard.expert_bytes,
13308 local_out,
13309 local_out / 32,
13310 shard.slot_major,
13311 )?;
13312 }
13313 if !down8 {
13318 let Nvfp4DeviceRoutesWorkspace {
13319 partial,
13320 combine_w,
13321 route_w,
13322 sel,
13323 accumulator,
13324 ..
13325 } = &mut *workspace;
13326 if device_routed {
13327 engine.axpy_rows_seq_md_into(
13328 &partial[rank_index],
13329 &route_w[rank_index],
13330 &experts.macros_down_dev[rank_index],
13331 &sel[rank_index],
13332 &mut accumulator[rank_index],
13333 experts.input_width,
13334 n_sel,
13335 )?;
13336 } else {
13337 engine.axpy_rows_seq_into(
13338 &partial[rank_index],
13339 &combine_w[rank_index],
13340 &mut accumulator[rank_index],
13341 experts.input_width,
13342 n_sel,
13343 )?;
13344 }
13345 }
13346 }
13347 Ok(())
13348 }
13349
13350 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_io(
13359 &self,
13360 experts: &ResidentNvfp4TensorParallel,
13361 e: &Engine,
13362 input_dev: &crate::CudaSlice<f32>,
13363 selected: &[usize],
13364 route_weights: &[f32],
13365 experts_per_token: usize,
13366 activation_limit: Option<f32>,
13367 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13368 if input_dev.len() != experts.input_width {
13369 return Err(format!(
13370 "NVFP4 device-io routes input {} != width {}",
13371 input_dev.len(),
13372 experts.input_width
13373 )
13374 .into());
13375 }
13376 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
13377 return Err(format!(
13378 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
13379 selected.len(),
13380 route_weights.len(),
13381 )
13382 .into());
13383 }
13384 if !route_weights.iter().all(|weight| weight.is_finite()) {
13385 return Err("NVFP4 device route weights contain a non-finite value".into());
13386 }
13387 let world = self.ranks.len();
13388 if world != NVFP4_CANONICAL_ROW_SHARDS {
13389 return Err(format!(
13390 "NVFP4 device routes require world == canonical shard grid \
13391 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
13392 )
13393 .into());
13394 }
13395 let local_out = experts.expert_width / world;
13396 let n_sel = experts_per_token;
13397
13398 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13399 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13400 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
13401 let started = timing.then(std::time::Instant::now);
13402
13403 let mut workspace_guard = experts
13404 .device_workspace
13405 .lock()
13406 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13407 if workspace_guard.is_none() {
13408 drop(workspace_guard);
13409 let zero = vec![0.0f32; experts.input_width];
13412 let zero_sel = vec![0usize; n_sel];
13413 let zero_w = vec![0.0f32; n_sel];
13414 let _ = self.run_tensor_parallel_routes_nvfp4_device(
13415 experts,
13416 &zero,
13417 &zero_sel,
13418 &zero_w,
13419 n_sel,
13420 activation_limit,
13421 )?;
13422 workspace_guard = experts
13423 .device_workspace
13424 .lock()
13425 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13426 }
13427 let workspace = workspace_guard
13428 .as_mut()
13429 .expect("NVFP4 device routes workspace initialized above");
13430 if workspace.n_sel != n_sel {
13431 return Err(format!(
13432 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
13433 workspace.n_sel
13434 )
13435 .into());
13436 }
13437 for &expert in selected {
13438 if expert >= experts.expert_count {
13439 return Err(format!(
13440 "NVFP4 device selected expert {expert} outside 0..{}",
13441 experts.expert_count
13442 )
13443 .into());
13444 }
13445 }
13446 let sel_i32 = selected
13447 .iter()
13448 .map(|&expert| expert as i32)
13449 .collect::<Vec<_>>();
13450
13451 if let Some((_, device)) = workspace.ev_entry.as_ref() {
13455 if *device != e.ctx().ordinal() {
13456 return Err("NVFP4 device-io routes engine changed".into());
13457 }
13458 } else {
13459 let _main = e.gpu.enter_main()?;
13460 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13461 }
13462 {
13463 let _main = e.gpu.enter_main()?;
13464 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13465 ev_entry.record(&e.stream())?;
13466 }
13467 for (rank_index, engine) in self.ranks.iter().enumerate() {
13468 let _main = engine.gpu.enter_main()?;
13469 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13470 engine.stream().wait(ev_entry)?;
13471 {
13472 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13473 engine
13474 .stream()
13475 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13476 }
13477 {
13478 let Nvfp4DeviceRoutesWorkspace {
13479 input, in_q, in_d, ..
13480 } = &mut *workspace;
13481 engine.quantize_q8_1_into(
13482 &input[rank_index],
13483 1,
13484 experts.input_width,
13485 &mut in_q[rank_index],
13486 &mut in_d[rank_index],
13487 )?;
13488 }
13489 }
13490 self.nvfp4_routes_batched_sweeps(
13491 experts,
13492 workspace,
13493 selected,
13494 route_weights,
13495 &sel_i32,
13496 local_out,
13497 n_sel,
13498 activation_limit,
13499 false,
13500 )?;
13501
13502 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
13508 let _main = engine.gpu.enter_main()?;
13509 workspace.ev_rank[rank_index].record(&engine.stream())?;
13510 }
13511 if moe_direct_on() && self.ranks.len() == 2 {
13512 {
13519 let root = &self.ranks[0];
13520 let _main = root.gpu.enter_main()?;
13521 workspace
13522 .ev_done
13523 .as_ref()
13524 .expect("device routes done event")
13525 .record(&root.stream())?;
13526 }
13527 let _main = e.gpu.enter_main()?;
13528 e.stream().wait(
13529 workspace
13530 .ev_done
13531 .as_ref()
13532 .expect("device routes done event"),
13533 )?;
13534 for ev in workspace.ev_rank.iter().skip(1) {
13535 e.stream().wait(ev)?;
13536 }
13537 let mut output = e.uninit(experts.input_width)?;
13538 e.add(
13539 &workspace.accumulator[0],
13540 &workspace.accumulator[1],
13541 &mut output,
13542 experts.input_width,
13543 )?;
13544 let output = output;
13545 if let Some(started) = started {
13546 use std::sync::atomic::Ordering;
13547 let ns = TIMING_NS
13548 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13549 + started.elapsed().as_nanos() as u64;
13550 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13551 if calls.is_multiple_of(430) {
13552 eprintln!(
13553 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13554 ns as f64 / 1.0e6,
13555 ns as f64 / calls as f64 / 1.0e3,
13556 );
13557 }
13558 }
13559 return Ok(output);
13560 }
13561 {
13562 let root = &self.ranks[0];
13563 let _main = root.gpu.enter_main()?;
13564 for ev in workspace.ev_rank.iter().skip(1) {
13565 root.stream().wait(ev)?;
13566 }
13567 root.stream()
13568 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
13569 {
13570 let Nvfp4DeviceRoutesWorkspace {
13571 accumulator,
13572 remote,
13573 combined,
13574 ..
13575 } = &mut *workspace;
13576 root.add(&accumulator[0], remote, combined, experts.input_width)?;
13577 }
13578 workspace
13579 .ev_done
13580 .as_ref()
13581 .expect("device routes done event")
13582 .record(&root.stream())?;
13583 }
13584 let output = {
13585 let _main = e.gpu.enter_main()?;
13586 e.stream().wait(
13587 workspace
13588 .ev_done
13589 .as_ref()
13590 .expect("device routes done event"),
13591 )?;
13592 let mut output = e.uninit(experts.input_width)?;
13595 e.stream().memcpy_dtod(
13596 &workspace.combined.slice(0..experts.input_width),
13597 &mut output.slice_mut(0..experts.input_width),
13598 )?;
13599 output
13600 };
13601 if let Some(started) = started {
13602 use std::sync::atomic::Ordering;
13603 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13604 + started.elapsed().as_nanos() as u64;
13605 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13606 if calls.is_multiple_of(430) {
13607 eprintln!(
13608 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13609 ns as f64 / 1.0e6,
13610 ns as f64 / calls as f64 / 1.0e3,
13611 );
13612 }
13613 }
13614 Ok(output)
13615 }
13616
13617 #[allow(clippy::too_many_arguments)]
13623 pub fn nvfp4_routes_prestage(
13628 &self,
13629 experts: &ResidentNvfp4TensorParallel,
13630 e: &Engine,
13631 input_dev: &crate::CudaSlice<f32>,
13632 ) -> Result<bool, Box<dyn std::error::Error>> {
13633 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
13634 }
13635
13636 pub fn nvfp4_routes_prestage_with(
13642 &self,
13643 experts: &ResidentNvfp4TensorParallel,
13644 e: &Engine,
13645 input_dev: &crate::CudaSlice<f32>,
13646 rank1_router: impl FnOnce(
13647 &Engine,
13648 &crate::CudaSlice<f32>,
13649 &mut crate::CudaSlice<i32>,
13650 &mut crate::CudaSlice<f32>,
13651 ) -> Result<bool, Box<dyn std::error::Error>>,
13652 ) -> Result<bool, Box<dyn std::error::Error>> {
13653 if !routes_prestage_on() || step_tp_graph_enabled()? {
13654 return Ok(false);
13655 }
13656 if input_dev.len() != experts.input_width {
13657 return Err("NVFP4 prestage input width mismatch".into());
13658 }
13659 let mut workspace_guard = experts
13660 .device_workspace
13661 .lock()
13662 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13663 let Some(workspace) = workspace_guard.as_mut() else {
13664 return Ok(false);
13665 };
13666 if workspace.ev_input.is_none() {
13667 let _main = e.gpu.enter_main()?;
13668 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13669 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
13670 return Err("NVFP4 prestage engine changed".into());
13671 }
13672 {
13673 let _main = e.gpu.enter_main()?;
13674 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13675 ev.record(&e.stream())?;
13676 }
13677 for (rank_index, engine) in self.ranks.iter().enumerate() {
13678 let _main = engine.gpu.enter_main()?;
13679 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13680 engine.stream().wait(ev)?;
13681 {
13682 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13683 engine
13684 .stream()
13685 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13686 }
13687 {
13688 let Nvfp4DeviceRoutesWorkspace {
13689 input, in_q, in_d, ..
13690 } = &mut *workspace;
13691 engine.quantize_q8_1_into(
13692 &input[rank_index],
13693 1,
13694 experts.input_width,
13695 &mut in_q[rank_index],
13696 &mut in_d[rank_index],
13697 )?;
13698 }
13699 }
13700 if self.ranks.len() == 2 {
13701 let rank1 = &self.ranks[1];
13702 let _r1 = rank1.gpu.enter_main()?;
13703 let Nvfp4DeviceRoutesWorkspace {
13704 input,
13705 sel,
13706 route_w,
13707 ..
13708 } = &mut *workspace;
13709 let (in1, rest_sel) = (&input[1], &mut sel[1]);
13710 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
13711 workspace.rank1_routed = true;
13712 }
13713 }
13714 workspace.prestaged = true;
13715 Ok(true)
13716 }
13717
13718 #[allow(clippy::too_many_arguments)]
13735 fn determ_stage_bytes(v: &[u8]) -> u64 {
13744 v.iter().fold(0u64, |a, b| {
13745 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
13746 })
13747 }
13748
13749 fn determ_stage_i32(v: &[i32]) -> u64 {
13754 v.iter().fold(0u64, |a, b| {
13755 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
13756 })
13757 }
13758
13759 fn determ_stage_sum(v: &[f32]) -> u64 {
13760 v.iter().fold(0u64, |a, x| {
13761 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
13762 })
13763 }
13764
13765 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
13767 &self,
13768 experts: &ResidentNvfp4TensorParallel,
13769 e: &Engine,
13770 z_t: &crate::CudaSlice<f32>,
13771 t: usize,
13772 sel: &[i32],
13773 w: &[f32],
13774 n_used: usize,
13775 activation_limit: Option<f32>,
13776 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13777 let world = self.ranks.len();
13778 if world != NVFP4_CANONICAL_ROW_SHARDS {
13779 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
13780 }
13781 let slot_major = experts.gate.iter().all(|b| b.slot_major)
13795 && experts.up.iter().all(|b| b.slot_major)
13796 && experts.down.iter().all(|b| b.slot_major);
13797 let any_slot_major = experts.gate.iter().any(|b| b.slot_major)
13798 || experts.up.iter().any(|b| b.slot_major)
13799 || experts.down.iter().any(|b| b.slot_major);
13800 if any_slot_major != slot_major {
13801 return Err(
13802 "NVFP4 grouped prime: gate/up/down banks disagree on the row layout — \
13803 one grouped GEMM cannot serve two byte maps"
13804 .into(),
13805 );
13806 }
13807 let bank_qt = if slot_major {
13808 crate::QT_NVFP4_V2
13809 } else {
13810 crate::QT_NVFP4
13811 };
13812 let width = experts.input_width;
13813 let n_expert = experts.expert_count;
13814 let n_pairs = t * n_used;
13815 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
13816 return Err("NVFP4 grouped prime geometry".into());
13817 }
13818 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
13827 let g_t0 = std::time::Instant::now();
13828 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
13831 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
13832 let s_id = s_id as usize;
13833 if s_id >= n_expert {
13834 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
13835 }
13836 buckets[s_id].push(p as i32);
13837 }
13838 let mut ex_ids: Vec<i32> = Vec::new();
13839 let mut ex_off: Vec<i32> = vec![0];
13840 let mut ex_pairs: Vec<i32> = Vec::new();
13841 for (e_id, b) in buckets.iter().enumerate() {
13842 if !b.is_empty() {
13843 ex_ids.push(e_id as i32);
13844 ex_pairs.extend_from_slice(b);
13845 ex_off.push(ex_pairs.len() as i32);
13846 }
13847 }
13848 let n_active = ex_ids.len();
13849 if n_active == 0 {
13850 return e.zeros(t * width);
13851 }
13852 if n_active > 512 {
13853 return Err("grouped prime n_active > 512 (direct lane cap)".into());
13854 }
13855 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
13856 let mut inv = vec![0i32; n_pairs];
13859 for (row, &pair) in ex_pairs.iter().enumerate() {
13860 inv[pair as usize] = row as i32;
13861 }
13862 let mg: Vec<f32> = ex_pairs
13864 .iter()
13865 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
13866 .collect();
13867 let mu: Vec<f32> = ex_pairs
13868 .iter()
13869 .map(|&p| experts.macros_up[sel[p as usize] as usize])
13870 .collect();
13871 let wd: Vec<f32> = (0..n_pairs)
13872 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
13873 .collect();
13874 {
13878 let mut tabs = experts
13879 .prime_tables
13880 .lock()
13881 .map_err(|_| "grouped prime table cache is poisoned")?;
13882 if tabs.len() != world {
13883 tabs.clear();
13884 for rank in 0..world {
13885 let engine = &self.ranks[rank];
13886 let _main = engine.gpu.enter_main()?;
13887 let (gb, ub, db) =
13888 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
13889 let mut tab = vec![0u64; 3 * n_expert];
13890 {
13891 use cudarc::driver::DevicePtr;
13892 let stream = engine.stream();
13893 let (pg, _g0) = gb.bank.device_ptr(&stream);
13894 let (pu, _g1) = ub.bank.device_ptr(&stream);
13895 let (pd, _g2) = db.bank.device_ptr(&stream);
13896 for ex in 0..n_expert {
13897 tab[ex] = pg + (ex * gb.expert_bytes) as u64;
13898 tab[n_expert + ex] = pu + (ex * ub.expert_bytes) as u64;
13899 tab[2 * n_expert + ex] = pd + (ex * db.expert_bytes) as u64;
13900 }
13901 }
13902 tabs.push(engine.htod_u64(&tab)?);
13903 }
13904 }
13905 }
13906 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
13907 let g_t1 = std::time::Instant::now();
13908 {
13915 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
13916 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13917 for rank in 0..world {
13918 let e_r = &self.ranks[rank];
13919 let _m = e_r.gpu.enter_main();
13920 eprintln!(
13921 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
13922 root_stream={:?}",
13923 e_r.ctx().ordinal(),
13924 std::sync::Arc::as_ptr(e_r.ctx()),
13925 e_r.stream().cu_stream(),
13926 e.ctx().ordinal(),
13927 e.stream().cu_stream(),
13928 );
13929 }
13930 }
13931 }
13932
13933 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
13934 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
13935 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
13936 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
13937 for rank in 0..world {
13938 let engine = &self.ranks[rank];
13939 let _main = engine.gpu.enter_main()?;
13940 if gprof {
13941 let h = engine
13946 .ctx()
13947 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13948 h.record(&engine.stream())?;
13949 ev_head.push(h);
13950 }
13951 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
13954 let gb = &experts.gate[rank];
13955 let ub = &experts.up[rank];
13956 let db = &experts.down[rank];
13957 if db.device_rank != rank {
13958 return Err("grouped prime: down shard placement drifted".into());
13959 }
13960 let local_ff = gb.local_out;
13961 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
13962 return Err("grouped prime: bank width mismatch".into());
13963 }
13964 let csr_tok_d = engine.htod_i32(&csr_tok)?;
13967 let exi_d = engine.htod_i32(&ex_ids)?;
13968 let exoff_d = engine.htod_i32(&ex_off)?;
13969 let mg_d = engine.htod(&mg)?;
13970 let mu_d = engine.htod(&mu)?;
13971 let tabs_guard = experts
13973 .prime_tables
13974 .lock()
13975 .map_err(|_| "grouped prime table cache is poisoned")?;
13976 let tab_d = &tabs_guard[rank];
13977 let mut z_r = engine.uninit(t * width)?;
13978 {
13979 let mut dst = z_r.slice_mut(0..t * width);
13980 engine
13981 .stream()
13982 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
13983 }
13984 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
13985 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
13986 if dstage {
13987 let zr = engine.dtoh(&z_r)?;
13991 let zsv = engine.dtoh(&zs)?;
13992 let z16v = engine.dtoh_u8(&z16)?;
13993 eprintln!(
13994 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
13995 Self::determ_stage_sum(&zr),
13996 Self::determ_stage_sum(&zsv),
13997 Self::determ_stage_bytes(&z16v)
13998 );
13999 }
14000 if dstage {
14001 engine.stream().synchronize()?;
14009 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
14010 let exi_v = engine.dtoh_i32(&exi_d)?;
14011 let exo_v = engine.dtoh_i32(&exoff_d)?;
14012 let mg_v = engine.dtoh(&mg_d)?;
14013 let mu_v = engine.dtoh(&mu_d)?;
14014 let tab_v = engine.dtoh_u64(tab_d)?;
14015 eprintln!(
14016 "[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={}",
14017 Self::determ_stage_i32(&csr_v),
14018 Self::determ_stage_i32(&exi_v),
14019 Self::determ_stage_i32(&exo_v),
14020 Self::determ_stage_i32(&ex_off),
14021 Self::determ_stage_sum(&mg_v),
14022 Self::determ_stage_sum(&mu_v),
14023 tab_v
14024 .iter()
14025 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
14026 gb.row_bytes
14027 );
14028 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
14031 let bank_v = engine.dtoh_u8(&gb.bank)?;
14032 eprintln!(
14033 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
14034 Self::determ_stage_bytes(&bank_v),
14035 bank_v.len()
14036 );
14037 }
14038 }
14039 let mut g = engine.moe_f16_grouped(
14040 tab_d,
14041 0,
14042 n_expert,
14043 &exi_d,
14044 &ex_off,
14045 &exoff_d,
14046 &z16,
14047 &zs,
14048 width,
14049 local_ff,
14050 n_active,
14051 n_pairs,
14052 bank_qt,
14053 gb.row_bytes,
14054 )?;
14055 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
14056 let mut u = engine.moe_f16_grouped(
14057 tab_d,
14058 1,
14059 n_expert,
14060 &exi_d,
14061 &ex_off,
14062 &exoff_d,
14063 &z16,
14064 &zs,
14065 width,
14066 local_ff,
14067 n_active,
14068 n_pairs,
14069 bank_qt,
14070 ub.row_bytes,
14071 )?;
14072 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
14073 let act = match activation_limit.filter(|l| *l > 1e-6) {
14077 Some(lim) => {
14078 let mut a = engine.uninit(n_pairs * local_ff)?;
14079 engine.swiglu_clamped_mul_scaled(
14080 &g,
14081 &u,
14082 1.0,
14083 1.0,
14084 lim,
14085 &mut a,
14086 n_pairs * local_ff,
14087 )?;
14088 a
14089 }
14090 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
14091 };
14092 if dstage {
14093 let gv = engine.dtoh(&g)?;
14094 let uv = engine.dtoh(&u)?;
14095 let av = engine.dtoh(&act)?;
14096 let key = (rank, t);
14101 let mut prev_map = DETERM_PREV
14102 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
14103 .lock()
14104 .map_err(|_| "determ prev map poisoned")?;
14105 let shape = match prev_map.get(&key) {
14106 Some(prev) if prev.len() == gv.len() => {
14107 let mut md = 0.0f32;
14108 let mut n_diff = 0usize;
14109 let mut n_big = 0usize;
14110 for (a, b) in prev.iter().zip(gv.iter()) {
14111 let d = (a - b).abs();
14112 if d > 0.0 {
14113 n_diff += 1;
14114 }
14115 if d > 1e-3 {
14116 n_big += 1;
14117 }
14118 if d > md {
14119 md = d;
14120 }
14121 }
14122 format!(
14123 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
14124 gv.len()
14125 )
14126 }
14127 _ => String::new(),
14128 };
14129 prev_map.insert(key, gv.clone());
14130 drop(prev_map);
14131 eprintln!(
14132 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
14133 Self::determ_stage_sum(&gv),
14134 Self::determ_stage_sum(&uv),
14135 Self::determ_stage_sum(&av)
14136 );
14137 }
14138 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
14139 let d_csr = engine.moe_f16_grouped(
14140 tab_d,
14141 2,
14142 n_expert,
14143 &exi_d,
14144 &ex_off,
14145 &exoff_d,
14146 &a16,
14147 &a_s,
14148 local_ff,
14149 width,
14150 n_active,
14151 n_pairs,
14152 bank_qt,
14153 db.row_bytes,
14154 )?;
14155
14156 if dstage {
14159 engine.stream().synchronize()?;
14160 let a16v = engine.dtoh_u8(&a16)?;
14161 let dv = engine.dtoh(&d_csr)?;
14162 eprintln!(
14163 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
14164 Self::determ_stage_bytes(&a16v),
14165 Self::determ_stage_sum(&dv)
14166 );
14167 }
14168 let ev = engine.ctx().new_event(None)?;
14169 ev.record(&engine.stream())?;
14170 if gprof {
14171 let tp = engine
14180 .ctx()
14181 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
14182 tp.record(&engine.stream())?;
14183 ev_tail_prof.push(tp);
14184 }
14185 ev_rank.push(ev);
14186 partials.push(d_csr);
14187 }
14188 let _main = e.gpu.enter_main()?;
14189 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
14190 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
14192 let g_t2 = std::time::Instant::now();
14193 for ev in &ev_rank {
14194 e.stream().wait(ev)?;
14195 }
14196 let mut y0 = e.uninit(n_pairs * width)?;
14199 {
14200 let mut dst = y0.slice_mut(0..n_pairs * width);
14201 e.stream()
14202 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
14203 }
14204 let mut y1 = e.uninit(n_pairs * width)?;
14205 {
14206 let mut dst = y1.slice_mut(0..n_pairs * width);
14207 e.stream()
14208 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
14209 }
14210 let inv_d = e.htod_i32(&inv)?;
14211 let wd_d = e.htod(&wd)?;
14212 let mut out = e.uninit(t * width)?;
14213 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
14214 if gprof {
14215 let _ = e.stream().synchronize();
14216 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
14217 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
14225 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
14226 let guard = self.ranks[rank].gpu.enter_main();
14227 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
14228 Ok(v) => span_ms.push(v),
14229 Err(err) => {
14230 static SAID: std::sync::atomic::AtomicBool =
14231 std::sync::atomic::AtomicBool::new(false);
14232 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
14233 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
14234 }
14235 span_ms.push(-1.0);
14236 }
14237 }
14238 }
14239 eprintln!(
14240 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
14241 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
14242 span_ms.iter().sum::<f32>(),
14243 span_ms.iter().cloned().fold(0.0f32, f32::max)
14244 );
14245 }
14246 Ok(out)
14247 }
14248
14249 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_routed(
14251 &self,
14252 experts: &ResidentNvfp4TensorParallel,
14253 e: &Engine,
14254 input_dev: &crate::CudaSlice<f32>,
14255 sel_d: &crate::CudaSlice<i32>,
14256 w_d: &crate::CudaSlice<f32>,
14257 experts_per_token: usize,
14258 activation_limit: Option<f32>,
14259 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
14260 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
14261 experts,
14262 e,
14263 input_dev,
14264 sel_d,
14265 w_d,
14266 experts_per_token,
14267 activation_limit,
14268 || Ok(()),
14269 )
14270 }
14271
14272 #[allow(clippy::too_many_arguments)]
14278 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
14279 &self,
14280 experts: &ResidentNvfp4TensorParallel,
14281 e: &Engine,
14282 input_dev: &crate::CudaSlice<f32>,
14283 sel_d: &crate::CudaSlice<i32>,
14284 w_d: &crate::CudaSlice<f32>,
14285 experts_per_token: usize,
14286 activation_limit: Option<f32>,
14287 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
14288 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
14289 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
14290 experts,
14291 e,
14292 input_dev,
14293 sel_d,
14294 w_d,
14295 experts_per_token,
14296 activation_limit,
14297 pre_join,
14298 None,
14299 )
14300 }
14301
14302 #[allow(clippy::too_many_arguments)]
14307 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
14308 &self,
14309 experts: &ResidentNvfp4TensorParallel,
14310 e: &Engine,
14311 input_dev: &crate::CudaSlice<f32>,
14312 sel_d: &crate::CudaSlice<i32>,
14313 w_d: &crate::CudaSlice<f32>,
14314 experts_per_token: usize,
14315 activation_limit: Option<f32>,
14316 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
14317 post_add: Option<(u64, u64)>,
14318 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
14319 if input_dev.len() != experts.input_width {
14320 return Err(format!(
14321 "NVFP4 device-routed input {} != width {}",
14322 input_dev.len(),
14323 experts.input_width
14324 )
14325 .into());
14326 }
14327 let n_sel = experts_per_token;
14328 if sel_d.len() < n_sel || w_d.len() < n_sel {
14329 return Err(format!(
14330 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
14331 sel_d.len(),
14332 w_d.len()
14333 )
14334 .into());
14335 }
14336 let world = self.ranks.len();
14337 if world != NVFP4_CANONICAL_ROW_SHARDS {
14338 return Err(format!(
14339 "NVFP4 device routes require world == canonical shard grid \
14340 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
14341 )
14342 .into());
14343 }
14344 let local_out = if experts.ep2 {
14345 experts.expert_width
14346 } else {
14347 experts.expert_width / world
14348 };
14349
14350 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
14351 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
14352 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
14353 let started = timing.then(std::time::Instant::now);
14354
14355 let mut workspace_guard = experts
14356 .device_workspace
14357 .lock()
14358 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
14359 if workspace_guard.is_none() {
14360 drop(workspace_guard);
14361 let zero = vec![0.0f32; experts.input_width];
14362 let zero_sel = vec![0usize; n_sel];
14363 let zero_w = vec![0.0f32; n_sel];
14364 let _ = self.run_tensor_parallel_routes_nvfp4_device(
14365 experts,
14366 &zero,
14367 &zero_sel,
14368 &zero_w,
14369 n_sel,
14370 activation_limit,
14371 )?;
14372 workspace_guard = experts
14373 .device_workspace
14374 .lock()
14375 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
14376 }
14377 let workspace = workspace_guard
14378 .as_mut()
14379 .expect("NVFP4 device routes workspace initialized above");
14380 if workspace.n_sel != n_sel {
14381 return Err(format!(
14382 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
14383 workspace.n_sel
14384 )
14385 .into());
14386 }
14387
14388 if step_tp_graph_enabled()? && step_tp_graph_headroom_ok(e) {
14399 if experts.ep2 {
14400 return Err(
14401 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
14402 co-gated; unset one"
14403 .into(),
14404 );
14405 }
14406 if workspace.dev_route_e.is_none() {
14407 let _main = e.gpu.enter_main()?;
14408 workspace.dev_route_e = Some((
14409 e.htod_i32(&vec![0i32; n_sel])?,
14410 e.htod(&vec![0.0f32; n_sel])?,
14411 ));
14412 }
14413 if workspace.in_stage_e.is_none() {
14414 let _main = e.gpu.enter_main()?;
14415 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14416 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14417 }
14418 if workspace.routes_graph.is_none() {
14419 let graph = self.nvfp4_routes_build_graph(
14420 experts,
14421 workspace,
14422 local_out,
14423 n_sel,
14424 activation_limit,
14425 )?;
14426 workspace.routes_graph = Some(graph);
14427 eprintln!(
14428 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
14429 children=3 updates=none performance_claim=false"
14430 );
14431 }
14432 let output = {
14433 let _main = e.gpu.enter_main()?;
14434 {
14435 let (sel_e, w_e) = workspace
14436 .dev_route_e
14437 .as_mut()
14438 .expect("device route staging set above");
14439 {
14440 let mut dst = sel_e.slice_mut(0..n_sel);
14441 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14442 }
14443 {
14444 let mut dst = w_e.slice_mut(0..n_sel);
14445 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14446 }
14447 }
14448 {
14449 let in_stage = workspace
14450 .in_stage_e
14451 .as_mut()
14452 .expect("graph staging set above");
14453 let mut dst = in_stage.slice_mut(0..experts.input_width);
14454 e.stream()
14455 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
14456 }
14457 unsafe {
14458 let r = cudarc::driver::sys::cuGraphLaunch(
14459 workspace
14460 .routes_graph
14461 .as_ref()
14462 .expect("routes graph built above")
14463 .exec,
14464 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
14465 );
14466 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
14467 return Err(format!("routes graph launch: {r:?}").into());
14468 }
14469 }
14470 let mut output = e.uninit(experts.input_width)?;
14471 {
14472 let out_stage = workspace
14473 .out_stage_e
14474 .as_ref()
14475 .expect("graph staging set above");
14476 e.stream().memcpy_dtod(
14477 &out_stage.slice(0..experts.input_width),
14478 &mut output.slice_mut(0..experts.input_width),
14479 )?;
14480 }
14481 output
14482 };
14483 if let Some(started) = started {
14484 use std::sync::atomic::Ordering;
14485 let ns = TIMING_NS
14486 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14487 + started.elapsed().as_nanos() as u64;
14488 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14489 if calls.is_multiple_of(430) {
14490 eprintln!(
14491 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14492 ns as f64 / 1.0e6,
14493 ns as f64 / calls as f64 / 1.0e3,
14494 );
14495 }
14496 }
14497 return Ok(output);
14498 }
14499
14500 if let Some((_, device)) = workspace.ev_entry.as_ref() {
14504 if *device != e.ctx().ordinal() {
14505 return Err("NVFP4 device-routed routes engine changed".into());
14506 }
14507 } else {
14508 let _main = e.gpu.enter_main()?;
14509 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
14510 }
14511 if workspace.dev_route_e.is_none() {
14512 let _main = e.gpu.enter_main()?;
14513 workspace.dev_route_e = Some((
14514 e.htod_i32(&vec![0i32; n_sel])?,
14515 e.htod(&vec![0.0f32; n_sel])?,
14516 ));
14517 }
14518 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
14524 let e_device = e.ctx().ordinal();
14525 let rank1_routed_peek = workspace.rank1_routed;
14527 let stage_needed = !mirror
14528 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
14529 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
14530 });
14531 {
14532 let _main = e.gpu.enter_main()?;
14533 if stage_needed {
14534 let (sel_e, w_e) = workspace
14535 .dev_route_e
14536 .as_mut()
14537 .expect("device route staging set above");
14538 {
14539 let mut dst = sel_e.slice_mut(0..n_sel);
14540 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14541 }
14542 {
14543 let mut dst = w_e.slice_mut(0..n_sel);
14544 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14545 }
14546 }
14547 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14548 ev_entry.record(&e.stream())?;
14549 }
14550 let prestaged = std::mem::take(&mut workspace.prestaged);
14553 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
14554 for (rank_index, engine) in self.ranks.iter().enumerate() {
14555 let _main = engine.gpu.enter_main()?;
14556 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14557 engine.stream().wait(ev_entry)?;
14558 if !prestaged {
14559 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
14560 engine
14561 .stream()
14562 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
14563 }
14564 if !(rank1_routed && rank_index == 1) {
14565 let same_dev = engine.ctx().ordinal() == e_device;
14569 if mirror {
14570 let Nvfp4DeviceRoutesWorkspace {
14573 sel,
14574 route_w,
14575 dev_route_e,
14576 ..
14577 } = &mut *workspace;
14578 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
14579 if same_dev {
14580 (sel_d, w_d)
14581 } else {
14582 let (sel_e, w_e) = dev_route_e
14583 .as_ref()
14584 .expect("device route staging set above");
14585 (sel_e, w_e)
14586 };
14587 engine.moe_sel_w_mirror(
14588 src_sel,
14589 src_w,
14590 &mut sel[rank_index],
14591 &mut route_w[rank_index],
14592 n_sel,
14593 )?;
14594 } else {
14595 let (sel_e, w_e) = workspace
14596 .dev_route_e
14597 .as_ref()
14598 .expect("device route staging set above");
14599 {
14600 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
14601 engine
14602 .stream()
14603 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
14604 }
14605 {
14606 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
14607 engine
14608 .stream()
14609 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
14610 }
14611 }
14612 }
14613 if !prestaged {
14614 let Nvfp4DeviceRoutesWorkspace {
14615 input, in_q, in_d, ..
14616 } = &mut *workspace;
14617 engine.quantize_q8_1_into(
14618 &input[rank_index],
14619 1,
14620 experts.input_width,
14621 &mut in_q[rank_index],
14622 &mut in_d[rank_index],
14623 )?;
14624 }
14625 }
14626 self.nvfp4_routes_batched_sweeps(
14627 experts,
14628 workspace,
14629 &[],
14630 &[],
14631 &[],
14632 local_out,
14633 n_sel,
14634 activation_limit,
14635 true,
14636 )?;
14637
14638 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
14641 let _main = engine.gpu.enter_main()?;
14642 workspace.ev_rank[rank_index].record(&engine.stream())?;
14643 }
14644 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
14647 let mut ticket = 0u32;
14648 if memops {
14649 use cudarc::driver::sys;
14650 if workspace.fence_flags_raw == 0 {
14651 let root = &self.ranks[0];
14652 let _main = root.gpu.enter_main()?;
14653 let mut ptr: sys::CUdeviceptr = 0;
14654 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
14655 if r != sys::CUresult::CUDA_SUCCESS {
14656 return Err(format!("fence flag alloc: {r:?}").into());
14657 }
14658 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
14659 if r != sys::CUresult::CUDA_SUCCESS {
14660 return Err(format!("fence flag memset: {r:?}").into());
14661 }
14662 workspace.fence_flags_raw = ptr as u64;
14663 }
14664 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
14665 ticket = workspace.fence_ticket;
14666 let base = workspace.fence_flags_raw;
14667 if fence_rank1_on() {
14673 let peer = &self.ranks[1];
14674 let _pmain = peer.gpu.enter_main()?;
14675 peer.ring_flag_raw(base, ticket)?;
14676 }
14677 {
14678 let root = &self.ranks[0];
14679 let _main = root.gpu.enter_main()?;
14680 let r = unsafe {
14681 sys::cuStreamWriteValue32_v2(
14682 root.stream().cu_stream() as sys::CUstream,
14683 (base + 4) as sys::CUdeviceptr,
14684 ticket,
14685 0,
14686 )
14687 };
14688 if r != sys::CUresult::CUDA_SUCCESS {
14689 return Err(format!("fence write root: {r:?}").into());
14690 }
14691 }
14692 }
14693 pre_join()?;
14696
14697 if moe_direct_on() && self.ranks.len() == 2 {
14698 let _main = e.gpu.enter_main()?;
14705 if memops {
14706 use cudarc::driver::sys;
14707 let base = workspace.fence_flags_raw;
14708 let r = unsafe {
14709 sys::cuStreamWaitValue32_v2(
14710 e.stream().cu_stream() as sys::CUstream,
14711 (base + 4) as sys::CUdeviceptr,
14712 ticket,
14713 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14714 )
14715 };
14716 if r != sys::CUresult::CUDA_SUCCESS {
14717 return Err(format!("fence wait: {r:?}").into());
14718 }
14719 if fence_rank1_on() {
14720 let r = unsafe {
14722 sys::cuStreamWaitValue32_v2(
14723 e.stream().cu_stream() as sys::CUstream,
14724 base as sys::CUdeviceptr,
14725 ticket,
14726 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14727 )
14728 };
14729 if r != sys::CUresult::CUDA_SUCCESS {
14730 return Err(format!("fence wait rank1: {r:?}").into());
14731 }
14732 } else {
14733 for ev in workspace.ev_rank.iter().skip(1) {
14734 e.stream().wait(ev)?;
14735 }
14736 }
14737 } else {
14738 {
14739 let root = &self.ranks[0];
14740 let _rmain = root.gpu.enter_main()?;
14741 workspace
14742 .ev_done
14743 .as_ref()
14744 .expect("device routes done event")
14745 .record(&root.stream())?;
14746 }
14747 e.stream().wait(
14748 workspace
14749 .ev_done
14750 .as_ref()
14751 .expect("device routes done event"),
14752 )?;
14753 for ev in workspace.ev_rank.iter().skip(1) {
14754 e.stream().wait(ev)?;
14755 }
14756 }
14757 let mut output = e.uninit(experts.input_width)?;
14758 if let Some((sh_raw, scale_raw)) = post_add {
14759 e.add3_raw(
14762 &workspace.accumulator[0],
14763 &workspace.accumulator[1],
14764 sh_raw,
14765 scale_raw,
14766 &mut output,
14767 experts.input_width,
14768 )?;
14769 } else {
14770 e.add(
14771 &workspace.accumulator[0],
14772 &workspace.accumulator[1],
14773 &mut output,
14774 experts.input_width,
14775 )?;
14776 }
14777 let output = output;
14778 if let Some(started) = started {
14779 use std::sync::atomic::Ordering;
14780 let ns = TIMING_NS
14781 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14782 + started.elapsed().as_nanos() as u64;
14783 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14784 if calls.is_multiple_of(430) {
14785 eprintln!(
14786 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14787 ns as f64 / 1.0e6,
14788 ns as f64 / calls as f64 / 1.0e3,
14789 );
14790 }
14791 }
14792 return Ok(output);
14793 }
14794 {
14795 let root = &self.ranks[0];
14796 let _main = root.gpu.enter_main()?;
14797 for ev in workspace.ev_rank.iter().skip(1) {
14798 root.stream().wait(ev)?;
14799 }
14800 root.stream()
14801 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
14802 {
14803 let Nvfp4DeviceRoutesWorkspace {
14804 accumulator,
14805 remote,
14806 combined,
14807 ..
14808 } = &mut *workspace;
14809 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14810 }
14811 workspace
14812 .ev_done
14813 .as_ref()
14814 .expect("device routes done event")
14815 .record(&root.stream())?;
14816 }
14817 let output = {
14818 let _main = e.gpu.enter_main()?;
14819 e.stream().wait(
14820 workspace
14821 .ev_done
14822 .as_ref()
14823 .expect("device routes done event"),
14824 )?;
14825 let mut output = e.uninit(experts.input_width)?;
14828 e.stream().memcpy_dtod(
14829 &workspace.combined.slice(0..experts.input_width),
14830 &mut output.slice_mut(0..experts.input_width),
14831 )?;
14832 output
14833 };
14834 if let Some(started) = started {
14835 use std::sync::atomic::Ordering;
14836 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14837 + started.elapsed().as_nanos() as u64;
14838 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14839 if calls.is_multiple_of(430) {
14840 eprintln!(
14841 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14842 ns as f64 / 1.0e6,
14843 ns as f64 / calls as f64 / 1.0e3,
14844 );
14845 }
14846 }
14847 Ok(output)
14848 }
14849
14850 pub(crate) fn decode_v2_finish_root_fused(
14854 &self,
14855 ws: &mut StepTpDecodeV2Ws,
14856 ) -> Result<(), Box<dyn std::error::Error>> {
14857 let root = &self.ranks[0];
14858 let _main = root.gpu.enter_main()?;
14859 if ws.raw_peer_partial != 0 {
14860 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
14862 } else {
14863 root.stream()
14864 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
14865 }
14866 {
14867 let StepTpDecodeV2Ws {
14868 o_partials,
14869 peer_partial,
14870 reduce_a,
14871 o_out,
14872 ..
14873 } = &mut *ws;
14874 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
14875 }
14876 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
14877 if shadows {
14878 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
14881 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
14882 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
14883 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
14884 }
14885 if shadows && ws.raw_peer_partial != 0 {
14886 raw_copy_bytes(
14887 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
14888 ws.raw_k1,
14889 ws.local_kv_dim * 4,
14890 root,
14891 )?;
14892 raw_copy_bytes(
14893 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
14894 ws.raw_v1,
14895 ws.local_kv_dim * 4,
14896 root,
14897 )?;
14898 } else if shadows {
14899 let start = ws.local_kv_dim;
14900 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
14901 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
14902 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
14903 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
14904 }
14905 if ws.raw_mixed_stage_e != 0 {
14906 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
14909 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
14910 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
14911 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
14912 }
14913 Ok(())
14914 }
14915
14916 pub(crate) fn decode_v2_arm_token_mirrors(
14919 &self,
14920 ws: &mut StepTpDecodeV2Ws,
14921 mixed_stage_e: u64,
14922 shadow_stage_e: (u64, u64),
14923 ) -> Result<(), Box<dyn std::error::Error>> {
14924 use cudarc::driver::DevicePtr;
14925 let root = &self.ranks[0];
14926 let _main = root.gpu.enter_main()?;
14927 let stream = root.stream();
14928 let (a, _g) = ws.reduce_a.device_ptr(&stream);
14929 ws.raw_reduce_a = a;
14930 ws.raw_mixed_stage_e = mixed_stage_e;
14931 ws.raw_shadow_stage_e = shadow_stage_e;
14932 Ok(())
14933 }
14934
14935 fn nvfp4_routes_build_graph(
14941 &self,
14942 experts: &ResidentNvfp4TensorParallel,
14943 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14944 local_out: usize,
14945 n_sel: usize,
14946 activation_limit: Option<f32>,
14947 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
14948 use cudarc::driver::DevicePtr;
14949 use cudarc::driver::sys;
14950 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14951 if r == sys::CUresult::CUDA_SUCCESS {
14952 Ok(())
14953 } else {
14954 Err(format!("{what}: {r:?}").into())
14955 }
14956 }
14957 let world = self.ranks.len();
14958 if world != 2 {
14959 return Err("routes graph door is built for the TP2 pair".into());
14960 }
14961 let width = experts.input_width;
14962
14963 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
14965 let stream = engine.stream();
14966 let (ptr, _g) = buf.device_ptr(&stream);
14967 ptr
14968 };
14969 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
14970 let stream = engine.stream();
14971 let (ptr, _g) = buf.device_ptr(&stream);
14972 ptr
14973 };
14974 let (sel_e, w_e) = workspace
14975 .dev_route_e
14976 .as_ref()
14977 .expect("device route staging set before graph build");
14978 let root_engine = &self.ranks[0];
14979 let p_in_stage = ptr_f32(
14980 workspace.in_stage_e.as_ref().expect("graph staging"),
14981 root_engine,
14982 );
14983 let p_out_stage = ptr_f32(
14984 workspace.out_stage_e.as_ref().expect("graph staging"),
14985 root_engine,
14986 );
14987 let p_sel_e = ptr_i32(sel_e, root_engine);
14988 let p_w_e = ptr_f32(w_e, root_engine);
14989 let p_input: Vec<u64> = (0..world)
14990 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
14991 .collect();
14992 let p_sel: Vec<u64> = (0..world)
14993 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
14994 .collect();
14995 let p_route_w: Vec<u64> = (0..world)
14996 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
14997 .collect();
14998 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
14999 let p_remote = ptr_f32(&workspace.remote, root_engine);
15000 let p_combined = ptr_f32(&workspace.combined, root_engine);
15001
15002 let raw_copy = |dst: u64,
15003 src: u64,
15004 bytes: usize,
15005 engine: &Engine|
15006 -> Result<(), Box<dyn std::error::Error>> {
15007 unsafe {
15008 cu_try(
15009 sys::cuMemcpyAsync(
15010 dst as sys::CUdeviceptr,
15011 src as sys::CUdeviceptr,
15012 bytes,
15013 engine.stream().cu_stream() as sys::CUstream,
15014 ),
15015 "routes graph cuMemcpyAsync",
15016 )
15017 }
15018 };
15019
15020 let mut children = Vec::with_capacity(3);
15021 for rank in 0..world {
15022 let engine = &self.ranks[rank];
15023 let _main = engine.gpu.enter_main()?;
15024 let (child, _retained) = engine.capture_graph_retained(|_| {
15025 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
15026 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
15027 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
15028 {
15029 let Nvfp4DeviceRoutesWorkspace {
15030 input, in_q, in_d, ..
15031 } = &mut *workspace;
15032 engine.quantize_q8_1_into(
15033 &input[rank],
15034 1,
15035 width,
15036 &mut in_q[rank],
15037 &mut in_d[rank],
15038 )?;
15039 }
15040 self.nvfp4_routes_batched_sweeps_rank(
15041 experts,
15042 workspace,
15043 &[],
15044 &[],
15045 &[],
15046 local_out,
15047 n_sel,
15048 activation_limit,
15049 true,
15050 rank,
15051 )?;
15052 Ok(())
15053 })?;
15054 children.push(child);
15055 }
15056 {
15057 let root = &self.ranks[0];
15058 let _main = root.gpu.enter_main()?;
15059 let (child, _retained) = root.capture_graph_retained(|_| {
15060 raw_copy(p_remote, p_acc1, width * 4, root)?;
15061 {
15062 let Nvfp4DeviceRoutesWorkspace {
15063 accumulator,
15064 remote,
15065 combined,
15066 ..
15067 } = &mut *workspace;
15068 root.add(&accumulator[0], remote, combined, width)?;
15069 }
15070 raw_copy(p_out_stage, p_combined, width * 4, root)?;
15071 Ok(())
15072 })?;
15073 children.push(child);
15074 }
15075
15076 let mut parent: sys::CUgraph = std::ptr::null_mut();
15077 unsafe {
15078 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
15079 }
15080 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
15081 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
15082 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
15083 unsafe {
15084 cu_try(
15085 sys::cuGraphAddChildGraphNode(
15086 &mut n0,
15087 parent,
15088 std::ptr::null(),
15089 0,
15090 children[0].cu_graph(),
15091 ),
15092 "routes child r0",
15093 )?;
15094 cu_try(
15095 sys::cuGraphAddChildGraphNode(
15096 &mut n1,
15097 parent,
15098 std::ptr::null(),
15099 0,
15100 children[1].cu_graph(),
15101 ),
15102 "routes child r1",
15103 )?;
15104 let deps = [n0, n1];
15105 cu_try(
15106 sys::cuGraphAddChildGraphNode(
15107 &mut n2,
15108 parent,
15109 deps.as_ptr(),
15110 2,
15111 children[2].cu_graph(),
15112 ),
15113 "routes child root",
15114 )?;
15115 }
15116 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
15117 unsafe {
15118 cu_try(
15119 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
15120 "routes instantiate",
15121 )?;
15122 }
15123 Ok(RoutesGraph {
15124 exec,
15125 parent,
15126 _children: children,
15127 })
15128 }
15129
15130 #[allow(clippy::too_many_arguments)]
15134 pub(crate) fn routes_rank_section(
15135 &self,
15136 experts: &ResidentNvfp4TensorParallel,
15137 workspace: &mut Nvfp4DeviceRoutesWorkspace,
15138 raw_input_src: u64,
15139 local_out: usize,
15140 n_sel: usize,
15141 activation_limit: Option<f32>,
15142 rank_index: usize,
15143 ) -> Result<(), Box<dyn std::error::Error>> {
15144 let engine = &self.ranks[rank_index];
15145 {
15146 let _main = engine.gpu.enter_main()?;
15147 let (sel_e_ptr, w_e_ptr) = workspace
15149 .raw_dev_route_e
15150 .ok_or("routes rank section requires armed staging pointers")?;
15151 raw_copy_bytes(
15152 workspace.raw_input[rank_index],
15153 raw_input_src,
15154 experts.input_width * 4,
15155 engine,
15156 )?;
15157 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
15158 raw_copy_bytes(
15159 workspace.raw_route_w[rank_index],
15160 w_e_ptr,
15161 n_sel * 4,
15162 engine,
15163 )?;
15164 {
15165 let Nvfp4DeviceRoutesWorkspace {
15166 input, in_q, in_d, ..
15167 } = &mut *workspace;
15168 engine.quantize_q8_1_into(
15169 &input[rank_index],
15170 1,
15171 experts.input_width,
15172 &mut in_q[rank_index],
15173 &mut in_d[rank_index],
15174 )?;
15175 }
15176 }
15177 self.nvfp4_routes_batched_sweeps_rank(
15178 experts,
15179 workspace,
15180 &[],
15181 &[],
15182 &[],
15183 local_out,
15184 n_sel,
15185 activation_limit,
15186 true,
15187 rank_index,
15188 )
15189 }
15190
15191 pub(crate) fn routes_root_section(
15194 &self,
15195 experts: &ResidentNvfp4TensorParallel,
15196 workspace: &mut Nvfp4DeviceRoutesWorkspace,
15197 ) -> Result<(), Box<dyn std::error::Error>> {
15198 let root = &self.ranks[0];
15199 let _main = root.gpu.enter_main()?;
15200 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
15201 .raw_combine
15202 .ok_or("routes root section requires armed combine pointers")?;
15203 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
15204 {
15205 let Nvfp4DeviceRoutesWorkspace {
15206 accumulator,
15207 remote,
15208 combined,
15209 ..
15210 } = &mut *workspace;
15211 root.add(&accumulator[0], remote, combined, experts.input_width)?;
15212 }
15213 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
15214 Ok(())
15215 }
15216
15217 pub(crate) fn routes_arm_raw(
15220 &self,
15221 experts: &ResidentNvfp4TensorParallel,
15222 workspace: &mut Nvfp4DeviceRoutesWorkspace,
15223 ) -> Result<(), Box<dyn std::error::Error>> {
15224 use cudarc::driver::DevicePtr;
15225 if workspace.raw_dev_route_e.is_some() {
15226 return Ok(());
15227 }
15228 let _ = experts;
15229 let (sel_e, w_e) = workspace
15230 .dev_route_e
15231 .as_ref()
15232 .ok_or("routes staging not armed")?;
15233 let root = &self.ranks[0];
15234 {
15235 let _main = root.gpu.enter_main()?;
15236 let stream = root.stream();
15237 let (a, _g) = sel_e.device_ptr(&stream);
15238 let (b, _g) = w_e.device_ptr(&stream);
15239 workspace.raw_dev_route_e = Some((a, b));
15240 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
15241 let (d, _g) = workspace.remote.device_ptr(&stream);
15242 let (f, _g) = workspace.combined.device_ptr(&stream);
15243 let out_stage = workspace
15244 .out_stage_e
15245 .as_ref()
15246 .ok_or("routes out stage not armed")?;
15247 let (g_, _g) = out_stage.device_ptr(&stream);
15248 workspace.raw_combine = Some((c, d, f, g_));
15249 }
15250 for rank in 0..self.ranks.len() {
15251 let engine = &self.ranks[rank];
15252 let _main = engine.gpu.enter_main()?;
15253 let stream = engine.stream();
15254 let (a, _g) = workspace.input[rank].device_ptr(&stream);
15255 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
15256 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
15257 workspace.raw_input.push(a);
15258 workspace.raw_sel.push(b);
15259 workspace.raw_route_w.push(c);
15260 }
15261 Ok(())
15262 }
15263
15264 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4(
15269 &self,
15270 experts: &ResidentNvfp4TensorParallel,
15271 input: &[f32],
15272 tokens: usize,
15273 selected: &[usize],
15274 route_weights: &[f32],
15275 experts_per_token: usize,
15276 activation_limit: Option<f32>,
15277 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
15278 validate_activations(input, tokens, experts.input_width)?;
15279 let pairs = tokens
15280 .checked_mul(experts_per_token)
15281 .ok_or("NVFP4 TP route count overflow")?;
15282 if selected.len() != pairs || route_weights.len() != pairs {
15283 return Err(format!(
15284 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
15285 {experts_per_token} ({pairs})",
15286 selected.len(),
15287 route_weights.len(),
15288 )
15289 .into());
15290 }
15291 if !route_weights.iter().all(|weight| weight.is_finite()) {
15292 return Err("NVFP4 TP route weights contain a non-finite value".into());
15293 }
15294
15295 let mut output = vec![0.0f32; tokens * experts.input_width];
15296 for token in 0..tokens {
15297 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
15298 for slot in 0..experts_per_token {
15299 let pair = token * experts_per_token + slot;
15300 let expert = selected[pair];
15301 if expert >= experts.expert_count {
15302 return Err(format!(
15303 "NVFP4 TP selected expert {expert} outside 0..{}",
15304 experts.expert_count
15305 )
15306 .into());
15307 }
15308 let gate = if experts.ep2 {
15314 self.run_full_bank_expert_nvfp4(
15315 &experts.gate,
15316 &experts.macros_gate,
15317 expert,
15318 input_row,
15319 )?
15320 } else {
15321 self.run_column_bank_expert_nvfp4(
15322 &experts.gate,
15323 &experts.macros_gate,
15324 expert,
15325 input_row,
15326 )?
15327 };
15328 let up = if experts.ep2 {
15329 self.run_full_bank_expert_nvfp4(
15330 &experts.up,
15331 &experts.macros_up,
15332 expert,
15333 input_row,
15334 )?
15335 } else {
15336 self.run_column_bank_expert_nvfp4(
15337 &experts.up,
15338 &experts.macros_up,
15339 expert,
15340 input_row,
15341 )?
15342 };
15343 let activated: Vec<f32> = gate
15344 .iter()
15345 .zip(&up)
15346 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
15347 .collect();
15348 debug_assert_eq!(activated.len(), experts.expert_width);
15349 let down = if experts.ep2 {
15350 self.run_full_down_expert_nvfp4(
15351 &experts.down,
15352 &experts.macros_down,
15353 expert,
15354 &activated,
15355 )?
15356 } else {
15357 self.run_row_bank_expert_nvfp4(
15358 &experts.down,
15359 &experts.macros_down,
15360 expert,
15361 &activated,
15362 )?
15363 };
15364 let weight = route_weights[pair];
15365 for (sum, value) in output
15366 [token * experts.input_width..(token + 1) * experts.input_width]
15367 .iter_mut()
15368 .zip(down)
15369 {
15370 *sum += weight * value;
15371 }
15372 }
15373 }
15374 Ok(output)
15375 }
15376}
15377
15378#[cfg(test)]
15379mod default_on_door_tests {
15380 use super::door_default_on_value;
15381
15382 #[test]
15393 fn the_default_on_door_parses_every_state_and_names_its_source() {
15394 assert_eq!(
15397 door_default_on_value("MEMRA_TEST_DOOR", None),
15398 (true, "default-on")
15399 );
15400 assert_eq!(
15402 door_default_on_value("MEMRA_TEST_DOOR", Some("1")),
15403 (true, "env=1")
15404 );
15405 assert_eq!(
15407 door_default_on_value("MEMRA_TEST_DOOR", Some("0")),
15408 (false, "env=0 (rollback seam)")
15409 );
15410 for bad in [
15415 "false", "off", "no", "", " 0", "0 ", "00", "true", "2", "-1",
15416 ] {
15417 let (on, source) = door_default_on_value("MEMRA_TEST_DOOR", Some(bad));
15418 assert!(on, "value {bad:?} must NOT disarm a default-ON door");
15419 assert!(
15420 source.contains("default-on") && source.contains("unrecognized"),
15421 "value {bad:?} gave source {source:?}, which does not announce itself as an \
15422 ignored value — a receipt reader would take it for a clean default"
15423 );
15424 }
15425 }
15426}
15427
15428#[cfg(test)]
15429mod bank_v2_layout_tests {
15430 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
15431
15432 #[test]
15442 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
15443 let (out_f, in_f) = (2usize, 128usize);
15445 let row_bytes = nvfp4_row_bytes(in_f);
15446 assert_eq!(row_bytes, 72);
15447 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
15448 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
15449 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
15450 let n_slots = in_f / 32;
15451 for row in 0..out_f {
15452 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
15453 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
15454 for g in 0..n_slots {
15455 let (sblk, h) = (g / 2, g % 2);
15456 let sb = &src[sblk * 36..sblk * 36 + 36];
15457 assert_eq!(
15458 &dst[g * 16..g * 16 + 16],
15459 &sb[4 + 16 * h..4 + 16 * h + 16],
15460 "row {row} slot {g} codes"
15461 );
15462 assert_eq!(
15463 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
15464 &sb[2 * h..2 * h + 2],
15465 "row {row} slot {g} scales"
15466 );
15467 }
15468 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
15470 a.sort_unstable();
15471 b.sort_unstable();
15472 assert_eq!(a, b, "row {row} is not a byte permutation");
15473 }
15474 }
15475}
15476
15477#[cfg(test)]
15478mod tests {
15479
15480 #[test]
15481 fn replicated_row_join_is_strictly_tp2_native_and_nonempty() {
15482 assert!(super::validate_tp2_replicated_row_join(2, true, 4096).is_ok());
15483 assert!(
15484 super::validate_tp2_replicated_row_join(1, true, 4096)
15485 .unwrap_err()
15486 .contains("exactly two ranks")
15487 );
15488 assert!(
15489 super::validate_tp2_replicated_row_join(4, true, 4096)
15490 .unwrap_err()
15491 .contains("exactly two ranks")
15492 );
15493 assert!(
15494 super::validate_tp2_replicated_row_join(2, false, 4096)
15495 .unwrap_err()
15496 .contains("native P2P")
15497 );
15498 assert!(super::validate_tp2_replicated_row_join(2, true, 0).is_err());
15499 }
15500
15501 #[test]
15502 fn door_composition_refuses_first_armed_flag_by_name() {
15503 let table: [(&str, &str); 2] = [
15504 ("MEMRA_DOOR_A", "gated on the unsharded walk only"),
15505 ("MEMRA_DOOR_B", "no sharded branches"),
15506 ];
15507 super::refuse_door_composition("MEMRA_X_TP", &table, |_| false).expect("cold doors pass");
15509 let err = super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_B")
15511 .expect_err("armed door must refuse");
15512 assert_eq!(
15513 err,
15514 "MEMRA_X_TP + MEMRA_DOOR_B: unproven composition, refused (no sharded branches)"
15515 );
15516 super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_C")
15518 .expect("foreign flags are not the matrix");
15519 }
15520
15521 #[test]
15528 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
15529 let (kp, bp) = (0xdead_0000u64, 0u64);
15530 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
15531 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
15532 assert_eq!(
15533 super::retired_rows_tab_key(kp, bp, 20, 2),
15534 super::retired_rows_tab_key(kp, bp, 20, 2),
15535 "same layer and t must hash the same, or the test proves nothing"
15536 );
15537 let a = super::rows_tab_host(&live, 0x9000, true, 1);
15538 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
15539 assert_ne!(a, b, "the two generations write DIFFERENT tables");
15540 assert_eq!(
15542 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
15543 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
15544 "the retired key collides across allocation generations"
15545 );
15546 }
15547
15548 #[test]
15551 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
15552 let parts = [
15553 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
15554 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
15555 ];
15556 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
15557 assert_eq!(
15558 same,
15559 vec![
15560 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
15561 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
15564 "same-session rows share one counter cell and step back t-1-r"
15565 );
15566 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
15567 assert_eq!(
15568 cross,
15569 vec![
15570 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
15571 0x00c1u64, 0x00d1u64, 0x7004, 0,
15572 ],
15573 "cross-session rows get their own counter cell and no step back"
15574 );
15575 }
15576 use super::*;
15577
15578 #[test]
15579 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
15580 let limit = Some(7.0);
15581 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
15582 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
15583 assert!(
15584 step_expert_activation_host(-20.0, 9.0, limit).abs()
15585 < step_expert_activation_host(-20.0, 9.0, None).abs()
15586 );
15587 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
15588 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
15589 assert!(validate_step_expert_activation_limit(limit).is_ok());
15590 }
15591
15592 #[test]
15593 fn moe_residual_host_preserves_official_add_order() {
15594 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
15595 assert_eq!(output, [0.0]);
15596 assert_eq!(
15597 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
15598 "MoE residual lengths residual=1 routed=2 shared=1"
15599 );
15600 }
15601
15602 #[test]
15603 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
15604 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
15605 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
15606 assert_eq!(owners.len(), 4);
15607 for (rank, owner) in owners.iter().enumerate() {
15608 assert_eq!(owner.rank, rank);
15609 assert_eq!(owner.selected, vec![0, 36]);
15610 assert_eq!(owner.token_rows, vec![0, 0]);
15611 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
15612 }
15613 }
15614
15615 #[test]
15616 fn expert_owner_routes_validate_geometry_and_selected_experts() {
15617 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
15618 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
15619 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
15620 assert!(error.contains("outside 0..288"));
15621 }
15622
15623 #[test]
15624 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
15625 let selected = [
15626 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
15627 ];
15628 assert_eq!(
15629 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
15630 16
15631 );
15632 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
15633 assert_eq!(
15634 owners
15635 .iter()
15636 .map(|owner| owner.selected.len())
15637 .collect::<Vec<_>>(),
15638 vec![2, 4, 6, 4]
15639 );
15640 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
15641 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
15642 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
15643 }
15644
15645 #[test]
15646 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
15647 let owner0 = [0usize, 3];
15648 let owner1 = [1usize, 2];
15649 let owners = [owner0.as_slice(), owner1.as_slice()];
15650 assert_eq!(
15651 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
15652 .unwrap(),
15653 WeightedRouteCombineShape {
15654 pairs: 4,
15655 max_pairs: 12,
15656 }
15657 );
15658 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
15659 assert!(
15660 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
15661 .is_err()
15662 );
15663 assert!(
15664 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
15665 .is_err()
15666 );
15667 assert!(
15668 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
15669 .is_err()
15670 );
15671 }
15672
15673 #[test]
15674 fn native_p2p_door_is_strict_and_default_off() {
15675 assert!(!parse_step_tp_native_p2p(None).unwrap());
15676 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
15677 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
15678 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
15679 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
15680 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
15681 }
15682
15683 #[test]
15684 fn bulk_p2p_door_is_strict_and_default_off() {
15685 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
15686 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
15687 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
15688 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
15689 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
15690 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
15691 }
15692
15693 #[test]
15694 fn ep_device_arithmetic_door_is_strict_and_default_off() {
15695 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
15696 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
15697 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
15698 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
15699 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
15700 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
15701 }
15702
15703 #[test]
15704 fn f32_mirror_door_is_strict_and_default_off() {
15705 assert!(!parse_step_tp_f32_mirror(None).unwrap());
15706 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
15707 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
15708 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
15709 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
15710 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
15711 }
15712
15713 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
15714 let codes = (0..out_features * in_features)
15715 .map(|index| (index % 251) as u8)
15716 .collect();
15717 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
15718 .map(|index| index as f32 + 1.0)
15719 .collect();
15720 (codes, scales)
15721 }
15722
15723 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
15724 (0..out_features * in_features)
15725 .flat_map(|value| (value as u16).to_le_bytes())
15726 .collect()
15727 }
15728
15729 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
15730 bytes
15731 .chunks_exact(2)
15732 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
15733 .collect()
15734 }
15735
15736 #[test]
15737 fn bf16_matrix_rejects_wrong_byte_count() {
15738 let bytes = vec![0u8; 4 * 4 * 2 - 1];
15739 let matrix = Bf16Matrix {
15740 bytes: &bytes,
15741 out_features: 4,
15742 in_features: 4,
15743 };
15744 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
15745 }
15746
15747 #[test]
15748 fn replicated_device_rows_require_exact_rank_local_shapes() {
15749 assert_eq!(
15750 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
15751 12_288
15752 );
15753 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
15754 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
15755 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
15756 assert!(
15757 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
15758 );
15759 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
15760 }
15761
15762 #[test]
15763 fn replicated_device_row_refresh_requires_exact_root_source() {
15764 assert_eq!(
15765 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
15766 12_288
15767 );
15768 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
15769 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
15770 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
15771 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
15772 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
15773 }
15774
15775 #[test]
15776 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
15777 for tp in [1, 2, 4, 8] {
15778 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
15779 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
15780 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
15781 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
15782 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
15783 }
15784 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
15785 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
15786 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
15787 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
15788 }
15789
15790 #[test]
15791 fn cache_rows_split_by_token_then_rank() {
15792 let rows = (0u8..24).collect::<Vec<_>>();
15793 assert_eq!(
15794 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
15795 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
15796 );
15797 assert_eq!(
15798 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
15799 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
15800 );
15801 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
15802 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
15803 }
15804
15805 #[test]
15806 fn bf16_column_shard_preserves_contiguous_output_rows() {
15807 let bytes = bf16_matrix_bytes(4, 4);
15808 let matrix = Bf16Matrix {
15809 bytes: &bytes,
15810 out_features: 4,
15811 in_features: 4,
15812 };
15813 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
15814 assert_eq!(shard.out_features, 2);
15815 assert_eq!(shard.in_features, 4);
15816 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
15817 }
15818
15819 #[test]
15820 fn bf16_row_shard_preserves_each_input_column_window() {
15821 let bytes = bf16_matrix_bytes(3, 4);
15822 let matrix = Bf16Matrix {
15823 bytes: &bytes,
15824 out_features: 3,
15825 in_features: 4,
15826 };
15827 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
15828 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
15829 }
15830
15831 #[test]
15832 fn bf16_row_block_preserves_global_column_order() {
15833 let bytes = bf16_matrix_bytes(3, 8);
15834 let matrix = Bf16Matrix {
15835 bytes: &bytes,
15836 out_features: 3,
15837 in_features: 8,
15838 };
15839 let block = bf16_row_block(matrix, 2, 3).unwrap();
15840 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
15841 }
15842
15843 #[test]
15844 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
15845 let (codes, scales) = matrix(1280, 4096);
15846 let matrix = E4m3BlockMatrix {
15847 codes: &codes,
15848 scales: &scales,
15849 out_features: 1280,
15850 in_features: 4096,
15851 };
15852 let shard = column_shard(matrix, 2, 1).unwrap();
15853 assert_eq!(shard.out_features, 640);
15854 assert_eq!(shard.codes, &codes[640 * 4096..]);
15855 assert_eq!(shard.scales, &scales[5 * 32..]);
15856 }
15857
15858 #[test]
15859 fn row_shard_preserves_each_weight_and_scale_column_window() {
15860 let (codes, scales) = matrix(4096, 1280);
15861 let matrix = E4m3BlockMatrix {
15862 codes: &codes,
15863 scales: &scales,
15864 out_features: 4096,
15865 in_features: 1280,
15866 };
15867 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
15868 assert_eq!(shard_codes.len(), 4096 * 640);
15869 assert_eq!(&shard_codes[..640], &codes[640..1280]);
15870 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
15871 assert_eq!(shard_scales.len(), 32 * 5);
15872 assert_eq!(&shard_scales[..5], &scales[5..10]);
15873 assert_eq!(&shard_scales[5..10], &scales[15..20]);
15874 }
15875
15876 #[test]
15877 fn activation_shards_keep_token_rows_separate() {
15878 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
15879 assert_eq!(
15880 activation_shard(&activations, 2, 8, 2, 1),
15881 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
15882 );
15883 }
15884
15885 #[test]
15886 fn expert_bank_selects_expert_major_code_and_scale_planes() {
15887 let expert_count = 2;
15888 let out_features = 128;
15889 let in_features = 128;
15890 let code_stride = out_features * in_features;
15891 let codes: Vec<u8> = (0..expert_count * code_stride)
15892 .map(|index| (index % 251) as u8)
15893 .collect();
15894 let scales = vec![1.0f32, 2.0];
15895 let bank = E4m3ExpertBank {
15896 codes: &codes,
15897 scales: &scales,
15898 expert_count,
15899 out_features,
15900 in_features,
15901 };
15902 bank.validate().unwrap();
15903 let expert = bank.expert(1).unwrap();
15904 assert_eq!(expert.codes, &codes[code_stride..]);
15905 assert_eq!(expert.scales, &[2.0]);
15906 }
15907
15908 #[test]
15909 fn expert_bank_rejects_non_positive_scale() {
15910 let codes = vec![0u8; 128 * 128];
15911 let scales = vec![0.0f32];
15912 let bank = E4m3ExpertBank {
15913 codes: &codes,
15914 scales: &scales,
15915 expert_count: 1,
15916 out_features: 128,
15917 in_features: 128,
15918 };
15919 assert!(bank.validate().unwrap_err().contains("non-positive"));
15920 }
15921
15922 #[test]
15923 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
15924 let expert_count = 2;
15925 let out_features = 256;
15926 let in_features = 128;
15927 let code_stride = out_features * in_features;
15928 let scale_stride = 2;
15929 let codes = (0..expert_count * code_stride)
15930 .map(|index| (index % 251) as u8)
15931 .collect::<Vec<_>>();
15932 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15933 let bank = E4m3ExpertBank {
15934 codes: &codes,
15935 scales: &scales,
15936 expert_count,
15937 out_features,
15938 in_features,
15939 };
15940
15941 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
15942 assert_eq!(rank.out_features, 128);
15943 assert_eq!(rank.in_features, 128);
15944 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15945 assert_eq!(rank.scales, vec![11.0, 21.0]);
15946 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
15947 assert_eq!(
15948 &rank.codes[128 * 128..],
15949 &codes[code_stride + 128 * 128..2 * code_stride]
15950 );
15951 assert_eq!(scale_stride, scales.len() / expert_count);
15952 }
15953
15954 #[test]
15955 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
15956 let expert_count = 2;
15957 let out_features = 128;
15958 let in_features = 256;
15959 let code_stride = out_features * in_features;
15960 let codes = (0..expert_count * code_stride)
15961 .map(|index| (index % 251) as u8)
15962 .collect::<Vec<_>>();
15963 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15964 let bank = E4m3ExpertBank {
15965 codes: &codes,
15966 scales: &scales,
15967 expert_count,
15968 out_features,
15969 in_features,
15970 };
15971
15972 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15973 assert_eq!(rank.out_features, 128);
15974 assert_eq!(rank.in_features, 128);
15975 assert_eq!(rank.k_blocks, Some(1));
15976 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15977 assert_eq!(rank.scales, vec![11.0, 21.0]);
15978 assert_eq!(&rank.codes[..128], &codes[128..256]);
15979 assert_eq!(
15980 &rank.codes[128 * 128..128 * 128 + 128],
15981 &codes[code_stride + 128..code_stride + 256]
15982 );
15983 }
15984
15985 #[test]
15986 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
15987 let expert_count = 2;
15988 let out_features = 256;
15989 let in_features = 512;
15990 let code_stride = out_features * in_features;
15991 let mut codes = vec![0u8; expert_count * code_stride];
15992 for expert in 0..expert_count {
15993 for row in 0..out_features {
15994 for block in 0..4 {
15995 let value = (expert * 80 + block * 16 + row % 16) as u8;
15996 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
15997 codes[start..start + FP8_BLOCK].fill(value);
15998 }
15999 }
16000 }
16001 let scales = vec![
16002 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,
16003 112.0, 113.0, 114.0,
16004 ];
16005 let bank = E4m3ExpertBank {
16006 codes: &codes,
16007 scales: &scales,
16008 expert_count,
16009 out_features,
16010 in_features,
16011 };
16012
16013 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
16014 assert_eq!(rank.out_features, out_features);
16015 assert_eq!(rank.in_features, 256);
16016 assert_eq!(rank.k_blocks, Some(2));
16017 assert_eq!(rank.code_stride, out_features * 256);
16018 assert_eq!(rank.scale_stride, 4);
16019 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
16020 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
16021
16022 let block_stride = out_features * FP8_BLOCK;
16023 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
16024 assert!(
16025 rank.codes[block_stride..block_stride + FP8_BLOCK]
16026 .iter()
16027 .all(|&code| code == 48)
16028 );
16029 assert!(
16030 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
16031 .iter()
16032 .all(|&code| code == 112)
16033 );
16034 assert!(
16035 rank.codes
16036 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
16037 .iter()
16038 .all(|&code| code == 128)
16039 );
16040 }
16041
16042 #[test]
16043 fn automatic_parallel_policy_needs_only_one_device_set_not_layer_recipes() {
16044 assert_eq!(parse_auto_parallel_devices(None, None).unwrap(), None);
16045 assert_eq!(
16046 parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3")).unwrap(),
16047 Some(vec![0, 1, 2, 3])
16048 );
16049 assert!(parse_auto_parallel_devices(Some("auto"), None).is_err());
16050 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,1")).is_err());
16051 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3,4")).is_err());
16052 assert!(parse_auto_parallel_devices(Some("ep"), Some("0,1")).is_err());
16053 }
16054
16055 #[test]
16056 fn automatic_ep_device_router_flag_is_strict() {
16057 assert!(!parse_parallel_ep_device_router(None).unwrap());
16058 assert!(!parse_parallel_ep_device_router(Some("0")).unwrap());
16059 assert!(parse_parallel_ep_device_router(Some("1")).unwrap());
16060 assert!(parse_parallel_ep_device_router(Some("true")).is_err());
16061 }
16062
16063 #[test]
16064 fn automatic_ep_graph_flag_is_strict_and_defaults_off() {
16065 assert!(!parse_parallel_ep_graph(None).unwrap());
16066 assert!(!parse_parallel_ep_graph(Some("0")).unwrap());
16067 assert!(parse_parallel_ep_graph(Some("1")).unwrap());
16068 assert!(parse_parallel_ep_graph(Some("true")).is_err());
16069 }
16070
16071 #[test]
16072 fn automatic_ep_pair_down_flag_is_strict_and_defaults_off() {
16073 assert!(!parse_parallel_ep_pair_down(None).unwrap());
16074 assert!(!parse_parallel_ep_pair_down(Some("0")).unwrap());
16075 assert!(parse_parallel_ep_pair_down(Some("1")).unwrap());
16076 assert!(parse_parallel_ep_pair_down(Some("true")).is_err());
16077 }
16078
16079 #[test]
16080 fn automatic_ep_q8_activation_flag_is_strict() {
16081 assert!(!parse_parallel_ep_q8_act(None).unwrap());
16082 assert!(!parse_parallel_ep_q8_act(Some("0")).unwrap());
16083 assert!(parse_parallel_ep_q8_act(Some("1")).unwrap());
16084 assert!(parse_parallel_ep_q8_act(Some("true")).is_err());
16085 }
16086
16087 #[test]
16088 fn automatic_ep_q8_scope_is_explicit_and_strict() {
16089 assert_eq!(parse_parallel_ep_q8_scope(None).unwrap(), None);
16090 assert_eq!(
16091 parse_parallel_ep_q8_scope(Some("all")).unwrap(),
16092 Some(ParallelEpQ8Scope::All)
16093 );
16094 assert_eq!(
16095 parse_parallel_ep_q8_scope(Some("gate-up")).unwrap(),
16096 Some(ParallelEpQ8Scope::GateUp)
16097 );
16098 assert_eq!(
16099 parse_parallel_ep_q8_scope(Some("down")).unwrap(),
16100 Some(ParallelEpQ8Scope::Down)
16101 );
16102 assert!(parse_parallel_ep_q8_scope(Some("input")).is_err());
16103 }
16104
16105 #[test]
16106 fn automatic_ep_q8_gate_up_paired_is_parent_scoped_and_strict() {
16107 assert_eq!(parse_parallel_ep_q8_gu_paired(None).unwrap(), None);
16108 assert_eq!(parse_parallel_ep_q8_gu_paired(Some("")).unwrap(), None);
16109 assert_eq!(
16110 parse_parallel_ep_q8_gu_paired(Some("0")).unwrap(),
16111 Some(false)
16112 );
16113 assert_eq!(
16114 parse_parallel_ep_q8_gu_paired(Some("1")).unwrap(),
16115 Some(true)
16116 );
16117 assert!(parse_parallel_ep_q8_gu_paired(Some("paired")).is_err());
16118 assert!(parse_parallel_ep_q8_gu_paired(Some("true")).is_err());
16119
16120 assert!(!resolve_parallel_ep_q8_gu_paired(None, false, None).unwrap());
16121 assert!(resolve_parallel_ep_q8_gu_paired(None, true, None).unwrap());
16122 assert!(
16123 resolve_parallel_ep_q8_gu_paired(None, true, Some(ParallelEpQ8Scope::GateUp)).unwrap()
16124 );
16125 assert!(
16126 !resolve_parallel_ep_q8_gu_paired(None, true, Some(ParallelEpQ8Scope::Down)).unwrap()
16127 );
16128 assert!(!resolve_parallel_ep_q8_gu_paired(Some("0"), false, None).unwrap());
16129 assert!(!resolve_parallel_ep_q8_gu_paired(Some("0"), true, None).unwrap());
16130 assert!(resolve_parallel_ep_q8_gu_paired(Some("1"), false, None).is_err());
16131 assert!(
16132 resolve_parallel_ep_q8_gu_paired(Some("1"), true, Some(ParallelEpQ8Scope::Down))
16133 .is_err()
16134 );
16135 }
16136
16137 #[test]
16138 fn w4a16_device_ep_accepts_a_capacity_backed_active_prefix() {
16139 let width = 4096;
16140 assert_eq!(
16141 nvfp4_ep_active_input_values(160 * width, 44, width).unwrap(),
16142 44 * width
16143 );
16144 assert_eq!(
16145 nvfp4_ep_active_input_values(44 * width, 44, width).unwrap(),
16146 44 * width
16147 );
16148 assert!(nvfp4_ep_active_input_values(43 * width, 44, width).is_err());
16149 assert!(
16150 nvfp4_ep_active_input_values(160 * width, NVFP4_EP_DEVICE_BATCH_CAP + 1, width)
16151 .is_err()
16152 );
16153 }
16154
16155 #[test]
16156 fn step_ep_layer_specs_are_literal_and_fail_closed() {
16157 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
16158 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
16159 assert_eq!(
16160 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
16161 vec![StepEpLayerSpec {
16162 layer: 24,
16163 devices: vec![1, 2],
16164 }]
16165 );
16166 assert_eq!(
16167 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
16168 vec![
16169 StepEpLayerSpec {
16170 layer: 24,
16171 devices: vec![1, 2],
16172 },
16173 StepEpLayerSpec {
16174 layer: 25,
16175 devices: vec![1, 2],
16176 },
16177 StepEpLayerSpec {
16178 layer: 31,
16179 devices: vec![0, 2],
16180 },
16181 ]
16182 );
16183 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
16184 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
16185 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
16186 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
16187 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
16188 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
16189 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
16190 }
16191
16192 #[test]
16193 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
16194 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
16195 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
16196 assert_eq!(
16197 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
16198 vec![
16199 StepTpLayerSpec {
16200 layer: 24,
16201 devices: vec![1, 2],
16202 },
16203 StepTpLayerSpec {
16204 layer: 25,
16205 devices: vec![1, 2],
16206 },
16207 ]
16208 );
16209 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
16210 assert!(error.contains("MEMRA_STEP_TP"));
16211 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
16212 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
16213
16214 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
16215 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
16216 assert_eq!(all.first().unwrap().layer, 0);
16217 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
16218 let devices = (0..8).collect::<Vec<_>>();
16219 assert!(all.iter().all(|spec| spec.devices == devices));
16220 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
16221 }
16222}
16223
16224struct TokenGraphChild {
16236 #[allow(dead_code)]
16237 graph: cudarc::driver::CudaGraph,
16239 node: cudarc::driver::sys::CUgraphNode,
16240 ctx: cudarc::driver::sys::CUcontext,
16241}
16242
16243struct TokenGraphFaSite {
16247 ctx: cudarc::driver::sys::CUcontext,
16248 memset_o: cudarc::driver::sys::CUgraphNode,
16249 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
16250 fa: cudarc::driver::sys::CUgraphNode,
16251 combine: cudarc::driver::sys::CUgraphNode,
16252 window: usize,
16253 n_head: usize,
16254 n_head_kv: usize,
16255 head_dim: usize,
16256}
16257
16258pub struct TokenGraphBuilder {
16259 parent: cudarc::driver::sys::CUgraph,
16260 children: Vec<TokenGraphChild>,
16261 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
16264 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
16267 group: Option<(
16270 u32,
16271 Vec<cudarc::driver::sys::CUgraphNode>,
16272 Vec<cudarc::driver::sys::CUgraphNode>,
16273 )>,
16274}
16275
16276unsafe impl Send for TokenGraphBuilder {}
16278
16279impl TokenGraphBuilder {
16280 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
16281 use cudarc::driver::sys;
16282 let mut parent: sys::CUgraph = std::ptr::null_mut();
16283 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
16284 if r != sys::CUresult::CUDA_SUCCESS {
16285 return Err(format!("token graph create: {r:?}").into());
16286 }
16287 Ok(Self {
16288 parent,
16289 children: Vec::new(),
16290 frontier: Vec::new(),
16291 pending_detached: Vec::new(),
16292 group: None,
16293 })
16294 }
16295
16296 fn push_child(
16297 &mut self,
16298 graph: cudarc::driver::CudaGraph,
16299 parallel_group: Option<u32>,
16300 detached: bool,
16301 absorb: bool,
16302 ctx: cudarc::driver::sys::CUcontext,
16303 ) -> Result<(), Box<dyn std::error::Error>> {
16304 use cudarc::driver::sys;
16305 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
16309 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
16310 (state, Some(group)) => {
16311 if let Some((_, _, members)) = state.take() {
16313 self.frontier = members;
16314 }
16315 let base = self.frontier.clone();
16316 *state = Some((group, base.clone(), Vec::new()));
16317 base
16318 }
16319 (state, None) if detached => match state.as_ref() {
16320 Some((_, base, _)) => base.clone(),
16321 None => self.frontier.clone(),
16322 },
16323 (state, None) => {
16324 if let Some((_, _, members)) = state.take() {
16325 self.frontier = members;
16326 }
16327 let mut deps = self.frontier.clone();
16328 if absorb {
16329 deps.append(&mut self.pending_detached);
16330 }
16331 deps
16332 }
16333 };
16334 let mut node: sys::CUgraphNode = std::ptr::null_mut();
16335 let r = unsafe {
16336 sys::cuGraphAddChildGraphNode(
16337 &mut node,
16338 self.parent,
16339 if deps.is_empty() {
16340 std::ptr::null()
16341 } else {
16342 deps.as_ptr()
16343 },
16344 deps.len(),
16345 graph.cu_graph(),
16346 )
16347 };
16348 if r != sys::CUresult::CUDA_SUCCESS {
16349 return Err(format!("token graph child: {r:?}").into());
16350 }
16351 match (&mut self.group, parallel_group, detached) {
16352 (_, None, true) => self.pending_detached.push(node),
16353 (Some((_, _, members)), Some(_), _) => members.push(node),
16354 _ => self.frontier = vec![node],
16355 }
16356 self.children.push(TokenGraphChild { graph, node, ctx });
16357 Ok(())
16358 }
16359
16360 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
16361 use cudarc::driver::sys;
16362 if let Some((_, _, members)) = self.group.take() {
16363 self.frontier = members;
16364 }
16365 let mut fa_sites = Vec::new();
16368 for child in &self.children {
16369 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
16370 fa_sites.push(site);
16371 }
16372 }
16373 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
16374 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
16375 if r != sys::CUresult::CUDA_SUCCESS {
16376 return Err(format!("token graph instantiate: {r:?}").into());
16377 }
16378 Ok(TokenGraph {
16379 exec,
16380 parent: self.parent,
16381 _children: self.children,
16382 fa_sites,
16383 })
16384 }
16385}
16386
16387fn discover_fa_site(
16390 child_node: cudarc::driver::sys::CUgraphNode,
16391 ctx: cudarc::driver::sys::CUcontext,
16392) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
16393 use cudarc::driver::sys;
16394 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16395 if r == sys::CUresult::CUDA_SUCCESS {
16396 Ok(())
16397 } else {
16398 Err(format!("{what}: {r:?}").into())
16399 }
16400 }
16401 let mut graph: sys::CUgraph = std::ptr::null_mut();
16402 unsafe {
16403 cu_try(
16404 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
16405 "fa-site child GetGraph",
16406 )?;
16407 }
16408 let mut count: usize = 0;
16409 unsafe {
16410 cu_try(
16411 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
16412 "fa-site GetNodes(count)",
16413 )?;
16414 }
16415 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
16416 unsafe {
16417 cu_try(
16418 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
16419 "fa-site GetNodes",
16420 )?;
16421 }
16422 nodes.truncate(count);
16423 let node_type =
16424 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
16425 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
16426 unsafe {
16427 cu_try(
16428 sys::cuGraphNodeGetType(node, &mut ty),
16429 "fa-site NodeGetType",
16430 )?;
16431 }
16432 Ok(ty)
16433 };
16434 let memsets: Vec<sys::CUgraphNode> = {
16435 let mut v = Vec::new();
16436 for &node in &nodes {
16437 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
16438 v.push(node);
16439 }
16440 }
16441 v
16442 };
16443 if memsets.len() != 3 {
16444 return Ok(None);
16445 }
16446 let dependents =
16448 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
16449 let mut n: usize = 0;
16450 unsafe {
16451 cu_try(
16452 sys::cuGraphNodeGetDependentNodes_v2(
16453 node,
16454 std::ptr::null_mut(),
16455 std::ptr::null_mut(),
16456 &mut n,
16457 ),
16458 "fa-site GetDependentNodes(count)",
16459 )?;
16460 }
16461 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
16462 unsafe {
16463 cu_try(
16464 sys::cuGraphNodeGetDependentNodes_v2(
16465 node,
16466 v.as_mut_ptr(),
16467 std::ptr::null_mut(),
16468 &mut n,
16469 ),
16470 "fa-site GetDependentNodes",
16471 )?;
16472 }
16473 v.truncate(n);
16474 Ok(v)
16475 };
16476 let mut fa: Option<sys::CUgraphNode> = None;
16479 let mut last_memset: Option<sys::CUgraphNode> = None;
16480 for &ms in &memsets {
16481 for dep in dependents(ms)? {
16482 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16483 fa = Some(dep);
16484 last_memset = Some(ms);
16485 }
16486 }
16487 }
16488 let (Some(fa), Some(_last)) = (fa, last_memset) else {
16489 return Ok(None);
16490 };
16491 let mut combine: Option<sys::CUgraphNode> = None;
16492 for dep in dependents(fa)? {
16493 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16494 combine = Some(dep);
16495 }
16496 }
16497 let Some(combine) = combine else {
16498 return Ok(None);
16499 };
16500 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16503 unsafe {
16504 cu_try(
16505 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
16506 "fa-site KernelNodeGetParams",
16507 )?;
16508 }
16509 let arg_i32 =
16510 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
16511 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
16512 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
16514 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16515 unsafe {
16516 cu_try(
16517 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16518 "fa-site MemsetNodeGetParams",
16519 )?;
16520 }
16521 Ok(mp.width)
16522 };
16523 let mut widest = memsets[0];
16524 for &ms in &memsets[1..] {
16525 if width_of(ms)? > width_of(widest)? {
16526 widest = ms;
16527 }
16528 }
16529 let memset_m: Vec<sys::CUgraphNode> =
16530 memsets.iter().copied().filter(|&m| m != widest).collect();
16531 Ok(Some(TokenGraphFaSite {
16532 ctx,
16533 memset_o: widest,
16534 memset_m: [memset_m[0], memset_m[1]],
16535 fa,
16536 combine,
16537 window: win as usize,
16538 n_head: nh as usize,
16539 n_head_kv: nhkv as usize,
16540 head_dim: hd as usize,
16541 }))
16542}
16543
16544pub struct TokenGraph {
16545 exec: cudarc::driver::sys::CUgraphExec,
16546 parent: cudarc::driver::sys::CUgraph,
16547 _children: Vec<TokenGraphChild>,
16548 fa_sites: Vec<TokenGraphFaSite>,
16549}
16550
16551unsafe impl Send for TokenGraph {}
16552
16553impl TokenGraph {
16554 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
16559 use cudarc::driver::sys;
16560 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16561 if r == sys::CUresult::CUDA_SUCCESS {
16562 Ok(())
16563 } else {
16564 Err(format!("{what}: {r:?}").into())
16565 }
16566 }
16567 for site in &self.fa_sites {
16568 let layer_bucket = if site.window > 0 {
16569 bucket.min(site.window)
16570 } else {
16571 bucket
16572 };
16573 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
16574 let nsp = layer_bucket.div_ceil(sp).max(1);
16575 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16577 unsafe {
16578 cu_try(
16579 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
16580 "retarget fa GetParams",
16581 )?;
16582 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
16583 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
16584 params.gridDimY = nsp as u32;
16585 cu_try(
16586 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
16587 "retarget fa SetParams",
16588 )?;
16589 }
16590 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16592 unsafe {
16593 cu_try(
16594 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
16595 "retarget combine GetParams",
16596 )?;
16597 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
16598 cu_try(
16599 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
16600 "retarget combine SetParams",
16601 )?;
16602 }
16603 let set_width =
16605 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
16606 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16607 unsafe {
16608 cu_try(
16609 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16610 "retarget memset GetParams",
16611 )?;
16612 }
16613 mp.width = width;
16614 unsafe {
16615 cu_try(
16616 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
16617 "retarget memset SetParams",
16618 )?;
16619 }
16620 Ok(())
16621 };
16622 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
16623 set_width(site.memset_m[0], site.n_head * nsp)?;
16624 set_width(site.memset_m[1], site.n_head * nsp)?;
16625 }
16626 Ok(())
16627 }
16628
16629 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
16630 use cudarc::driver::sys;
16631 let _main = e.gpu.enter_main()?;
16632 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
16633 if r != sys::CUresult::CUDA_SUCCESS {
16634 return Err(format!("token graph launch: {r:?}").into());
16635 }
16636 Ok(())
16637 }
16638}
16639
16640impl Drop for TokenGraph {
16641 fn drop(&mut self) {
16642 unsafe {
16643 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
16644 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
16645 }
16646 }
16647}
16648
16649std::thread_local! {
16650 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
16651 const { std::cell::RefCell::new(None) };
16652}
16653
16654pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
16656 let builder = TokenGraphBuilder::new()?;
16657 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
16658 Ok(())
16659}
16660
16661pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
16663 let builder = TOKEN_GRAPH_BUILDER
16664 .with(|cell| cell.borrow_mut().take())
16665 .ok_or("token graph build was not begun")?;
16666 builder.finish()
16667}
16668
16669pub fn token_graph_building() -> bool {
16671 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
16672}
16673
16674pub fn graph_section<F>(
16679 engine: &Engine,
16680 parallel_group: Option<u32>,
16681 f: F,
16682) -> Result<(), Box<dyn std::error::Error>>
16683where
16684 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16685{
16686 graph_section_opts(engine, parallel_group, false, false, f)
16687}
16688
16689pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16691where
16692 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16693{
16694 graph_section_opts(engine, None, false, true, f)
16695}
16696
16697pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16700where
16701 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16702{
16703 graph_section_opts(engine, None, true, false, f)
16704}
16705
16706pub fn graph_section_opts<F>(
16707 engine: &Engine,
16708 parallel_group: Option<u32>,
16709 detached: bool,
16710 absorb: bool,
16711 f: F,
16712) -> Result<(), Box<dyn std::error::Error>>
16713where
16714 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16715{
16716 let building = token_graph_building();
16717 if !building {
16718 let mut f = f;
16719 return f();
16720 }
16721 let (child, ctx) = {
16722 let _main = engine.gpu.enter_main()?;
16723 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
16724 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
16725 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
16726 return Err(format!("graph section ctx query: {r:?}").into());
16727 }
16728 let mut f = f;
16729 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
16732 (child, ctx)
16733 };
16734 TOKEN_GRAPH_BUILDER.with(|cell| {
16735 cell.borrow_mut()
16736 .as_mut()
16737 .expect("builder checked above")
16738 .push_child(child, parallel_group, detached, absorb, ctx)
16739 })
16740}