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(ring_base) = cache.ring_base() {
2699 let base_val = ring_base as i32;
2700 for rank in 0..self.ranks.len() {
2701 let engine = &self.ranks[rank];
2702 let _main = engine.gpu.enter_main()?;
2703 let rank_cache = cache
2704 .rank_mut(rank)
2705 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2706 if rank_cache.base_d().is_none() {
2707 rank_cache.arm_base_d(engine.htod_i32(&[base_val])?);
2708 }
2709 }
2710 }
2711 if let Some(KvRingAppend::Rebase {
2712 src_row,
2713 keep_rows,
2714 new_base,
2715 ..
2716 }) = plan.ring_append()
2717 {
2718 for rank in 0..self.ranks.len() {
2719 let engine = &self.ranks[rank];
2720 let _main = engine.gpu.enter_main()?;
2721 let rank_cache = cache
2722 .rank_mut(rank)
2723 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2724 if keep_rows > 0 {
2725 let k_len = keep_rows
2726 .checked_mul(k_tok_bytes)
2727 .ok_or("TP KV K rebase-byte overflow")?;
2728 let v_len = keep_rows
2729 .checked_mul(v_tok_bytes)
2730 .ok_or("TP KV V rebase-byte overflow")?;
2731 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
2732 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2733 engine.copy_u8_range_into(
2734 &mut k_tmp,
2735 0,
2736 rank_cache.k(),
2737 src_row * k_tok_bytes,
2738 k_len,
2739 )?;
2740 engine.copy_u8_range_into(
2741 &mut v_tmp,
2742 0,
2743 rank_cache.v(),
2744 src_row * v_tok_bytes,
2745 v_len,
2746 )?;
2747 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2748 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2749 }
2750 let value = new_base as i32;
2754 if let Some(base_d) = rank_cache.base_d_mut() {
2755 engine.set_i32_one(base_d, value)?;
2756 } else {
2757 rank_cache.arm_base_d(engine.htod_i32(&[value])?);
2758 }
2759 }
2760 }
2761 cache.publish_append_rebase(plan)?;
2762 let write_row = plan.write_row();
2763 for rank in 0..self.ranks.len() {
2764 if external_rank_appends {
2765 break;
2766 }
2767 let engine = &self.ranks[rank];
2768 let _main = engine.gpu.enter_main()?;
2769 if k_shards[rank].len() != expected_k
2770 || v_shards[rank].len() != expected_v
2771 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2772 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2773 {
2774 return Err(format!(
2775 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2776 != expected {expected_k}/{expected_v} on device {}",
2777 k_shards[rank].len(),
2778 k_shards[rank].ordinal(),
2779 v_shards[rank].len(),
2780 v_shards[rank].ordinal(),
2781 engine.ctx().ordinal(),
2782 )
2783 .into());
2784 }
2785 let rank_cache = cache
2786 .rank_mut(rank)
2787 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2788 let (rank_k, rank_v) = rank_cache.planes_mut();
2789 engine.append_kv_quantized_rows(
2790 &k_shards[rank],
2791 &v_shards[rank],
2792 rank_k,
2793 rank_v,
2794 write_row,
2795 rows,
2796 kv_dim_k,
2797 kv_dim_v,
2798 k_tok_bytes,
2799 v_tok_bytes,
2800 Engine::kv_fp8_on(),
2801 )?;
2802 }
2803 if !external_rank_appends {
2804 self.set_tp_kv_len_mirrors(cache, target)?;
2807 }
2808 cache.publish_append_plan(plan)?;
2809 Ok(())
2810 }
2811
2812 pub fn commit_tp_kv_transaction(
2813 &self,
2814 cache: &mut ResidentTpKvCache,
2815 transaction: TpKvTransaction,
2816 accepted_rows: usize,
2817 ) -> Result<(), Box<dyn std::error::Error>> {
2818 self.validate_tp_kv_cache(cache)?;
2819 let target = cache.commit_target(transaction, accepted_rows)?;
2820 self.set_tp_kv_len_mirrors(cache, target)?;
2821 cache.publish_finalize(transaction, target)?;
2822 cache.mark_rows_external(false);
2825 Ok(())
2826 }
2827
2828 pub fn commit_tp_kv_transaction_external(
2834 &self,
2835 cache: &mut ResidentTpKvCache,
2836 transaction: TpKvTransaction,
2837 accepted_rows: usize,
2838 ) -> Result<(), Box<dyn std::error::Error>> {
2839 self.validate_tp_kv_cache(cache)?;
2840 let target = cache.commit_target(transaction, accepted_rows)?;
2841 cache.publish_finalize(transaction, target)?;
2842 cache.mark_rows_external(true);
2846 Ok(())
2847 }
2848
2849 pub fn rollback_tp_kv_transaction(
2850 &self,
2851 cache: &mut ResidentTpKvCache,
2852 transaction: TpKvTransaction,
2853 ) -> Result<(), Box<dyn std::error::Error>> {
2854 self.validate_tp_kv_cache(cache)?;
2855 cache.validate_transaction(transaction)?;
2856 let target = transaction.base_len();
2857 self.set_tp_kv_len_mirrors(cache, target)?;
2858 cache.publish_finalize(transaction, target)?;
2859 Ok(())
2860 }
2861
2862 pub fn tp_kv_device_lengths(
2863 &self,
2864 cache: &ResidentTpKvCache,
2865 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2866 self.validate_tp_kv_cache(cache)?;
2867 let mut lengths = Vec::with_capacity(self.ranks.len());
2868 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2869 let _main = engine.gpu.enter_main()?;
2870 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2871 }
2872 Ok(lengths)
2873 }
2874
2875 fn set_tp_kv_len_mirrors(
2876 &self,
2877 cache: &mut ResidentTpKvCache,
2878 len: usize,
2879 ) -> Result<(), Box<dyn std::error::Error>> {
2880 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2881 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2882 let _main = engine.gpu.enter_main()?;
2883 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2884 }
2885 Ok(())
2886 }
2887
2888 fn validate_tp_kv_cache(
2889 &self,
2890 cache: &ResidentTpKvCache,
2891 ) -> Result<(), Box<dyn std::error::Error>> {
2892 if cache.ranks_len() != self.ranks.len() {
2893 return Err(format!(
2894 "TP KV cache ranks {} != runtime ranks {}",
2895 cache.ranks_len(),
2896 self.ranks.len()
2897 )
2898 .into());
2899 }
2900 let expected_k = cache
2901 .physical_capacity()
2902 .checked_mul(cache.k_tok_bytes())
2903 .and_then(|bytes| bytes.checked_add(8))
2904 .ok_or("TP KV K plane validation overflow")?;
2905 let expected_v = cache
2906 .physical_capacity()
2907 .checked_mul(cache.v_tok_bytes())
2908 .and_then(|bytes| bytes.checked_add(8))
2909 .ok_or("TP KV V plane validation overflow")?;
2910 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2911 let device = engine.ctx().ordinal();
2912 if rank_cache.k().len() != expected_k
2913 || rank_cache.v().len() != expected_v
2914 || rank_cache.len_d().len() != 1
2915 || rank_cache.k().ordinal() != device
2916 || rank_cache.v().ordinal() != device
2917 || rank_cache.len_d().ordinal() != device
2918 {
2919 return Err(format!(
2920 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2921 )
2922 .into());
2923 }
2924 }
2925 Ok(())
2926 }
2927
2928 pub fn full(
2929 &self,
2930 matrix: E4m3BlockMatrix<'_>,
2931 activations: &[f32],
2932 tokens: usize,
2933 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2934 matrix.validate()?;
2935 validate_activations(activations, tokens, matrix.in_features)?;
2936 run_rank(&self.ranks[0], matrix, activations, tokens)
2937 }
2938
2939 #[allow(clippy::manual_is_multiple_of)] pub fn column_parallel(
2944 &self,
2945 matrix: E4m3BlockMatrix<'_>,
2946 activations: &[f32],
2947 tokens: usize,
2948 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2949 matrix.validate()?;
2950 validate_activations(activations, tokens, matrix.in_features)?;
2951 let tp = self.ranks.len();
2952 if matrix.out_features % tp != 0 {
2953 return Err(format!(
2954 "column-parallel out_features {} is not divisible by TP={tp}",
2955 matrix.out_features
2956 )
2957 .into());
2958 }
2959 let local_out = matrix.out_features / tp;
2960 if !local_out.is_multiple_of(FP8_BLOCK) {
2961 return Err(format!(
2962 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2963 E4M3 scale block"
2964 )
2965 .into());
2966 }
2967
2968 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2969 let mut rank_outputs = Vec::with_capacity(tp);
2970 for (rank_index, rank) in self.ranks.iter().enumerate() {
2971 let shard = column_shard(matrix, tp, rank_index)?;
2972 let output = run_rank(rank, shard, activations, tokens)?;
2973 let row_start = rank_index * local_out;
2974 for token in 0..tokens {
2975 gathered[token * matrix.out_features + row_start
2976 ..token * matrix.out_features + row_start + local_out]
2977 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2978 }
2979 rank_outputs.push(output);
2980 }
2981 Ok(ColumnParallelResult {
2982 gathered,
2983 rank_outputs,
2984 })
2985 }
2986
2987 pub fn upload_column_parallel(
2988 &self,
2989 matrix: E4m3BlockMatrix<'_>,
2990 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2991 matrix.validate()?;
2992 let tp = self.ranks.len();
2993 validate_column_shape(matrix, tp)?;
2994 let mut ranks = Vec::with_capacity(tp);
2995 for (rank_index, engine) in self.ranks.iter().enumerate() {
2996 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2997 }
2998 Ok(ResidentColumnParallel {
2999 ranks,
3000 out_features: matrix.out_features,
3001 in_features: matrix.in_features,
3002 })
3003 }
3004
3005 pub fn column_parallel_resident(
3006 &self,
3007 matrix: &ResidentColumnParallel,
3008 activations: &[f32],
3009 tokens: usize,
3010 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
3011 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
3012 validate_activations(activations, tokens, matrix.in_features)?;
3013 let local_out = matrix.out_features / self.ranks.len();
3014 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
3015 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3016 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3017 let output = run_resident_rank(engine, shard, activations, tokens)?;
3018 let row_start = rank_index * local_out;
3019 for token in 0..tokens {
3020 gathered[token * matrix.out_features + row_start
3021 ..token * matrix.out_features + row_start + local_out]
3022 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
3023 }
3024 rank_outputs.push(output);
3025 }
3026 Ok(ColumnParallelResult {
3027 gathered,
3028 rank_outputs,
3029 })
3030 }
3031
3032 #[allow(clippy::manual_is_multiple_of)] pub fn row_parallel(
3037 &self,
3038 matrix: E4m3BlockMatrix<'_>,
3039 activations: &[f32],
3040 tokens: usize,
3041 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3042 matrix.validate()?;
3043 validate_activations(activations, tokens, matrix.in_features)?;
3044 let tp = self.ranks.len();
3045 if matrix.in_features % tp != 0 {
3046 return Err(format!(
3047 "row-parallel in_features {} is not divisible by TP={tp}",
3048 matrix.in_features
3049 )
3050 .into());
3051 }
3052 let local_in = matrix.in_features / tp;
3053 if !local_in.is_multiple_of(FP8_BLOCK) {
3054 return Err(format!(
3055 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
3056 E4M3 scale block"
3057 )
3058 .into());
3059 }
3060
3061 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3062 let mut rank_partials = Vec::with_capacity(tp);
3063 for (rank_index, rank) in self.ranks.iter().enumerate() {
3064 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
3065 let local_activations =
3066 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
3067 let shard = E4m3BlockMatrix {
3068 codes: &codes,
3069 scales: &scales,
3070 out_features: matrix.out_features,
3071 in_features: local_in,
3072 };
3073 let partial = run_rank(rank, shard, &local_activations, tokens)?;
3074 for (sum, value) in reduced.iter_mut().zip(&partial) {
3075 *sum += *value;
3076 }
3077 rank_partials.push(partial);
3078 }
3079 Ok(RowParallelResult {
3080 reduced,
3081 rank_partials,
3082 })
3083 }
3084
3085 pub fn upload_row_parallel(
3086 &self,
3087 matrix: E4m3BlockMatrix<'_>,
3088 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
3089 matrix.validate()?;
3090 let tp = self.ranks.len();
3091 validate_row_shape(matrix, tp)?;
3092 let local_in = matrix.in_features / tp;
3093 let mut ranks = Vec::with_capacity(tp);
3094 for (rank_index, engine) in self.ranks.iter().enumerate() {
3095 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
3096 ranks.push(upload_rank(
3097 engine,
3098 E4m3BlockMatrix {
3099 codes: &codes,
3100 scales: &scales,
3101 out_features: matrix.out_features,
3102 in_features: local_in,
3103 },
3104 )?);
3105 }
3106 Ok(ResidentRowParallel {
3107 ranks,
3108 out_features: matrix.out_features,
3109 in_features: matrix.in_features,
3110 })
3111 }
3112
3113 pub fn row_parallel_resident(
3114 &self,
3115 matrix: &ResidentRowParallel,
3116 activations: &[f32],
3117 tokens: usize,
3118 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3119 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
3120 validate_activations(activations, tokens, matrix.in_features)?;
3121 let tp = self.ranks.len();
3122 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3123 let mut rank_partials = Vec::with_capacity(tp);
3124 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3125 let local_activations =
3126 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
3127 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
3128 for (sum, value) in reduced.iter_mut().zip(&partial) {
3129 *sum += *value;
3130 }
3131 rank_partials.push(partial);
3132 }
3133 Ok(RowParallelResult {
3134 reduced,
3135 rank_partials,
3136 })
3137 }
3138
3139 pub fn upload_bf16_column_parallel(
3140 &self,
3141 matrix: Bf16Matrix<'_>,
3142 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3143 self.upload_bf16_column_parallel_inner(matrix, None, false)
3144 }
3145
3146 pub fn upload_step_bf16_column_parallel(
3148 &self,
3149 matrix: Bf16Matrix<'_>,
3150 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3151 self.upload_step_bf16_column_parallel_inner(matrix, false)
3152 }
3153
3154 pub fn upload_step_bf16_column_parallel_f32_mirror(
3159 &self,
3160 matrix: Bf16Matrix<'_>,
3161 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3162 self.upload_step_bf16_column_parallel_inner(matrix, true)
3163 }
3164
3165 fn upload_step_bf16_column_parallel_inner(
3166 &self,
3167 matrix: Bf16Matrix<'_>,
3168 f32_mirror: bool,
3169 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3170 let canonical_chunk_rows =
3171 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
3172 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
3173 }
3174
3175 #[allow(clippy::manual_is_multiple_of)] fn upload_bf16_column_parallel_inner(
3177 &self,
3178 matrix: Bf16Matrix<'_>,
3179 canonical_chunk_rows: Option<usize>,
3180 f32_mirror: bool,
3181 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3182 matrix.validate()?;
3183 let tp = self.ranks.len();
3184 if matrix.out_features % tp != 0 {
3185 return Err(format!(
3186 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
3187 matrix.out_features
3188 )
3189 .into());
3190 }
3191 let mut ranks = Vec::with_capacity(tp);
3192 for (rank, engine) in self.ranks.iter().enumerate() {
3193 ranks.push(upload_bf16_rank(
3194 engine,
3195 bf16_column_shard(matrix, tp, rank)?,
3196 f32_mirror,
3197 )?);
3198 }
3199 Ok(ResidentBf16ColumnParallel {
3200 ranks,
3201 out_features: matrix.out_features,
3202 in_features: matrix.in_features,
3203 canonical_chunk_rows,
3204 })
3205 }
3206
3207 pub fn bf16_column_parallel_resident(
3208 &self,
3209 matrix: &ResidentBf16ColumnParallel,
3210 activations: &[f32],
3211 tokens: usize,
3212 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
3213 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3214 validate_activations(activations, tokens, matrix.in_features)?;
3215 let local_out = matrix.out_features / self.ranks.len();
3216 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
3217 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3218 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3219 let output = run_resident_bf16_rank(
3220 engine,
3221 shard,
3222 activations,
3223 tokens,
3224 matrix.canonical_chunk_rows,
3225 )?;
3226 for token in 0..tokens {
3227 let src = &output[token * local_out..(token + 1) * local_out];
3228 let dst_start = token * matrix.out_features + rank * local_out;
3229 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
3230 }
3231 rank_outputs.push(output);
3232 }
3233 Ok(ColumnParallelResult {
3234 gathered,
3235 rank_outputs,
3236 })
3237 }
3238
3239 pub fn bf16_column_parallel_resident_native(
3246 &self,
3247 matrix: &ResidentBf16ColumnParallel,
3248 activations: &[f32],
3249 tokens: usize,
3250 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3251 let rank_outputs =
3252 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
3253 let local_out = matrix.out_features / self.ranks.len();
3254 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
3255 }
3256
3257 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
3262 self.ranks
3263 .first()
3264 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
3265 }
3266
3267 pub fn bf16_column_parallel_resident_native_device(
3280 &self,
3281 matrix: &ResidentBf16ColumnParallel,
3282 root_activation: &CudaSlice<f32>,
3283 tokens: usize,
3284 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3285 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
3286 matrix,
3287 root_activation,
3288 tokens,
3289 )?;
3290 let local_out = matrix.out_features / self.ranks.len();
3291 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
3292 let root = &self.ranks[0];
3293 let _main = root.gpu.enter_main()?;
3294 root.stream().synchronize()?;
3295 Ok(gathered)
3296 }
3297
3298 pub fn bf16_column_parallel_resident_device_shards_from_root(
3303 &self,
3304 matrix: &ResidentBf16ColumnParallel,
3305 root_activation: &CudaSlice<f32>,
3306 tokens: usize,
3307 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3308 if self.ranks.len() > 1 && !self.native_p2p {
3309 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3310 }
3311 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3312 let values = tokens
3313 .checked_mul(matrix.in_features)
3314 .ok_or("device BF16 column activation size overflow")?;
3315 let root = &self.ranks[0];
3316 if tokens == 0
3317 || root_activation.len() < values
3318 || root_activation.ordinal() != root.ctx().ordinal()
3319 {
3320 return Err("device BF16 column root activation geometry mismatch".into());
3321 }
3322
3323 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3324 let root_input = {
3325 let _main = root.gpu.enter_main()?;
3326 let mut root_input = root.uninit(values)?;
3327 root.stream()
3328 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3329 root_input
3330 };
3331 {
3335 let _main = root.gpu.enter_main()?;
3336 root.stream().synchronize()?;
3337 }
3338 rank_inputs.push(root_input);
3339 for engine in &self.ranks[1..] {
3340 let peer_input = {
3341 let _main = engine.gpu.enter_main()?;
3342 let mut peer_input = engine.uninit(values)?;
3343 engine
3344 .stream()
3345 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3346 peer_input
3347 };
3348 rank_inputs.push(peer_input);
3349 }
3350
3351 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3352 #[allow(clippy::needless_range_loop)]
3353 for rank in 0..self.ranks.len() {
3355 rank_outputs.push(run_resident_bf16_rank_device(
3356 &self.ranks[rank],
3357 &matrix.ranks[rank],
3358 &rank_inputs[rank],
3359 tokens,
3360 matrix.canonical_chunk_rows,
3361 self.bulk_p2p,
3362 )?);
3363 }
3364 Ok(rank_outputs)
3365 }
3366
3367 pub fn bf16_column_parallel_resident_device_shards(
3374 &self,
3375 matrix: &ResidentBf16ColumnParallel,
3376 activations: &[f32],
3377 tokens: usize,
3378 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3379 if self.ranks.len() > 1 && !self.native_p2p {
3380 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3381 }
3382 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3383 validate_activations(activations, tokens, matrix.in_features)?;
3384
3385 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3386 let root_input = {
3387 let root = &self.ranks[0];
3388 let _main = root.gpu.enter_main()?;
3389 root.htod(activations)?
3390 };
3391 {
3397 let root = &self.ranks[0];
3398 let _main = root.gpu.enter_main()?;
3399 root.stream().synchronize()?;
3400 }
3401 rank_inputs.push(root_input);
3402 for engine in &self.ranks[1..] {
3403 let peer_input = {
3404 let _main = engine.gpu.enter_main()?;
3405 let mut peer_input = engine.uninit(activations.len())?;
3406 engine
3407 .stream()
3408 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3409 peer_input
3410 };
3411 rank_inputs.push(peer_input);
3412 }
3413
3414 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3415 #[allow(clippy::needless_range_loop)]
3416 for rank in 0..self.ranks.len() {
3418 rank_outputs.push(run_resident_bf16_rank_device(
3419 &self.ranks[rank],
3420 &matrix.ranks[rank],
3421 &rank_inputs[rank],
3422 tokens,
3423 matrix.canonical_chunk_rows,
3424 self.bulk_p2p,
3425 )?);
3426 }
3427 Ok(rank_outputs)
3428 }
3429
3430 pub fn allocate_replicated_device_rows(
3434 &self,
3435 tokens: usize,
3436 width: usize,
3437 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3438 if self.ranks.len() > 1 && !self.native_p2p {
3439 return Err("replicated device rows require native P2P ranks".into());
3440 }
3441 let values = tokens
3442 .checked_mul(width)
3443 .ok_or("replicated device row size overflow")?;
3444 let rank_lengths = vec![values; self.ranks.len()];
3445 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
3446 let mut ranks = Vec::with_capacity(self.ranks.len());
3447 for engine in &self.ranks {
3448 let _main = engine.gpu.enter_main()?;
3449 ranks.push(engine.uninit(values)?);
3450 }
3451 Ok(ResidentReplicatedDeviceRows {
3452 ranks,
3453 tokens,
3454 width,
3455 })
3456 }
3457
3458 pub fn refresh_replicated_device_rows_from_root(
3460 &self,
3461 rows: &mut ResidentReplicatedDeviceRows,
3462 source: &CudaSlice<f32>,
3463 ) -> Result<(), Box<dyn std::error::Error>> {
3464 if self.ranks.len() > 1 && !self.native_p2p {
3465 return Err("replicated device rows require native P2P ranks".into());
3466 }
3467 validate_replicated_device_rows(&self.ranks, rows)?;
3468 let root = self
3469 .ranks
3470 .first()
3471 .ok_or("replicated rows have no root rank")?;
3472 let values = replicated_device_row_source_values(
3473 rows.tokens,
3474 rows.width,
3475 source.len(),
3476 source.ordinal(),
3477 root.ctx().ordinal(),
3478 )?;
3479 let (root_rows, peer_rows) = rows
3480 .ranks
3481 .split_first_mut()
3482 .ok_or("replicated rows have no root allocation")?;
3483 {
3484 let _main = root.gpu.enter_main()?;
3485 let mut destination = root_rows.slice_mut(0..values);
3486 root.stream()
3487 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
3488 root.stream().synchronize()?;
3489 }
3490 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
3491 let _main = engine.gpu.enter_main()?;
3492 let mut destination = peer_rows.slice_mut(0..values);
3493 engine
3494 .stream()
3495 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
3496 }
3497 Ok(())
3498 }
3499
3500 pub fn upload_replicated_device_rows(
3502 &self,
3503 rows: &[f32],
3504 tokens: usize,
3505 width: usize,
3506 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3507 if self.ranks.len() > 1 && !self.native_p2p {
3508 return Err("replicated device rows require native P2P ranks".into());
3509 }
3510 validate_activations(rows, tokens, width)?;
3511 let root = self
3512 .ranks
3513 .first()
3514 .ok_or("replicated rows have no root rank")?;
3515 let root_rows = {
3516 let _main = root.gpu.enter_main()?;
3517 root.htod(rows)?
3518 };
3519 {
3520 let _main = root.gpu.enter_main()?;
3521 root.stream().synchronize()?;
3522 }
3523 let mut ranks = Vec::with_capacity(self.ranks.len());
3524 ranks.push(root_rows);
3525 for engine in self.ranks.iter().skip(1) {
3526 let _main = engine.gpu.enter_main()?;
3527 let mut peer_rows = engine.uninit(rows.len())?;
3528 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
3529 ranks.push(peer_rows);
3530 }
3531 Ok(ResidentReplicatedDeviceRows {
3532 ranks,
3533 tokens,
3534 width,
3535 })
3536 }
3537
3538 pub fn bf16_column_parallel_resident_replicated_device_shards(
3540 &self,
3541 matrix: &ResidentBf16ColumnParallel,
3542 activations: &ResidentReplicatedDeviceRows,
3543 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3544 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3545 validate_replicated_device_rows(&self.ranks, activations)?;
3546 if activations.width != matrix.in_features {
3547 return Err(format!(
3548 "replicated BF16 column input width {} != matrix width {}",
3549 activations.width, matrix.in_features
3550 )
3551 .into());
3552 }
3553 let mut outputs = Vec::with_capacity(self.ranks.len());
3554 for rank in 0..self.ranks.len() {
3555 outputs.push(run_resident_bf16_rank_device(
3556 &self.ranks[rank],
3557 &matrix.ranks[rank],
3558 &activations.ranks[rank],
3559 activations.tokens,
3560 matrix.canonical_chunk_rows,
3561 self.bulk_p2p,
3562 )?);
3563 }
3564 Ok(outputs)
3565 }
3566
3567 #[allow(clippy::too_many_arguments)]
3569 pub fn upload_sigmoid_topk_router(
3570 &self,
3571 weight: Bf16Matrix<'_>,
3572 correction_bias: &[f32],
3573 active: Option<&[bool]>,
3574 experts_per_token: usize,
3575 scaling_factor: f32,
3576 route_norm: bool,
3577 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
3578 weight.validate()?;
3579 if correction_bias.len() != weight.out_features
3580 || experts_per_token == 0
3581 || experts_per_token > weight.out_features
3582 || !correction_bias.iter().all(|value| value.is_finite())
3583 || !scaling_factor.is_finite()
3584 || scaling_factor <= 0.0
3585 {
3586 return Err(format!(
3587 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
3588 weight.out_features,
3589 weight.in_features,
3590 correction_bias.len(),
3591 experts_per_token,
3592 )
3593 .into());
3594 }
3595 let active_row = active
3596 .map(|mask| {
3597 if mask.len() != weight.out_features {
3598 return Err(format!(
3599 "sigmoid router active mask {} != experts {}",
3600 mask.len(),
3601 weight.out_features
3602 ));
3603 }
3604 Ok(mask
3605 .iter()
3606 .map(|&enabled| u8::from(enabled))
3607 .collect::<Vec<_>>())
3608 })
3609 .transpose()?
3610 .unwrap_or_else(|| vec![1; weight.out_features]);
3611 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
3612 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
3613
3614 let root = self
3615 .ranks
3616 .first()
3617 .ok_or("sigmoid router runtime has no root rank")?;
3618 let _main = root.gpu.enter_main()?;
3619 let bf16 = root.htod_bytes(weight.bytes)?;
3620 let weight_f32 = root.bf16_to_f32(
3621 &bf16.slice(0..bf16.len()),
3622 weight.out_features * weight.in_features,
3623 )?;
3624 Ok(ResidentSigmoidTopKRouter {
3625 weight: weight_f32,
3626 correction_bias: root.htod(correction_bias)?,
3627 active: root.htod_bytes(&active_row)?,
3628 root_device: root.ctx().ordinal(),
3629 input_width: weight.in_features,
3630 expert_count: weight.out_features,
3631 experts_per_token,
3632 active_count,
3633 scaling_factor,
3634 route_norm,
3635 })
3636 }
3637
3638 pub fn sigmoid_topk_replicated_device_rows_host(
3643 &self,
3644 router: &ResidentSigmoidTopKRouter,
3645 input: &ResidentReplicatedDeviceRows,
3646 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
3647 validate_replicated_device_rows(&self.ranks, input)?;
3648 if input.width != router.input_width {
3649 return Err(format!(
3650 "sigmoid router input width {} != resident width {}",
3651 input.width, router.input_width
3652 )
3653 .into());
3654 }
3655 let root = self
3656 .ranks
3657 .first()
3658 .ok_or("sigmoid router runtime has no root rank")?;
3659 let _main = root.gpu.enter_main()?;
3660 if root.ctx().ordinal() != router.root_device
3661 || router.weight.ordinal() != router.root_device
3662 || router.correction_bias.ordinal() != router.root_device
3663 || router.active.ordinal() != router.root_device
3664 {
3665 return Err("sigmoid router root residency changed".into());
3666 }
3667 let logits = root.router_gemv(
3668 &router.weight,
3669 &input.ranks[0],
3670 router.input_width,
3671 router.expert_count,
3672 input.tokens,
3673 )?;
3674 let (selected, weights) = root.moe_router_sigmoid_topk_host(
3675 &logits,
3676 input.tokens,
3677 router.expert_count,
3678 router.experts_per_token,
3679 router.active_count,
3680 &router.correction_bias,
3681 &router.active,
3682 router.scaling_factor,
3683 router.route_norm,
3684 )?;
3685 Ok(SigmoidTopKHostOutput {
3686 logits: root.dtoh(&logits)?,
3687 selected,
3688 weights,
3689 })
3690 }
3691
3692 pub fn upload_replicated_bf16_swiglu(
3694 &self,
3695 gate: Bf16Matrix<'_>,
3696 up: Bf16Matrix<'_>,
3697 down: Bf16Matrix<'_>,
3698 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
3699 gate.validate()?;
3700 up.validate()?;
3701 down.validate()?;
3702 if gate.in_features != up.in_features
3703 || gate.out_features != up.out_features
3704 || down.in_features != gate.out_features
3705 || down.out_features != gate.in_features
3706 {
3707 return Err(format!(
3708 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
3709 gate.out_features,
3710 gate.in_features,
3711 up.out_features,
3712 up.in_features,
3713 down.out_features,
3714 down.in_features,
3715 )
3716 .into());
3717 }
3718 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
3719 let mut up_ranks = Vec::with_capacity(self.ranks.len());
3720 let mut down_ranks = Vec::with_capacity(self.ranks.len());
3721 for engine in &self.ranks {
3722 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
3723 up_ranks.push(upload_bf16_rank(engine, up, false)?);
3724 down_ranks.push(upload_bf16_rank(engine, down, false)?);
3725 }
3726 Ok(ResidentReplicatedBf16SwiGlu {
3727 gate: gate_ranks,
3728 up: up_ranks,
3729 down: down_ranks,
3730 input_width: gate.in_features,
3731 intermediate_width: gate.out_features,
3732 })
3733 }
3734
3735 pub fn replicated_bf16_swiglu_resident_device(
3737 &self,
3738 mlp: &ResidentReplicatedBf16SwiGlu,
3739 input: &ResidentReplicatedDeviceRows,
3740 activation_limit: Option<f32>,
3741 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3742 validate_step_expert_activation_limit(activation_limit)?;
3743 validate_replicated_device_rows(&self.ranks, input)?;
3744 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3745 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3746 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3747 if input.width != mlp.input_width
3748 || mlp.gate.len() != self.ranks.len()
3749 || mlp.up.len() != self.ranks.len()
3750 || mlp.down.len() != self.ranks.len()
3751 {
3752 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3753 }
3754
3755 let mut outputs = Vec::with_capacity(self.ranks.len());
3756 for rank in 0..self.ranks.len() {
3757 let engine = &self.ranks[rank];
3758 let gate = run_resident_bf16_rank_device(
3759 engine,
3760 &mlp.gate[rank],
3761 &input.ranks[rank],
3762 input.tokens,
3763 None,
3764 self.bulk_p2p,
3765 )?;
3766 let up = run_resident_bf16_rank_device(
3767 engine,
3768 &mlp.up[rank],
3769 &input.ranks[rank],
3770 input.tokens,
3771 None,
3772 self.bulk_p2p,
3773 )?;
3774 let _main = engine.gpu.enter_main()?;
3775 let values = input
3776 .tokens
3777 .checked_mul(mlp.intermediate_width)
3778 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3779 let mut activation = engine.uninit(values)?;
3780 if let Some(limit) = activation_limit {
3781 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3782 } else {
3783 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3784 }
3785 outputs.push(run_resident_bf16_rank_device(
3786 engine,
3787 &mlp.down[rank],
3788 &activation,
3789 input.tokens,
3790 None,
3791 self.bulk_p2p,
3792 )?);
3793 }
3794 Ok(ResidentReplicatedDeviceRows {
3795 ranks: outputs,
3796 tokens: input.tokens,
3797 width: mlp.input_width,
3798 })
3799 }
3800
3801 pub fn rms_norm_replicated_device_rows(
3803 &self,
3804 input: &ResidentReplicatedDeviceRows,
3805 weight: &[f32],
3806 eps: f32,
3807 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3808 validate_replicated_device_rows(&self.ranks, input)?;
3809 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3810 return Err(format!(
3811 "replicated RMS norm weight/eps {}/{} != width {}",
3812 weight.len(),
3813 eps,
3814 input.width
3815 )
3816 .into());
3817 }
3818 let mut ranks = Vec::with_capacity(self.ranks.len());
3819 for (rank, engine) in self.ranks.iter().enumerate() {
3820 let _main = engine.gpu.enter_main()?;
3821 let weight = engine.htod(weight)?;
3822 let mut output = engine.uninit(input.tokens * input.width)?;
3823 engine.rms_norm(
3824 &input.ranks[rank],
3825 &weight,
3826 &mut output,
3827 input.width,
3828 input.tokens,
3829 eps,
3830 )?;
3831 ranks.push(output);
3832 }
3833 Ok(ResidentReplicatedDeviceRows {
3834 ranks,
3835 tokens: input.tokens,
3836 width: input.width,
3837 })
3838 }
3839
3840 pub fn add_rms_norm_replicated_device_rows(
3842 &self,
3843 input: &ResidentReplicatedDeviceRows,
3844 update: &ResidentReplicatedDeviceRows,
3845 weight: &[f32],
3846 eps: f32,
3847 ) -> Result<
3848 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3849 Box<dyn std::error::Error>,
3850 > {
3851 validate_replicated_device_rows(&self.ranks, input)?;
3852 validate_replicated_device_rows(&self.ranks, update)?;
3853 if input.tokens != update.tokens
3854 || input.width != update.width
3855 || weight.len() != input.width
3856 || !eps.is_finite()
3857 || eps <= 0.0
3858 {
3859 return Err(format!(
3860 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3861 input.tokens,
3862 input.width,
3863 update.tokens,
3864 update.width,
3865 weight.len(),
3866 )
3867 .into());
3868 }
3869 let values = input.tokens * input.width;
3870 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3871 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3872 for (rank, engine) in self.ranks.iter().enumerate() {
3873 let _main = engine.gpu.enter_main()?;
3874 let weight = engine.htod(weight)?;
3875 let mut residual = engine.uninit(values)?;
3876 let mut normalized = engine.uninit(values)?;
3877 engine.add_rms_norm(
3878 &input.ranks[rank],
3879 &update.ranks[rank],
3880 &weight,
3881 &mut residual,
3882 &mut normalized,
3883 input.width,
3884 input.tokens,
3885 eps,
3886 )?;
3887 residual_ranks.push(residual);
3888 normalized_ranks.push(normalized);
3889 }
3890 Ok((
3891 ResidentReplicatedDeviceRows {
3892 ranks: residual_ranks,
3893 tokens: input.tokens,
3894 width: input.width,
3895 },
3896 ResidentReplicatedDeviceRows {
3897 ranks: normalized_ranks,
3898 tokens: input.tokens,
3899 width: input.width,
3900 },
3901 ))
3902 }
3903
3904 pub fn collect_replicated_device_rows(
3905 &self,
3906 rows: &ResidentReplicatedDeviceRows,
3907 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3908 validate_replicated_device_rows(&self.ranks, rows)?;
3909 let mut outputs = Vec::with_capacity(self.ranks.len());
3910 for (rank, engine) in self.ranks.iter().enumerate() {
3911 let _main = engine.gpu.enter_main()?;
3912 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3913 }
3914 Ok(outputs)
3915 }
3916
3917 #[allow(clippy::manual_is_multiple_of)] pub fn upload_bf16_row_parallel(
3919 &self,
3920 matrix: Bf16Matrix<'_>,
3921 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3922 matrix.validate()?;
3923 let tp = self.ranks.len();
3924 if matrix.in_features % tp != 0 {
3925 return Err(format!(
3926 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3927 matrix.in_features
3928 )
3929 .into());
3930 }
3931 let mut ranks = Vec::with_capacity(tp);
3932 for (rank, engine) in self.ranks.iter().enumerate() {
3933 let shard = bf16_row_shard(matrix, tp, rank)?;
3934 ranks.push(upload_bf16_rank(
3935 engine,
3936 Bf16Matrix {
3937 bytes: &shard,
3938 out_features: matrix.out_features,
3939 in_features: matrix.in_features / tp,
3940 },
3941 false,
3942 )?);
3943 }
3944 Ok(ResidentBf16RowParallel {
3945 ranks,
3946 out_features: matrix.out_features,
3947 in_features: matrix.in_features,
3948 })
3949 }
3950
3951 pub fn bf16_row_parallel_resident(
3952 &self,
3953 matrix: &ResidentBf16RowParallel,
3954 activations: &[f32],
3955 tokens: usize,
3956 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3957 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3958 validate_activations(activations, tokens, matrix.in_features)?;
3959 let tp = self.ranks.len();
3960 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3961 let mut rank_partials = Vec::with_capacity(tp);
3962 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3963 let local_activations =
3964 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3965 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3966 for (sum, value) in reduced.iter_mut().zip(&partial) {
3967 *sum += value;
3968 }
3969 rank_partials.push(partial);
3970 }
3971 Ok(RowParallelResult {
3972 reduced,
3973 rank_partials,
3974 })
3975 }
3976
3977 pub fn upload_step_bf16_row_parallel(
3979 &self,
3980 matrix: Bf16Matrix<'_>,
3981 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3982 self.upload_step_bf16_row_parallel_inner(matrix, false)
3983 }
3984
3985 pub fn upload_step_bf16_row_parallel_f32_mirror(
3986 &self,
3987 matrix: Bf16Matrix<'_>,
3988 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3989 self.upload_step_bf16_row_parallel_inner(matrix, true)
3990 }
3991
3992 fn upload_step_bf16_row_parallel_inner(
3993 &self,
3994 matrix: Bf16Matrix<'_>,
3995 f32_mirror: bool,
3996 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3997 matrix.validate()?;
3998 let tp = self.ranks.len();
3999 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
4000 let local_in = matrix.in_features / tp;
4001 let blocks_per_rank = local_in / canonical_chunk_cols;
4002 let mut ranks = Vec::with_capacity(tp);
4003 for (rank, engine) in self.ranks.iter().enumerate() {
4004 let mut blocks = Vec::with_capacity(blocks_per_rank);
4005 for block in 0..blocks_per_rank {
4006 let global_block = rank * blocks_per_rank + block;
4007 let col_start = global_block * canonical_chunk_cols;
4008 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
4009 blocks.push(upload_bf16_rank(
4010 engine,
4011 Bf16Matrix {
4012 bytes: &bytes,
4013 out_features: matrix.out_features,
4014 in_features: canonical_chunk_cols,
4015 },
4016 f32_mirror,
4017 )?);
4018 }
4019 ranks.push(blocks);
4020 }
4021 Ok(ResidentStepBf16RowParallel {
4022 ranks,
4023 out_features: matrix.out_features,
4024 in_features: matrix.in_features,
4025 canonical_chunk_cols,
4026 })
4027 }
4028
4029 pub fn step_bf16_row_parallel_resident(
4034 &self,
4035 matrix: &ResidentStepBf16RowParallel,
4036 activations: &[f32],
4037 tokens: usize,
4038 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4039 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4040 validate_activations(activations, tokens, matrix.in_features)?;
4041 let root = &self.ranks[0];
4042 let output_len = tokens
4043 .checked_mul(matrix.out_features)
4044 .ok_or("Step BF16 row output size overflow")?;
4045 let mut reduced = {
4046 let _main = root.gpu.enter_main()?;
4047 root.htod(&vec![0.0f32; output_len])?
4048 };
4049 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4050 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4051 for (block, resident) in blocks.iter().enumerate() {
4052 let global_block = rank * blocks_per_rank + block;
4053 let input = activation_shard(
4054 activations,
4055 tokens,
4056 matrix.in_features,
4057 PRODUCT_MAX_CARDS,
4058 global_block,
4059 );
4060 let partial =
4061 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
4062 let next = {
4063 let _main = root.gpu.enter_main()?;
4064 let partial = root.htod(&partial)?;
4065 let mut next = root.uninit(output_len)?;
4066 root.add(&reduced, &partial, &mut next, output_len)?;
4067 next
4068 };
4069 reduced = next;
4070 }
4071 }
4072 let _main = root.gpu.enter_main()?;
4073 root.dtoh(&reduced)
4074 }
4075
4076 pub fn step_bf16_row_parallel_resident_native(
4082 &self,
4083 matrix: &ResidentStepBf16RowParallel,
4084 activations: &[f32],
4085 tokens: usize,
4086 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4087 if self.ranks.len() > 1 && !self.native_p2p {
4088 return Err("native Step BF16 row parallelism requires P2P ranks".into());
4089 }
4090 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4091 validate_activations(activations, tokens, matrix.in_features)?;
4092 let root = &self.ranks[0];
4093 let root_input = {
4094 let _main = root.gpu.enter_main()?;
4095 root.htod(activations)?
4096 };
4097 {
4100 let _main = root.gpu.enter_main()?;
4101 root.stream().synchronize()?;
4102 }
4103 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
4104 let _main = root.gpu.enter_main()?;
4105 root.dtoh(&reduced)
4106 }
4107
4108 pub fn step_bf16_row_parallel_resident_native_device(
4116 &self,
4117 matrix: &ResidentStepBf16RowParallel,
4118 root_activation: &CudaSlice<f32>,
4119 tokens: usize,
4120 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4121 if self.ranks.len() > 1 && !self.native_p2p {
4122 return Err("native Step BF16 row parallelism requires P2P ranks".into());
4123 }
4124 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4125 let values = tokens
4126 .checked_mul(matrix.in_features)
4127 .ok_or("device Step BF16 row activation size overflow")?;
4128 let root = &self.ranks[0];
4129 if tokens == 0
4130 || root_activation.len() < values
4131 || root_activation.ordinal() != root.ctx().ordinal()
4132 {
4133 return Err("device Step BF16 row root activation geometry mismatch".into());
4134 }
4135 let root_input = {
4136 let _main = root.gpu.enter_main()?;
4137 let mut root_input = root.uninit(values)?;
4138 root.stream()
4139 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
4140 root.stream().synchronize()?; root_input
4142 };
4143 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
4144 let _main = root.gpu.enter_main()?;
4145 root.stream().synchronize()?;
4146 Ok(reduced)
4147 }
4148
4149 fn step_bf16_row_native_reduce_from_root(
4154 &self,
4155 matrix: &ResidentStepBf16RowParallel,
4156 root_input: &CudaSlice<f32>,
4157 tokens: usize,
4158 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4159 let root = &self.ranks[0];
4160 let output_len = tokens
4161 .checked_mul(matrix.out_features)
4162 .ok_or("native Step BF16 row output size overflow")?;
4163 let mut reduced = {
4164 let _main = root.gpu.enter_main()?;
4165 root.htod(&vec![0.0f32; output_len])?
4166 };
4167 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4168 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
4169 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
4170 let mut remote_partial_keepalive = Vec::new();
4171 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4172 for (block, resident) in blocks.iter().enumerate() {
4173 let global_block = rank * blocks_per_rank + block;
4174 let col_start = global_block * matrix.canonical_chunk_cols;
4175 let block_len = tokens
4176 .checked_mul(matrix.canonical_chunk_cols)
4177 .ok_or("native Step BF16 row block size overflow")?;
4178 let block_input = if self.bulk_p2p {
4179 let root_packed = {
4180 let _main = root.gpu.enter_main()?;
4181 let mut root_packed = root.uninit(block_len)?;
4182 root.copy_rows_strided(
4183 root_input,
4184 &mut root_packed,
4185 matrix.canonical_chunk_cols,
4186 tokens,
4187 matrix.in_features,
4188 col_start,
4189 )?;
4190 root_packed
4191 };
4192 if rank == 0 {
4193 root_packed
4194 } else {
4195 {
4198 let _main = root.gpu.enter_main()?;
4199 root.stream().synchronize()?;
4200 }
4201 let engine = &self.ranks[rank];
4202 let _main = engine.gpu.enter_main()?;
4203 let mut block_input = engine.uninit(block_len)?;
4204 engine
4205 .stream()
4206 .memcpy_dtod(&root_packed, &mut block_input)?;
4207 root_packed_keepalive.push(root_packed);
4208 block_input
4209 }
4210 } else {
4211 let engine = &self.ranks[rank];
4212 let _main = engine.gpu.enter_main()?;
4213 let mut block_input = engine.uninit(block_len)?;
4214 for token in 0..tokens {
4215 let source_start = token * matrix.in_features + col_start;
4216 let source = root_input
4217 .slice(source_start..source_start + matrix.canonical_chunk_cols);
4218 let destination_start = token * matrix.canonical_chunk_cols;
4219 let mut destination = block_input.slice_mut(
4220 destination_start..destination_start + matrix.canonical_chunk_cols,
4221 );
4222 engine.stream().memcpy_dtod(&source, &mut destination)?;
4223 }
4224 block_input
4225 };
4226 let partial = run_resident_bf16_rank_device(
4227 &self.ranks[rank],
4228 resident,
4229 &block_input,
4230 tokens,
4231 None,
4232 self.bulk_p2p,
4233 )?;
4234 block_input_keepalive.push(block_input);
4235 let root_partial = if rank == 0 {
4236 partial
4237 } else {
4238 {
4241 let engine = &self.ranks[rank];
4242 let _main = engine.gpu.enter_main()?;
4243 engine.stream().synchronize()?;
4244 }
4245 let _main = root.gpu.enter_main()?;
4246 let mut peer_partial = root.uninit(output_len)?;
4247 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4248 remote_partial_keepalive.push(partial);
4249 peer_partial
4250 };
4251 let next = {
4252 let _main = root.gpu.enter_main()?;
4253 let mut next = root.uninit(output_len)?;
4254 root.add(&reduced, &root_partial, &mut next, output_len)?;
4255 next
4256 };
4257 reduced = next;
4258 }
4259 }
4260 {
4261 let _main = root.gpu.enter_main()?;
4262 root.stream().synchronize()?;
4263 }
4264 drop(remote_partial_keepalive);
4265 drop(root_packed_keepalive);
4266 drop(block_input_keepalive);
4267 Ok(reduced)
4268 }
4269
4270 pub fn step_bf16_row_parallel_resident_root_device(
4273 &self,
4274 matrix: &ResidentStepBf16RowParallel,
4275 rank_activations: &[CudaSlice<f32>],
4276 tokens: usize,
4277 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4278 if self.ranks.len() > 1 && !self.native_p2p {
4279 return Err(
4280 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
4281 );
4282 }
4283 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4284 let local_width = matrix.in_features / self.ranks.len();
4285 let shard_len = tokens
4286 .checked_mul(local_width)
4287 .ok_or("device Step BF16 row shard size overflow")?;
4288 if tokens == 0
4289 || rank_activations.len() != self.ranks.len()
4290 || rank_activations
4291 .iter()
4292 .zip(&self.ranks)
4293 .any(|(rows, engine)| {
4294 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
4295 })
4296 {
4297 return Err("device Step BF16 row activation shard geometry changed".into());
4298 }
4299
4300 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4301 let mut block_inputs = Vec::with_capacity(self.ranks.len());
4302 let mut partials = Vec::with_capacity(self.ranks.len());
4303 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4304 if blocks.len() != blocks_per_rank {
4305 return Err(format!(
4306 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
4307 blocks.len()
4308 )
4309 .into());
4310 }
4311 let engine = &self.ranks[rank];
4312 let _main = engine.gpu.enter_main()?;
4313 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
4314 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
4315 for (block, resident) in blocks.iter().enumerate() {
4316 let block_len = tokens
4317 .checked_mul(matrix.canonical_chunk_cols)
4318 .ok_or("device Step BF16 row block size overflow")?;
4319 let mut block_input = engine.uninit(block_len)?;
4320 let local_col_start = block * matrix.canonical_chunk_cols;
4321 if self.bulk_p2p {
4322 engine.copy_rows_strided(
4323 &rank_activations[rank],
4324 &mut block_input,
4325 matrix.canonical_chunk_cols,
4326 tokens,
4327 local_width,
4328 local_col_start,
4329 )?;
4330 } else {
4331 for token in 0..tokens {
4332 let source_start = token * local_width + local_col_start;
4333 let source = rank_activations[rank]
4334 .slice(source_start..source_start + matrix.canonical_chunk_cols);
4335 let destination_start = token * matrix.canonical_chunk_cols;
4336 let mut destination = block_input.slice_mut(
4337 destination_start..destination_start + matrix.canonical_chunk_cols,
4338 );
4339 engine.stream().memcpy_dtod(&source, &mut destination)?;
4340 }
4341 }
4342 let partial = run_resident_bf16_rank_device(
4343 engine,
4344 resident,
4345 &block_input,
4346 tokens,
4347 None,
4348 self.bulk_p2p,
4349 )?;
4350 rank_inputs.push(block_input);
4351 rank_partials.push(partial);
4352 }
4353 block_inputs.push(rank_inputs);
4354 partials.push(rank_partials);
4355 }
4356 for engine in self.ranks.iter().skip(1) {
4357 let _main = engine.gpu.enter_main()?;
4358 engine.stream().synchronize()?;
4359 }
4360
4361 let output_len = tokens
4362 .checked_mul(matrix.out_features)
4363 .ok_or("device Step BF16 row output size overflow")?;
4364 let root = &self.ranks[0];
4365 let _main = root.gpu.enter_main()?;
4366 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
4367 let mut remote_partials = Vec::new();
4368 for (rank, rank_partials) in partials.into_iter().enumerate() {
4369 for partial in rank_partials {
4370 let root_partial = if rank == 0 {
4371 partial
4372 } else {
4373 let mut peer_partial = root.uninit(output_len)?;
4374 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4375 remote_partials.push(partial);
4376 peer_partial
4377 };
4378 let mut next = root.uninit(output_len)?;
4379 root.add(&reduced, &root_partial, &mut next, output_len)?;
4380 reduced = next;
4381 }
4382 }
4383 root.stream().synchronize()?;
4384 drop(remote_partials);
4385 drop(block_inputs);
4386 Ok(reduced)
4387 }
4388
4389 pub fn step_bf16_row_parallel_resident_replicated_device(
4391 &self,
4392 matrix: &ResidentStepBf16RowParallel,
4393 rank_activations: &[CudaSlice<f32>],
4394 tokens: usize,
4395 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4396 let reduced =
4397 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
4398 let output_len = tokens
4399 .checked_mul(matrix.out_features)
4400 .ok_or("device Step BF16 row output size overflow")?;
4401 let mut ranks = Vec::with_capacity(self.ranks.len());
4402 ranks.push(reduced);
4403 for engine in self.ranks.iter().skip(1) {
4404 let _main = engine.gpu.enter_main()?;
4405 let mut peer_output = engine.uninit(output_len)?;
4406 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
4407 ranks.push(peer_output);
4408 }
4409 Ok(ResidentReplicatedDeviceRows {
4410 ranks,
4411 tokens,
4412 width: matrix.out_features,
4413 })
4414 }
4415
4416 pub fn upload_expert(
4417 &self,
4418 gate: E4m3BlockMatrix<'_>,
4419 up: E4m3BlockMatrix<'_>,
4420 down: E4m3BlockMatrix<'_>,
4421 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
4422 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4423 return Err("TP expert gate/up dimensions differ".into());
4424 }
4425 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4426 return Err(format!(
4427 "TP expert down {}x{} does not invert gate/up {}x{}",
4428 down.out_features, down.in_features, gate.out_features, gate.in_features
4429 )
4430 .into());
4431 }
4432 Ok(ResidentTpExpert {
4433 gate: self.upload_column_parallel(gate)?,
4434 up: self.upload_column_parallel(up)?,
4435 down: self.upload_row_parallel(down)?,
4436 input_width: gate.in_features,
4437 expert_width: gate.out_features,
4438 })
4439 }
4440
4441 pub fn run_expert(
4442 &self,
4443 expert: &ResidentTpExpert,
4444 input: &[f32],
4445 tokens: usize,
4446 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4447 validate_activations(input, tokens, expert.input_width)?;
4448 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
4449 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
4450 let activated: Vec<f32> = gate
4451 .gathered
4452 .iter()
4453 .zip(&up.gathered)
4454 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4455 .collect();
4456 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
4457 Ok(self
4458 .row_parallel_resident(&expert.down, &activated, tokens)?
4459 .reduced)
4460 }
4461
4462 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel(
4464 &self,
4465 gate: E4m3ExpertBank<'_>,
4466 up: E4m3ExpertBank<'_>,
4467 down: E4m3ExpertBank<'_>,
4468 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
4469 gate.validate()?;
4470 up.validate()?;
4471 down.validate()?;
4472 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4473 return Err("EP gate/up/down expert counts differ".into());
4474 }
4475 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4476 return Err("EP gate/up dimensions differ".into());
4477 }
4478 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4479 return Err(format!(
4480 "EP down {}x{} does not invert gate/up {}x{}",
4481 down.out_features, down.in_features, gate.out_features, gate.in_features
4482 )
4483 .into());
4484 }
4485 if gate.expert_count % self.ranks.len() != 0 {
4486 return Err(format!(
4487 "EP expert count {} is not divisible by {} ranks",
4488 gate.expert_count,
4489 self.ranks.len()
4490 )
4491 .into());
4492 }
4493
4494 let per_rank = gate.expert_count / self.ranks.len();
4495 let mut ranks = Vec::with_capacity(self.ranks.len());
4496 for (rank, engine) in self.ranks.iter().enumerate() {
4497 let expert_range = rank * per_rank..(rank + 1) * per_rank;
4498 ranks.push(ResidentEpRank {
4499 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
4500 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
4501 down: upload_expert_bank_rank(engine, down, expert_range)?,
4502 });
4503 }
4504 Ok(ResidentExpertParallel {
4505 ranks,
4506 expert_count: gate.expert_count,
4507 input_width: gate.in_features,
4508 expert_width: gate.out_features,
4509 })
4510 }
4511
4512 #[allow(clippy::too_many_arguments)]
4518 pub fn prepare_step_grouped_fp8_gate(
4519 &self,
4520 gate: E4m3ExpertBank<'_>,
4521 up: E4m3ExpertBank<'_>,
4522 down: E4m3ExpertBank<'_>,
4523 input: &[f32],
4524 tokens: usize,
4525 selected: &[usize],
4526 activation_limit: Option<f32>,
4527 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
4528 gate.validate()?;
4529 up.validate()?;
4530 down.validate()?;
4531 validate_step_expert_activation_limit(activation_limit)?;
4532 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
4533 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
4534 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
4535 {
4536 return Err(format!(
4537 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
4538 got gate/up/down={}/{}/{}",
4539 gate.expert_count, up.expert_count, down.expert_count,
4540 )
4541 .into());
4542 }
4543 if gate.in_features != up.in_features
4544 || gate.out_features != STEP_GROUPED_FP8_WIDTH
4545 || up.out_features != STEP_GROUPED_FP8_WIDTH
4546 || down.in_features != STEP_GROUPED_FP8_WIDTH
4547 || down.out_features != gate.in_features
4548 {
4549 return Err(format!(
4550 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
4551 gate.out_features,
4552 gate.in_features,
4553 up.out_features,
4554 up.in_features,
4555 down.out_features,
4556 down.in_features,
4557 )
4558 .into());
4559 }
4560 validate_activations(input, tokens, gate.in_features)?;
4561 let pairs = tokens
4562 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4563 .ok_or("official Step grouped FP8 route count overflow")?;
4564 if selected.len() != pairs {
4565 return Err(format!(
4566 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
4567 ({pairs})",
4568 selected.len()
4569 )
4570 .into());
4571 }
4572 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
4573 let mut unique = routes.to_vec();
4574 unique.sort_unstable();
4575 unique.dedup();
4576 if unique.len() != STEP_GROUPED_FP8_TOP_K {
4577 return Err(format!(
4578 "official Step grouped FP8 token {token} routes are not top-8 unique: \
4579 {routes:?}"
4580 )
4581 .into());
4582 }
4583 }
4584
4585 let engine = self
4586 .ranks
4587 .first()
4588 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4589 let _main = engine.gpu.enter_main()?;
4590 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
4591 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
4592 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
4593 let down = upload_expert_bank_rank(engine, down, expert_range)?;
4594 let input = engine.htod(input)?;
4595 let route_csr = ExpertCsr::from_token_routes(
4596 STEP_GROUPED_FP8_EXPERTS,
4597 tokens,
4598 STEP_GROUPED_FP8_TOP_K,
4599 selected,
4600 )?
4601 .upload(engine)?;
4602 let pair_rows = (0..pairs).collect::<Vec<_>>();
4603 let down_csr =
4604 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
4605 .upload(engine)?;
4606 let gate_workspace =
4607 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
4608 let up_workspace =
4609 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
4610 let down_workspace =
4611 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
4612 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
4613 Ok(PreparedStepGroupedFp8Gate {
4614 device: engine.ctx().ordinal(),
4615 gate,
4616 up,
4617 down,
4618 input,
4619 route_csr,
4620 down_csr,
4621 gate_workspace,
4622 up_workspace,
4623 down_workspace,
4624 activation,
4625 activation_limit,
4626 tokens,
4627 pairs,
4628 })
4629 }
4630
4631 pub fn run_step_grouped_fp8_gate(
4633 &self,
4634 plan: &mut PreparedStepGroupedFp8Gate,
4635 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4636 let engine = self
4637 .ranks
4638 .first()
4639 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4640 if engine.ctx().ordinal() != plan.device {
4641 return Err(format!(
4642 "official Step grouped FP8 plan device {} != rank-zero device {}",
4643 plan.device,
4644 engine.ctx().ordinal()
4645 )
4646 .into());
4647 }
4648 let _main = engine.gpu.enter_main()?;
4649
4650 plan.gate_workspace.quantize(engine, &plan.input)?;
4651 plan.gate_workspace.project(
4652 engine,
4653 &plan.gate.codes,
4654 &plan.gate.scales,
4655 &plan.route_csr,
4656 plan.gate.code_stride,
4657 plan.gate.scale_stride,
4658 1.0,
4659 )?;
4660 plan.up_workspace.quantize(engine, &plan.input)?;
4661 plan.up_workspace.project(
4662 engine,
4663 &plan.up.codes,
4664 &plan.up.scales,
4665 &plan.route_csr,
4666 plan.up.code_stride,
4667 plan.up.scale_stride,
4668 1.0,
4669 )?;
4670 if let Some(limit) = plan.activation_limit {
4671 engine.silu_clamped_mul_host_expf(
4672 plan.gate_workspace.output(),
4673 plan.up_workspace.output(),
4674 limit,
4675 &mut plan.activation,
4676 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4677 )?;
4678 } else {
4679 engine.silu_mul_host_expf(
4680 plan.gate_workspace.output(),
4681 plan.up_workspace.output(),
4682 &mut plan.activation,
4683 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4684 )?;
4685 }
4686 plan.down_workspace.quantize(engine, &plan.activation)?;
4687 plan.down_workspace.project(
4688 engine,
4689 &plan.down.codes,
4690 &plan.down.scales,
4691 &plan.down_csr,
4692 plan.down.code_stride,
4693 plan.down.scale_stride,
4694 1.0,
4695 )?;
4696
4697 Ok(StepGroupedFp8ProjectionOutput {
4698 gate: engine.dtoh(plan.gate_workspace.output())?,
4699 up: engine.dtoh(plan.up_workspace.output())?,
4700 down: engine.dtoh(plan.down_workspace.output())?,
4701 })
4702 }
4703
4704 pub fn prepare_step_grouped_expert_parallel_gate(
4705 &self,
4706 experts: &ResidentExpertParallel,
4707 input: &[f32],
4708 tokens: usize,
4709 selected: &[usize],
4710 activation_limit: Option<f32>,
4711 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4712 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
4713 experts,
4714 input,
4715 tokens,
4716 selected,
4717 activation_limit,
4718 tokens,
4719 )
4720 }
4721
4722 #[allow(clippy::too_many_arguments)]
4723 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
4724 &self,
4725 experts: &ResidentExpertParallel,
4726 input: &[f32],
4727 tokens: usize,
4728 selected: &[usize],
4729 activation_limit: Option<f32>,
4730 max_tokens: usize,
4731 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4732 if !self.native_p2p || !self.ep_device_arithmetic {
4733 return Err(
4734 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
4735 );
4736 }
4737 validate_step_expert_activation_limit(activation_limit)?;
4738 validate_ep_residency(&self.ranks, experts)?;
4739 validate_activations(input, tokens, experts.input_width)?;
4740 if max_tokens < tokens || max_tokens > i32::MAX as usize {
4741 return Err(format!(
4742 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
4743 )
4744 .into());
4745 }
4746 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4747 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4748 {
4749 return Err(format!(
4750 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4751 STEP_GROUPED_FP8_EXPERTS,
4752 STEP_GROUPED_FP8_WIDTH,
4753 experts.expert_count,
4754 experts.expert_width,
4755 )
4756 .into());
4757 }
4758 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4759 let max_pairs = max_tokens
4760 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4761 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4762 let input_capacity = max_tokens
4763 .checked_mul(experts.input_width)
4764 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4765
4766 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4767 for engine in &self.ranks {
4768 let _main = engine.gpu.enter_main()?;
4769 rank_inputs.push(engine.uninit(input_capacity)?);
4770 }
4771
4772 let mut owners = Vec::with_capacity(self.ranks.len());
4773 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4774 if rank.gate.expert_range != rank.up.expert_range
4775 || rank.gate.expert_range != rank.down.expert_range
4776 {
4777 return Err(format!(
4778 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4779 owner_rank
4780 )
4781 .into());
4782 }
4783 let local_experts = rank.gate.expert_range.len();
4784 let engine = &self.ranks[owner_rank];
4785 let _main = engine.gpu.enter_main()?;
4786 let route_csr =
4787 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4788 let down_csr =
4789 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4790 let gate_workspace = Fp8GroupedWorkspace::new(
4791 engine,
4792 experts.input_width,
4793 experts.expert_width,
4794 max_tokens,
4795 max_pairs,
4796 )?;
4797 let up_workspace = Fp8GroupedWorkspace::new(
4798 engine,
4799 experts.input_width,
4800 experts.expert_width,
4801 max_tokens,
4802 max_pairs,
4803 )?;
4804 let down_workspace = Fp8GroupedWorkspace::new(
4805 engine,
4806 experts.expert_width,
4807 experts.input_width,
4808 max_pairs,
4809 max_pairs,
4810 )?;
4811 let activation = engine.uninit(
4812 max_pairs
4813 .checked_mul(experts.expert_width)
4814 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4815 )?;
4816 owners.push(PreparedStepGroupedExpertOwner {
4817 rank: owner_rank,
4818 global_pairs: Vec::new(),
4819 route_csr,
4820 down_csr,
4821 gate_workspace,
4822 up_workspace,
4823 down_workspace,
4824 activation,
4825 });
4826 }
4827
4828 let mut plan = PreparedStepGroupedExpertParallelGate {
4829 rank_inputs,
4830 owners,
4831 activation_limit,
4832 tokens: 0,
4833 pairs: 0,
4834 max_tokens,
4835 max_pairs,
4836 input_width: experts.input_width,
4837 expert_width: experts.expert_width,
4838 generation: 0,
4839 executed_generation: None,
4840 ready: false,
4841 };
4842 self.refresh_step_grouped_expert_parallel_gate(
4843 experts, &mut plan, input, tokens, selected,
4844 )?;
4845 Ok(plan)
4846 }
4847
4848 #[allow(clippy::type_complexity)] fn prepare_step_grouped_expert_parallel_refresh(
4850 &self,
4851 experts: &ResidentExpertParallel,
4852 plan: &PreparedStepGroupedExpertParallelGate,
4853 tokens: usize,
4854 selected: &[usize],
4855 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4856 {
4857 validate_ep_residency(&self.ranks, experts)?;
4858 if plan.rank_inputs.len() != self.ranks.len()
4859 || plan.owners.len() != self.ranks.len()
4860 || plan.input_width != experts.input_width
4861 || plan.expert_width != experts.expert_width
4862 || tokens > plan.max_tokens
4863 {
4864 return Err(format!(
4865 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4866 input={}/{} expert={}/{} tokens={}/{}",
4867 plan.rank_inputs.len(),
4868 self.ranks.len(),
4869 plan.owners.len(),
4870 self.ranks.len(),
4871 plan.input_width,
4872 experts.input_width,
4873 plan.expert_width,
4874 experts.expert_width,
4875 tokens,
4876 plan.max_tokens,
4877 )
4878 .into());
4879 }
4880 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4881 if pairs > plan.max_pairs {
4882 return Err(format!(
4883 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4884 plan.max_pairs
4885 )
4886 .into());
4887 }
4888 let next_generation = plan
4889 .generation
4890 .checked_add(1)
4891 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4892 let owner_routes = partition_expert_owner_routes(
4893 experts.expert_count,
4894 self.ranks.len(),
4895 tokens,
4896 STEP_GROUPED_FP8_TOP_K,
4897 selected,
4898 )?;
4899 let mut schedules = Vec::with_capacity(self.ranks.len());
4900 for routes in owner_routes {
4901 if routes.selected.is_empty() {
4902 schedules.push(None);
4903 continue;
4904 }
4905 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4906 let local_pairs = routes.selected.len();
4907 let route_csr = ExpertCsr::from_pair_rows(
4908 local_experts,
4909 tokens,
4910 &routes.selected,
4911 &routes.token_rows,
4912 )?;
4913 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4914 let down_csr = ExpertCsr::from_pair_rows(
4915 local_experts,
4916 local_pairs,
4917 &routes.selected,
4918 &down_rows,
4919 )?;
4920 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4921 global_pairs: routes.global_pairs,
4922 route_csr,
4923 down_csr,
4924 }));
4925 }
4926 Ok((pairs, next_generation, schedules))
4927 }
4928
4929 fn commit_step_grouped_expert_parallel_refresh(
4930 &self,
4931 plan: &mut PreparedStepGroupedExpertParallelGate,
4932 tokens: usize,
4933 pairs: usize,
4934 next_generation: u64,
4935 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4936 ) -> Result<(), Box<dyn std::error::Error>> {
4937 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4938 let engine = &self.ranks[owner.rank];
4939 let _main = engine.gpu.enter_main()?;
4940 if let Some(schedule) = schedule {
4941 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4942 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4943 owner.global_pairs = schedule.global_pairs;
4944 } else {
4945 owner.route_csr.clear();
4946 owner.down_csr.clear();
4947 owner.global_pairs.clear();
4948 }
4949 }
4950 plan.tokens = tokens;
4951 plan.pairs = pairs;
4952 plan.generation = next_generation;
4953 plan.ready = true;
4954 Ok(())
4955 }
4956
4957 pub fn refresh_step_grouped_expert_parallel_gate(
4958 &self,
4959 experts: &ResidentExpertParallel,
4960 plan: &mut PreparedStepGroupedExpertParallelGate,
4961 input: &[f32],
4962 tokens: usize,
4963 selected: &[usize],
4964 ) -> Result<(), Box<dyn std::error::Error>> {
4965 validate_activations(input, tokens, experts.input_width)?;
4966 let (pairs, next_generation, schedules) =
4967 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4968
4969 plan.ready = false;
4970 plan.executed_generation = None;
4971 {
4972 let root = &self.ranks[0];
4973 let _main = root.gpu.enter_main()?;
4974 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4975 root.stream().memcpy_htod(input, &mut destination)?;
4976 root.stream().synchronize()?;
4977 }
4978 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4979 let root_input = &root_inputs[0];
4980 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4981 let engine = &self.ranks[rank + 1];
4982 let _main = engine.gpu.enter_main()?;
4983 let mut destination = peer_input.slice_mut(0..input.len());
4984 engine
4985 .stream()
4986 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4987 }
4988 self.commit_step_grouped_expert_parallel_refresh(
4989 plan,
4990 tokens,
4991 pairs,
4992 next_generation,
4993 schedules,
4994 )
4995 }
4996
4997 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
5002 &self,
5003 experts: &ResidentExpertParallel,
5004 plan: &mut PreparedStepGroupedExpertParallelGate,
5005 input: &CudaSlice<f32>,
5006 tokens: usize,
5007 selected: &[usize],
5008 ) -> Result<(), Box<dyn std::error::Error>> {
5009 let input_values = tokens
5010 .checked_mul(experts.input_width)
5011 .ok_or("Step owner-grouped FP8 input size overflow")?;
5012 let root = self
5013 .ranks
5014 .first()
5015 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
5016 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
5017 return Err(format!(
5018 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
5019 device {}",
5020 input.len(),
5021 input.ordinal(),
5022 input_values,
5023 root.ctx().ordinal(),
5024 )
5025 .into());
5026 }
5027 let (pairs, next_generation, schedules) =
5028 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
5029
5030 plan.ready = false;
5031 plan.executed_generation = None;
5032 {
5033 let _main = root.gpu.enter_main()?;
5034 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
5035 root.stream()
5036 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
5037 root.stream().synchronize()?;
5038 }
5039 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
5040 let root_input = &root_inputs[0];
5041 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
5042 let engine = &self.ranks[rank + 1];
5043 let _main = engine.gpu.enter_main()?;
5044 let mut destination = peer_input.slice_mut(0..input_values);
5045 engine
5046 .stream()
5047 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
5048 }
5049 self.commit_step_grouped_expert_parallel_refresh(
5050 plan,
5051 tokens,
5052 pairs,
5053 next_generation,
5054 schedules,
5055 )
5056 }
5057
5058 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
5063 &self,
5064 experts: &ResidentExpertParallel,
5065 plan: &mut PreparedStepGroupedExpertParallelGate,
5066 input: &ResidentReplicatedDeviceRows,
5067 ) -> Result<(), Box<dyn std::error::Error>> {
5068 validate_ep_residency(&self.ranks, experts)?;
5069 validate_replicated_device_rows(&self.ranks, input)?;
5070 if !plan.ready
5071 || input.tokens != plan.tokens
5072 || input.width != plan.input_width
5073 || input.tokens > plan.max_tokens
5074 || plan.rank_inputs.len() != self.ranks.len()
5075 || plan.owners.len() != self.ranks.len()
5076 || plan.input_width != experts.input_width
5077 || plan.expert_width != experts.expert_width
5078 {
5079 return Err("Step owner-grouped replicated input geometry changed".into());
5080 }
5081 let values = input
5082 .tokens
5083 .checked_mul(input.width)
5084 .ok_or("Step owner-grouped replicated input size overflow")?;
5085 let next_generation = plan
5086 .generation
5087 .checked_add(1)
5088 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
5089 plan.ready = false;
5090 plan.executed_generation = None;
5091 for (rank, engine) in self.ranks.iter().enumerate() {
5092 let _main = engine.gpu.enter_main()?;
5093 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
5094 engine
5095 .stream()
5096 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
5097 }
5098 plan.generation = next_generation;
5099 plan.ready = true;
5100 Ok(())
5101 }
5102
5103 pub fn execute_step_grouped_expert_parallel_gate(
5104 &self,
5105 experts: &ResidentExpertParallel,
5106 plan: &mut PreparedStepGroupedExpertParallelGate,
5107 ) -> Result<(), Box<dyn std::error::Error>> {
5108 validate_ep_residency(&self.ranks, experts)?;
5109 if !plan.ready
5110 || plan.rank_inputs.len() != self.ranks.len()
5111 || plan.owners.len() != self.ranks.len()
5112 || plan.input_width != experts.input_width
5113 || plan.expert_width != experts.expert_width
5114 {
5115 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
5116 }
5117 plan.executed_generation = None;
5118
5119 for owner in &mut plan.owners {
5120 if owner.global_pairs.is_empty() {
5121 continue;
5122 }
5123 let engine = &self.ranks[owner.rank];
5124 let bank = &experts.ranks[owner.rank];
5125 let _main = engine.gpu.enter_main()?;
5126 let local_pairs = owner.global_pairs.len();
5127 owner.gate_workspace.quantize_for_shape(
5128 engine,
5129 &plan.rank_inputs[owner.rank],
5130 plan.tokens,
5131 local_pairs,
5132 )?;
5133 owner.gate_workspace.project(
5134 engine,
5135 &bank.gate.codes,
5136 &bank.gate.scales,
5137 &owner.route_csr,
5138 bank.gate.code_stride,
5139 bank.gate.scale_stride,
5140 1.0,
5141 )?;
5142 owner.up_workspace.quantize_for_shape(
5143 engine,
5144 &plan.rank_inputs[owner.rank],
5145 plan.tokens,
5146 local_pairs,
5147 )?;
5148 owner.up_workspace.project(
5149 engine,
5150 &bank.up.codes,
5151 &bank.up.scales,
5152 &owner.route_csr,
5153 bank.up.code_stride,
5154 bank.up.scale_stride,
5155 1.0,
5156 )?;
5157 }
5158 for owner in &mut plan.owners {
5159 if owner.global_pairs.is_empty() {
5160 continue;
5161 }
5162 let engine = &self.ranks[owner.rank];
5163 let _main = engine.gpu.enter_main()?;
5164 let values = owner.global_pairs.len() * plan.expert_width;
5165 if let Some(limit) = plan.activation_limit {
5166 engine.silu_clamped_mul_host_expf(
5167 owner.gate_workspace.output(),
5168 owner.up_workspace.output(),
5169 limit,
5170 &mut owner.activation,
5171 values,
5172 )?;
5173 } else {
5174 engine.silu_mul_host_expf(
5175 owner.gate_workspace.output(),
5176 owner.up_workspace.output(),
5177 &mut owner.activation,
5178 values,
5179 )?;
5180 }
5181 }
5182 for owner in &mut plan.owners {
5183 if owner.global_pairs.is_empty() {
5184 continue;
5185 }
5186 let engine = &self.ranks[owner.rank];
5187 let bank = &experts.ranks[owner.rank];
5188 let _main = engine.gpu.enter_main()?;
5189 let local_pairs = owner.global_pairs.len();
5190 owner.down_workspace.quantize_for_shape(
5191 engine,
5192 &owner.activation,
5193 local_pairs,
5194 local_pairs,
5195 )?;
5196 owner.down_workspace.project(
5197 engine,
5198 &bank.down.codes,
5199 &bank.down.scales,
5200 &owner.down_csr,
5201 bank.down.code_stride,
5202 bank.down.scale_stride,
5203 1.0,
5204 )?;
5205 }
5206 plan.executed_generation = Some(plan.generation);
5207 Ok(())
5208 }
5209
5210 pub fn collect_step_grouped_expert_parallel_gate(
5211 &self,
5212 plan: &PreparedStepGroupedExpertParallelGate,
5213 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
5214 if !plan.ready || plan.executed_generation != Some(plan.generation) {
5215 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
5216 }
5217 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
5218 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
5219 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
5220 for owner in &plan.owners {
5221 if owner.global_pairs.is_empty() {
5222 continue;
5223 }
5224 let engine = &self.ranks[owner.rank];
5225 let _main = engine.gpu.enter_main()?;
5226 let owner_gate = engine.dtoh_view(
5227 &owner
5228 .gate_workspace
5229 .output()
5230 .slice(0..owner.gate_workspace.output_len()),
5231 )?;
5232 let owner_up = engine.dtoh_view(
5233 &owner
5234 .up_workspace
5235 .output()
5236 .slice(0..owner.up_workspace.output_len()),
5237 )?;
5238 let owner_down = engine.dtoh_view(
5239 &owner
5240 .down_workspace
5241 .output()
5242 .slice(0..owner.down_workspace.output_len()),
5243 )?;
5244 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
5245 let local_expert = local_pair * plan.expert_width;
5246 let global_expert = global_pair * plan.expert_width;
5247 gate[global_expert..global_expert + plan.expert_width]
5248 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
5249 up[global_expert..global_expert + plan.expert_width]
5250 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
5251
5252 let local_hidden = local_pair * plan.input_width;
5253 let global_hidden = global_pair * plan.input_width;
5254 down[global_hidden..global_hidden + plan.input_width]
5255 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
5256 }
5257 }
5258 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
5259 }
5260
5261 pub fn run_step_grouped_expert_parallel_gate(
5262 &self,
5263 experts: &ResidentExpertParallel,
5264 plan: &mut PreparedStepGroupedExpertParallelGate,
5265 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
5266 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
5267 self.collect_step_grouped_expert_parallel_gate(plan)
5268 }
5269
5270 pub fn prepare_step_grouped_expert_parallel_combine(
5271 &self,
5272 plan: &PreparedStepGroupedExpertParallelGate,
5273 route_weights: &[f32],
5274 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
5275 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
5276 return Err(
5277 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
5278 );
5279 }
5280 let owner_pairs = plan
5281 .owners
5282 .iter()
5283 .map(|owner| owner.global_pairs.as_slice())
5284 .collect::<Vec<_>>();
5285 let shape = validate_weighted_route_combine(
5286 plan.input_width,
5287 STEP_GROUPED_FP8_TOP_K,
5288 plan.max_tokens,
5289 plan.tokens,
5290 &owner_pairs,
5291 route_weights,
5292 )?;
5293 if shape.max_pairs != plan.max_pairs {
5294 return Err(format!(
5295 "Step owner-grouped combine capacity {} != projection capacity {}",
5296 shape.max_pairs, plan.max_pairs
5297 )
5298 .into());
5299 }
5300 let root = self
5301 .ranks
5302 .first()
5303 .ok_or("Step owner-grouped combine has no root rank")?;
5304 let slot_values = shape
5305 .max_pairs
5306 .checked_mul(plan.input_width)
5307 .ok_or("Step owner-grouped combine slot capacity overflow")?;
5308 let output_values = plan
5309 .max_tokens
5310 .checked_mul(plan.input_width)
5311 .ok_or("Step owner-grouped combine output capacity overflow")?;
5312 let (root_device, owners, peer_staging, slots, weights, output) = {
5313 let _main = root.gpu.enter_main()?;
5314 let mut owners = Vec::with_capacity(plan.owners.len());
5315 for _ in &plan.owners {
5316 owners.push(PreparedPeerWeightedRouteOwner {
5317 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
5318 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
5319 weights: root.htod(&vec![0.0; shape.max_pairs])?,
5320 active_pairs: 0,
5321 });
5322 }
5323 (
5324 root.ctx().ordinal(),
5325 owners,
5326 root.uninit(slot_values)?,
5327 root.uninit(slot_values)?,
5328 root.uninit(shape.max_pairs)?,
5329 root.uninit(output_values)?,
5330 )
5331 };
5332 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
5333 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
5334 for engine in self.ranks.iter().skip(1) {
5335 let _main = engine.gpu.enter_main()?;
5336 peer_devices.push(engine.ctx().ordinal());
5337 peer_outputs.push(engine.uninit(output_values)?);
5338 }
5339 let mut combine = PreparedPeerWeightedRouteCombine {
5340 root_device,
5341 owners,
5342 peer_staging,
5343 slots,
5344 weights,
5345 output,
5346 peer_devices,
5347 peer_outputs,
5348 width: plan.input_width,
5349 experts_per_token: STEP_GROUPED_FP8_TOP_K,
5350 max_tokens: plan.max_tokens,
5351 max_pairs: shape.max_pairs,
5352 tokens: 0,
5353 pairs: 0,
5354 projection_generation: 0,
5355 output_generation: None,
5356 broadcast_generation: None,
5357 ready: false,
5358 };
5359 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
5360 Ok(combine)
5361 }
5362
5363 pub fn refresh_step_grouped_expert_parallel_combine(
5364 &self,
5365 plan: &PreparedStepGroupedExpertParallelGate,
5366 combine: &mut PreparedPeerWeightedRouteCombine,
5367 route_weights: &[f32],
5368 ) -> Result<(), Box<dyn std::error::Error>> {
5369 let output_capacity = combine
5370 .max_tokens
5371 .checked_mul(combine.width)
5372 .ok_or("Step owner-grouped combine output capacity overflow")?;
5373 if !plan.ready
5374 || combine.owners.len() != plan.owners.len()
5375 || combine.peer_devices.len() + 1 != self.ranks.len()
5376 || combine.peer_outputs.len() + 1 != self.ranks.len()
5377 || combine.width != plan.input_width
5378 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
5379 || combine.max_tokens != plan.max_tokens
5380 || combine.max_pairs != plan.max_pairs
5381 || combine.output.len() < output_capacity
5382 || combine
5383 .peer_outputs
5384 .iter()
5385 .any(|output| output.len() < output_capacity)
5386 {
5387 return Err("Step owner-grouped combine/projection geometry changed".into());
5388 }
5389 if self
5390 .ranks
5391 .iter()
5392 .skip(1)
5393 .zip(&combine.peer_devices)
5394 .any(|(engine, &device)| engine.ctx().ordinal() != device)
5395 {
5396 return Err("Step owner-grouped combine peer devices changed".into());
5397 }
5398 let owner_pairs = plan
5399 .owners
5400 .iter()
5401 .map(|owner| owner.global_pairs.as_slice())
5402 .collect::<Vec<_>>();
5403 let shape = validate_weighted_route_combine(
5404 combine.width,
5405 combine.experts_per_token,
5406 combine.max_tokens,
5407 plan.tokens,
5408 &owner_pairs,
5409 route_weights,
5410 )?;
5411 if shape.max_pairs != combine.max_pairs {
5412 return Err("Step owner-grouped combine capacity changed during refresh".into());
5413 }
5414 let metadata = owner_pairs
5415 .iter()
5416 .map(|pairs| {
5417 let token_rows = pairs
5418 .iter()
5419 .map(|&pair| (pair / combine.experts_per_token) as i32)
5420 .collect::<Vec<_>>();
5421 let slots = pairs
5422 .iter()
5423 .map(|&pair| (pair % combine.experts_per_token) as i32)
5424 .collect::<Vec<_>>();
5425 let weights = pairs
5426 .iter()
5427 .map(|&pair| route_weights[pair])
5428 .collect::<Vec<_>>();
5429 (token_rows, slots, weights)
5430 })
5431 .collect::<Vec<_>>();
5432
5433 combine.ready = false;
5434 combine.output_generation = None;
5435 combine.broadcast_generation = None;
5436 let root = self
5437 .ranks
5438 .first()
5439 .ok_or("Step owner-grouped combine has no root rank")?;
5440 let _main = root.gpu.enter_main()?;
5441 if root.ctx().ordinal() != combine.root_device {
5442 return Err(format!(
5443 "Step owner-grouped combine root device changed {} != {}",
5444 root.ctx().ordinal(),
5445 combine.root_device
5446 )
5447 .into());
5448 }
5449 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
5450 if token_rows.is_empty() {
5451 owner.active_pairs = 0;
5452 continue;
5453 }
5454 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
5455 root.htod_i32_into(&mut owner.slots, &slots)?;
5456 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
5457 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
5458 owner.active_pairs = token_rows.len();
5459 }
5460 combine.tokens = plan.tokens;
5461 combine.pairs = shape.pairs;
5462 combine.projection_generation = plan.generation;
5463 combine.ready = true;
5464 Ok(())
5465 }
5466
5467 pub fn execute_step_grouped_expert_parallel_combine(
5468 &self,
5469 plan: &PreparedStepGroupedExpertParallelGate,
5470 combine: &mut PreparedPeerWeightedRouteCombine,
5471 ) -> Result<(), Box<dyn std::error::Error>> {
5472 if !plan.ready
5473 || plan.executed_generation != Some(plan.generation)
5474 || !combine.ready
5475 || combine.tokens != plan.tokens
5476 || combine.pairs != plan.pairs
5477 || combine.width != plan.input_width
5478 || combine.owners.len() != plan.owners.len()
5479 || combine.projection_generation != plan.generation
5480 {
5481 return Err("Step owner-grouped combine is stale or its geometry changed".into());
5482 }
5483 combine.output_generation = None;
5484 combine.broadcast_generation = None;
5485 for owner in &plan.owners {
5486 if owner.rank == 0 || owner.global_pairs.is_empty() {
5487 continue;
5488 }
5489 let engine = &self.ranks[owner.rank];
5490 let _main = engine.gpu.enter_main()?;
5491 engine.stream().synchronize()?;
5492 }
5493 let root = self
5494 .ranks
5495 .first()
5496 .ok_or("Step owner-grouped combine has no root rank")?;
5497 let _main = root.gpu.enter_main()?;
5498 if root.ctx().ordinal() != combine.root_device {
5499 return Err("Step owner-grouped combine is not resident on the root device".into());
5500 }
5501 for (index, owner) in plan.owners.iter().enumerate() {
5502 let metadata = &combine.owners[index];
5503 if owner.global_pairs.len() != metadata.active_pairs {
5504 return Err(format!(
5505 "Step owner-grouped combine owner {index} rows {} != metadata {}",
5506 owner.global_pairs.len(),
5507 metadata.active_pairs
5508 )
5509 .into());
5510 }
5511 if metadata.active_pairs == 0 {
5512 continue;
5513 }
5514 let values = metadata
5515 .active_pairs
5516 .checked_mul(combine.width)
5517 .ok_or("Step owner-grouped combine peer value count overflow")?;
5518 if owner.rank == 0 {
5519 root.scatter_slot(
5520 owner.down_workspace.output(),
5521 &metadata.token_rows,
5522 &metadata.slots,
5523 &metadata.weights,
5524 &mut combine.slots,
5525 &mut combine.weights,
5526 combine.width,
5527 combine.experts_per_token,
5528 metadata.active_pairs,
5529 )?;
5530 } else {
5531 let source = owner.down_workspace.output().slice(0..values);
5532 let mut destination = combine.peer_staging.slice_mut(0..values);
5533 root.stream().memcpy_dtod(&source, &mut destination)?;
5534 root.scatter_slot(
5535 &combine.peer_staging,
5536 &metadata.token_rows,
5537 &metadata.slots,
5538 &metadata.weights,
5539 &mut combine.slots,
5540 &mut combine.weights,
5541 combine.width,
5542 combine.experts_per_token,
5543 metadata.active_pairs,
5544 )?;
5545 }
5546 }
5547 root.reduce_slots_host(
5548 &combine.slots,
5549 &combine.weights,
5550 &mut combine.output,
5551 combine.width,
5552 combine.experts_per_token,
5553 combine.tokens,
5554 )?;
5555 combine.output_generation = Some(plan.generation);
5556 Ok(())
5557 }
5558
5559 pub fn collect_step_grouped_expert_parallel_combine(
5560 &self,
5561 plan: &PreparedStepGroupedExpertParallelGate,
5562 combine: &PreparedPeerWeightedRouteCombine,
5563 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5564 if !plan.ready
5565 || combine.output_generation != Some(plan.generation)
5566 || combine.projection_generation != plan.generation
5567 {
5568 return Err("Step owner-grouped combine output is stale or has not executed".into());
5569 }
5570 let root = self
5571 .ranks
5572 .first()
5573 .ok_or("Step owner-grouped combine has no root rank")?;
5574 let _main = root.gpu.enter_main()?;
5575 if root.ctx().ordinal() != combine.root_device {
5576 return Err("Step owner-grouped combine is not resident on the root device".into());
5577 }
5578 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
5579 }
5580
5581 pub fn copy_step_grouped_expert_parallel_combine_root(
5586 &self,
5587 plan: &PreparedStepGroupedExpertParallelGate,
5588 combine: &PreparedPeerWeightedRouteCombine,
5589 destination: &Engine,
5590 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5591 if !plan.ready
5592 || combine.output_generation != Some(plan.generation)
5593 || combine.projection_generation != plan.generation
5594 {
5595 return Err("Step owner-grouped combine output is stale or has not executed".into());
5596 }
5597 let root = self
5598 .ranks
5599 .first()
5600 .ok_or("Step owner-grouped combine has no root rank")?;
5601 if root.ctx().ordinal() != combine.root_device
5602 || destination.ctx().ordinal() != combine.root_device
5603 {
5604 return Err(format!(
5605 "Step owner-grouped combine root/destination devices {}/{} != {}",
5606 root.ctx().ordinal(),
5607 destination.ctx().ordinal(),
5608 combine.root_device,
5609 )
5610 .into());
5611 }
5612 let values = combine
5613 .tokens
5614 .checked_mul(combine.width)
5615 .ok_or("Step owner-grouped combine copy size overflow")?;
5616 {
5617 let _main = root.gpu.enter_main()?;
5618 root.stream().synchronize()?;
5619 }
5620 let _main = destination.gpu.enter_main()?;
5621 let mut output = destination.uninit(values)?;
5622 destination
5623 .stream()
5624 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
5625 Ok(output)
5626 }
5627
5628 pub fn broadcast_step_grouped_expert_parallel_combine(
5629 &self,
5630 plan: &PreparedStepGroupedExpertParallelGate,
5631 combine: &mut PreparedPeerWeightedRouteCombine,
5632 ) -> Result<(), Box<dyn std::error::Error>> {
5633 if !plan.ready
5634 || combine.output_generation != Some(plan.generation)
5635 || combine.projection_generation != plan.generation
5636 || combine.peer_devices.len() + 1 != self.ranks.len()
5637 || combine.peer_outputs.len() + 1 != self.ranks.len()
5638 {
5639 return Err("Step owner-grouped combine output cannot be broadcast".into());
5640 }
5641 combine.broadcast_generation = None;
5642 let values = combine
5643 .tokens
5644 .checked_mul(combine.width)
5645 .ok_or("Step owner-grouped combine broadcast size overflow")?;
5646 {
5647 let root = self
5648 .ranks
5649 .first()
5650 .ok_or("Step owner-grouped combine has no root rank")?;
5651 let _main = root.gpu.enter_main()?;
5652 if root.ctx().ordinal() != combine.root_device {
5653 return Err("Step owner-grouped combine root device changed".into());
5654 }
5655 root.stream().synchronize()?;
5656 }
5657 let source = &combine.output;
5658 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
5659 let engine = &self.ranks[index + 1];
5660 let _main = engine.gpu.enter_main()?;
5661 if engine.ctx().ordinal() != combine.peer_devices[index] {
5662 return Err(format!(
5663 "Step owner-grouped combine peer {} device changed",
5664 index + 1
5665 )
5666 .into());
5667 }
5668 let mut destination = destination_buffer.slice_mut(0..values);
5669 engine
5670 .stream()
5671 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
5672 }
5673 combine.broadcast_generation = Some(plan.generation);
5674 Ok(())
5675 }
5676
5677 pub fn collect_step_grouped_expert_parallel_broadcast(
5678 &self,
5679 plan: &PreparedStepGroupedExpertParallelGate,
5680 combine: &PreparedPeerWeightedRouteCombine,
5681 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
5682 if !plan.ready
5683 || combine.output_generation != Some(plan.generation)
5684 || combine.broadcast_generation != Some(plan.generation)
5685 || combine.peer_outputs.len() + 1 != self.ranks.len()
5686 {
5687 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
5688 }
5689 let values = combine
5690 .tokens
5691 .checked_mul(combine.width)
5692 .ok_or("Step owner-grouped combine collection size overflow")?;
5693 let mut outputs = Vec::with_capacity(self.ranks.len());
5694 {
5695 let root = &self.ranks[0];
5696 let _main = root.gpu.enter_main()?;
5697 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
5698 }
5699 for (index, output) in combine.peer_outputs.iter().enumerate() {
5700 let engine = &self.ranks[index + 1];
5701 let _main = engine.gpu.enter_main()?;
5702 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
5703 }
5704 Ok(outputs)
5705 }
5706
5707 pub fn finish_step_grouped_expert_parallel_layer(
5709 &self,
5710 plan: &PreparedStepGroupedExpertParallelGate,
5711 combine: &PreparedPeerWeightedRouteCombine,
5712 shared: &ResidentReplicatedDeviceRows,
5713 residual: &ResidentReplicatedDeviceRows,
5714 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
5715 validate_replicated_device_rows(&self.ranks, shared)?;
5716 validate_replicated_device_rows(&self.ranks, residual)?;
5717 if !plan.ready
5718 || plan.executed_generation != Some(plan.generation)
5719 || combine.output_generation != Some(plan.generation)
5720 || combine.broadcast_generation != Some(plan.generation)
5721 || combine.projection_generation != plan.generation
5722 || combine.peer_outputs.len() + 1 != self.ranks.len()
5723 || shared.tokens != combine.tokens
5724 || residual.tokens != combine.tokens
5725 || shared.width != combine.width
5726 || residual.width != combine.width
5727 {
5728 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
5729 }
5730 let values = combine
5731 .tokens
5732 .checked_mul(combine.width)
5733 .ok_or("Step full-layer output size overflow")?;
5734 let mut ranks = Vec::with_capacity(self.ranks.len());
5735 for rank in 0..self.ranks.len() {
5736 let engine = &self.ranks[rank];
5737 let _main = engine.gpu.enter_main()?;
5738 let routed = if rank == 0 {
5739 &combine.output
5740 } else {
5741 &combine.peer_outputs[rank - 1]
5742 };
5743 let mut ffn = engine.uninit(values)?;
5744 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
5745 let mut output = engine.uninit(values)?;
5746 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5747 ranks.push(output);
5748 }
5749 Ok(ResidentReplicatedDeviceRows {
5750 ranks,
5751 tokens: combine.tokens,
5752 width: combine.width,
5753 })
5754 }
5755
5756 pub fn run_step_grouped_expert_parallel_combine(
5757 &self,
5758 plan: &PreparedStepGroupedExpertParallelGate,
5759 combine: &mut PreparedPeerWeightedRouteCombine,
5760 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5761 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5762 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5763 }
5764
5765 pub fn upload_tensor_parallel(
5766 &self,
5767 gate: E4m3ExpertBank<'_>,
5768 up: E4m3ExpertBank<'_>,
5769 down: E4m3ExpertBank<'_>,
5770 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5771 gate.validate()?;
5772 up.validate()?;
5773 down.validate()?;
5774 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5775 return Err("TP gate/up/down expert counts differ".into());
5776 }
5777 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5778 return Err("TP gate/up dimensions differ".into());
5779 }
5780 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5781 return Err(format!(
5782 "TP down {}x{} does not invert gate/up {}x{}",
5783 down.out_features, down.in_features, gate.out_features, gate.in_features
5784 )
5785 .into());
5786 }
5787 let tp = self.ranks.len();
5788 validate_column_bank_shape(gate, tp)?;
5789 validate_column_bank_shape(up, tp)?;
5790 validate_row_bank_shape(down, tp)?;
5791
5792 let mut gate_ranks = Vec::with_capacity(tp);
5793 let mut up_ranks = Vec::with_capacity(tp);
5794 let mut down_ranks = Vec::with_capacity(tp);
5795 for (rank, engine) in self.ranks.iter().enumerate() {
5796 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5797 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5798 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5799 }
5800 Ok(ResidentTensorParallel {
5801 bank: ResidentTpExpertBank {
5802 gate: gate_ranks,
5803 up: up_ranks,
5804 down: down_ranks,
5805 expert_count: gate.expert_count,
5806 input_width: gate.in_features,
5807 expert_width: gate.out_features,
5808 },
5809 })
5810 }
5811
5812 pub fn run_tensor_parallel_routes(
5813 &self,
5814 experts: &ResidentTensorParallel,
5815 input: &[f32],
5816 tokens: usize,
5817 selected: &[usize],
5818 route_weights: &[f32],
5819 experts_per_token: usize,
5820 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5821 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5822 validate_activations(input, tokens, experts.bank.input_width)?;
5823 let pairs = tokens
5824 .checked_mul(experts_per_token)
5825 .ok_or("TP route count overflow")?;
5826 if selected.len() != pairs || route_weights.len() != pairs {
5827 return Err(format!(
5828 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5829 {experts_per_token} ({pairs})",
5830 selected.len(),
5831 route_weights.len(),
5832 )
5833 .into());
5834 }
5835 if !route_weights.iter().all(|weight| weight.is_finite()) {
5836 return Err("TP route weights contain a non-finite value".into());
5837 }
5838
5839 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5840 for token in 0..tokens {
5841 let input_row =
5842 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5843 for slot in 0..experts_per_token {
5844 let pair = token * experts_per_token + slot;
5845 let expert = selected[pair];
5846 if expert >= experts.bank.expert_count {
5847 return Err(format!(
5848 "TP selected expert {expert} outside 0..{}",
5849 experts.bank.expert_count
5850 )
5851 .into());
5852 }
5853 let down = if self.native_p2p {
5854 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5855 } else {
5856 let gate =
5857 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5858 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5859 let activated: Vec<f32> = gate
5860 .iter()
5861 .zip(&up)
5862 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5863 .collect();
5864 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5865 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5866 };
5867 let weight = route_weights[pair];
5868 for (sum, value) in output
5869 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5870 .iter_mut()
5871 .zip(down)
5872 {
5873 *sum += weight * value;
5874 }
5875 }
5876 }
5877 Ok(output)
5878 }
5879
5880 fn run_column_bank_expert(
5881 &self,
5882 ranks: &[ResidentE4m3ExpertBankRank],
5883 expert: usize,
5884 input: &[f32],
5885 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5886 let local_out = ranks
5887 .first()
5888 .ok_or("TP column bank has no ranks")?
5889 .out_features;
5890 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5891 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5892 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5893 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5894 }
5895 Ok(gathered)
5896 }
5897
5898 fn run_row_bank_expert(
5899 &self,
5900 ranks: &[ResidentE4m3ExpertBankRank],
5901 expert: usize,
5902 input: &[f32],
5903 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5904 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5905 if input.len() != local_in * ranks.len() {
5906 return Err(format!(
5907 "TP row input {} != {} ranks x {local_in}",
5908 input.len(),
5909 ranks.len()
5910 )
5911 .into());
5912 }
5913 let out_features = ranks[0].out_features;
5914 let mut reduced = vec![0.0f32; out_features];
5915 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5916 let blocks = bank
5917 .k_blocks
5918 .ok_or("TP row bank is not packed in native K-block order")?;
5919 if blocks * FP8_BLOCK != local_in {
5920 return Err(format!(
5921 "TP row bank has {blocks} blocks but local input width is {local_in}"
5922 )
5923 .into());
5924 }
5925 for block in 0..blocks {
5926 let global_start = rank * local_in + block * FP8_BLOCK;
5927 let partial = run_resident_bank_expert_block(
5928 engine,
5929 bank,
5930 expert,
5931 block,
5932 &input[global_start..global_start + FP8_BLOCK],
5933 )?;
5934 for (sum, value) in reduced.iter_mut().zip(partial) {
5935 *sum += value;
5936 }
5937 }
5938 }
5939 Ok(reduced)
5940 }
5941
5942 fn run_tensor_parallel_expert_native(
5943 &self,
5944 bank: &ResidentTpExpertBank,
5945 expert: usize,
5946 input: &[f32],
5947 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5948 if !self.native_p2p || self.ranks.len() < 2 {
5949 return Err("native TP expert execution requires at least two P2P ranks".into());
5950 }
5951 let local_out = bank
5952 .gate
5953 .first()
5954 .ok_or("native TP gate bank has no ranks")?
5955 .out_features;
5956 if local_out * self.ranks.len() != bank.expert_width {
5957 return Err(format!(
5958 "native TP gate shards {}x{local_out} != expert width {}",
5959 self.ranks.len(),
5960 bank.expert_width
5961 )
5962 .into());
5963 }
5964
5965 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5968 let root_input = {
5969 let root = &self.ranks[0];
5970 let _main = root.gpu.enter_main()?;
5971 root.htod(input)?
5972 };
5973 rank_inputs.push(root_input);
5974 for engine in &self.ranks[1..] {
5975 let peer_input = {
5976 let _main = engine.gpu.enter_main()?;
5977 let mut peer_input = engine.uninit(input.len())?;
5978 engine
5979 .stream()
5980 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5981 peer_input
5982 };
5983 rank_inputs.push(peer_input);
5984 }
5985
5986 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5987 let mut up_shards = Vec::with_capacity(self.ranks.len());
5988 #[allow(clippy::needless_range_loop)]
5989 for rank in 0..self.ranks.len() {
5991 gate_shards.push(run_resident_bank_expert_device(
5992 &self.ranks[rank],
5993 &bank.gate[rank],
5994 expert,
5995 &rank_inputs[rank],
5996 1,
5997 )?);
5998 up_shards.push(run_resident_bank_expert_device(
5999 &self.ranks[rank],
6000 &bank.up[rank],
6001 expert,
6002 &rank_inputs[rank],
6003 1,
6004 )?);
6005 }
6006
6007 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
6011 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
6012 let activated = gate
6013 .iter()
6014 .zip(&up)
6015 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
6016 .collect::<Vec<_>>();
6017 debug_assert_eq!(activated.len(), bank.expert_width);
6018
6019 let root_activated = {
6020 let root = &self.ranks[0];
6021 let _main = root.gpu.enter_main()?;
6022 root.htod(&activated)?
6023 };
6024 let mut rank_activated = Vec::with_capacity(self.ranks.len());
6025 for (rank, engine) in self.ranks.iter().enumerate() {
6026 let start = rank * local_out;
6027 let source = root_activated.slice(start..start + local_out);
6028 let local = {
6029 let _main = engine.gpu.enter_main()?;
6030 let mut local = engine.uninit(local_out)?;
6031 engine.stream().memcpy_dtod(&source, &mut local)?;
6032 local
6033 };
6034 rank_activated.push(local);
6035 }
6036
6037 let out_features = bank
6038 .down
6039 .first()
6040 .ok_or("native TP down bank has no ranks")?
6041 .out_features;
6042 let mut reduced = {
6043 let root = &self.ranks[0];
6044 let _main = root.gpu.enter_main()?;
6045 root.htod(&vec![0.0f32; out_features])?
6046 };
6047 let mut remote_partial_keepalive = Vec::new();
6048 #[allow(clippy::needless_range_loop)]
6049 for rank in 0..self.ranks.len() {
6051 let down = &bank.down[rank];
6052 let blocks = down
6053 .k_blocks
6054 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
6055 if blocks * FP8_BLOCK != local_out {
6056 return Err(format!(
6057 "native TP rank {rank} has {blocks} blocks but local activation width is \
6058 {local_out}"
6059 )
6060 .into());
6061 }
6062 for block in 0..blocks {
6063 let start = block * FP8_BLOCK;
6064 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
6065 let partial = run_resident_bank_expert_block_device(
6066 &self.ranks[rank],
6067 down,
6068 expert,
6069 block,
6070 &input_block,
6071 )?;
6072 let root_partial = if rank == 0 {
6073 partial
6074 } else {
6075 let root = &self.ranks[0];
6076 let _main = root.gpu.enter_main()?;
6077 let mut peer_partial = root.uninit(out_features)?;
6078 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
6079 remote_partial_keepalive.push(partial);
6080 peer_partial
6081 };
6082 let next = {
6083 let root = &self.ranks[0];
6084 let _main = root.gpu.enter_main()?;
6085 let mut next = root.uninit(out_features)?;
6086 root.add(&reduced, &root_partial, &mut next, out_features)?;
6087 next
6088 };
6089 reduced = next;
6090 }
6091 }
6092 let output = {
6093 let root = &self.ranks[0];
6094 let _main = root.gpu.enter_main()?;
6095 root.dtoh(&reduced)?
6096 };
6097 drop(remote_partial_keepalive);
6098 Ok(output)
6099 }
6100
6101 pub fn gather_native_column_shards_device(
6103 &self,
6104 shards: &[CudaSlice<f32>],
6105 tokens: usize,
6106 local_out: usize,
6107 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6108 let shard_len = tokens
6109 .checked_mul(local_out)
6110 .ok_or("native TP gather shard size overflow")?;
6111 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
6112 return Err("native TP gather shard geometry mismatch".into());
6113 }
6114 for engine in &self.ranks[1..] {
6118 let _main = engine.gpu.enter_main()?;
6119 engine.stream().synchronize()?;
6120 }
6121 let root = &self.ranks[0];
6122 let _main = root.gpu.enter_main()?;
6123 let global_out = shards
6124 .len()
6125 .checked_mul(local_out)
6126 .ok_or("native TP gather output width overflow")?;
6127 let gathered_len = tokens
6128 .checked_mul(global_out)
6129 .ok_or("native TP gather output size overflow")?;
6130 let mut gathered = root.uninit(gathered_len)?;
6131 if self.bulk_p2p {
6132 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
6133 if shards.len() > 1 {
6134 let mut staging = root.uninit(shard_len)?;
6135 for (rank, shard) in shards.iter().enumerate().skip(1) {
6136 root.stream().memcpy_dtod(shard, &mut staging)?;
6137 root.place_rows_strided(
6138 &staging,
6139 &mut gathered,
6140 local_out,
6141 tokens,
6142 global_out,
6143 rank * local_out,
6144 )?;
6145 }
6146 }
6147 } else {
6148 for token in 0..tokens {
6149 for (rank, shard) in shards.iter().enumerate() {
6150 let source = shard.slice(token * local_out..(token + 1) * local_out);
6151 let start = token * global_out + rank * local_out;
6152 let mut destination = gathered.slice_mut(start..start + local_out);
6153 root.stream().memcpy_dtod(&source, &mut destination)?;
6154 }
6155 }
6156 }
6157 Ok(gathered)
6158 }
6159
6160 pub fn gather_native_column_shards(
6161 &self,
6162 shards: &[CudaSlice<f32>],
6163 tokens: usize,
6164 local_out: usize,
6165 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6166 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
6167 let root = &self.ranks[0];
6168 let _main = root.gpu.enter_main()?;
6169 root.dtoh(&gathered)
6170 }
6171
6172 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
6173 &self.decode_v2
6174 }
6175
6176 #[allow(clippy::manual_is_multiple_of)] pub(crate) fn decode_v2_ensure(
6186 &self,
6187 e: &Engine,
6188 q_m: &ResidentBf16ColumnParallel,
6189 k_m: &ResidentBf16ColumnParallel,
6190 v_m: &ResidentBf16ColumnParallel,
6191 o_m: &ResidentStepBf16RowParallel,
6192 heads: usize,
6193 ) -> Result<usize, Box<dyn std::error::Error>> {
6194 if self.ranks.len() > 1 && !self.native_p2p {
6195 return Err("step TP decode v2 requires native P2P ranks".into());
6196 }
6197 let ranks = self.ranks.len();
6198 let fused_door = step_tp_qkv_fused_enabled()?;
6202 let arm_ok = |weight: &ResidentBf16Weight| match weight {
6203 ResidentBf16Weight::F32(_) => true,
6204 ResidentBf16Weight::Bf16(_) => fused_door,
6205 };
6206 for matrix in [q_m, k_m, v_m] {
6207 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
6208 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
6209 return Err("step TP decode v2 QKV geometry mismatch".into());
6210 }
6211 for rank in &matrix.ranks {
6212 if !arm_ok(&rank.weight) {
6213 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
6214 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
6215 .into());
6216 }
6217 }
6218 }
6219 validate_step_bf16_row_residency(&self.ranks, o_m)?;
6220 for blocks in &o_m.ranks {
6221 for block in blocks {
6222 if !arm_ok(&block.weight) {
6223 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
6224 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
6225 .into());
6226 }
6227 }
6228 }
6229 if v_m.out_features != k_m.out_features
6230 || o_m.in_features != q_m.out_features
6231 || heads == 0
6232 || heads % ranks != 0
6233 {
6234 return Err("step TP decode v2 K/V/O geometry mismatch".into());
6235 }
6236 let local_q_dim = q_m.out_features / ranks;
6237 let local_kv_dim = k_m.out_features / ranks;
6238 let o_out = o_m.out_features;
6239 let o_block_cols = o_m.canonical_chunk_cols;
6240 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
6241 if blocks_per_rank == 0
6242 || o_m
6243 .ranks
6244 .iter()
6245 .any(|blocks| blocks.len() != blocks_per_rank)
6246 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
6247 {
6248 return Err("step TP decode v2 O canonical block grid mismatch".into());
6249 }
6250
6251 let mut guard = self
6252 .decode_v2
6253 .lock()
6254 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6255 if let Some(index) = guard.iter().position(|ws| {
6256 ws.local_q_dim == local_q_dim
6257 && ws.local_kv_dim == local_kv_dim
6258 && ws.heads == heads
6259 && ws.o_out == o_out
6260 && ws.o_block_cols == o_block_cols
6261 && ws.blocks_per_rank == blocks_per_rank
6262 && ws.e_device == e.ctx().ordinal()
6263 && ws.q.len() == ranks
6264 }) {
6265 return Ok(index);
6266 }
6267
6268 let mut q_raw = Vec::with_capacity(ranks);
6269 let mut k_raw = Vec::with_capacity(ranks);
6270 let mut v_raw = Vec::with_capacity(ranks);
6271 let mut q = Vec::with_capacity(ranks);
6272 let mut k = Vec::with_capacity(ranks);
6273 let mut pos = Vec::with_capacity(ranks);
6274 let mut gate = Vec::with_capacity(ranks);
6275 let mut attn_out = Vec::with_capacity(ranks);
6276 let mut gated = Vec::with_capacity(ranks);
6277 let mut fuse_ctr = Vec::with_capacity(ranks);
6278 let mut o_partials = Vec::with_capacity(ranks);
6279 let mut ev_rank = Vec::with_capacity(ranks);
6280 let direct_join = oproj_direct_on();
6281 for (rank, engine) in self.ranks.iter().enumerate() {
6282 let _main = engine.gpu.enter_main()?;
6283 q_raw.push(engine.uninit(local_q_dim)?);
6284 k_raw.push(engine.uninit(local_kv_dim)?);
6285 v_raw.push(engine.uninit(local_kv_dim)?);
6286 q.push(engine.uninit(local_q_dim)?);
6287 k.push(engine.uninit(local_kv_dim)?);
6288 pos.push(engine.htod_i32(&[0])?);
6289 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
6290 gate.push(engine.uninit(heads / ranks)?);
6291 attn_out.push(engine.uninit(local_q_dim)?);
6292 gated.push(engine.uninit(local_q_dim)?);
6293 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
6294 for _ in 0..blocks_per_rank {
6295 if direct_join && rank != 0 {
6298 let root = &self.ranks[0];
6299 let _root_main = root.gpu.enter_main()?;
6300 rank_partials.push(root.uninit(o_out)?);
6301 } else {
6302 rank_partials.push(engine.uninit(o_out)?);
6303 }
6304 }
6305 o_partials.push(rank_partials);
6306 ev_rank.push(engine.ctx().new_event(None)?);
6307 }
6308 use cudarc::driver::DevicePtr;
6309 let mut raw_o_partials = Vec::with_capacity(ranks);
6310 let mut raw_k = Vec::with_capacity(ranks);
6311 let mut raw_v_raw = Vec::with_capacity(ranks);
6312 for rank in 0..ranks {
6313 let engine = &self.ranks[rank];
6314 {
6315 let _main = engine.gpu.enter_main()?;
6316 let stream = engine.stream();
6317 let (k_ptr, _k_guard) = k[rank].device_ptr(&stream);
6318 let (v_ptr, _v_guard) = v_raw[rank].device_ptr(&stream);
6319 raw_k.push(k_ptr);
6320 raw_v_raw.push(v_ptr);
6321 }
6322 let partial_engine = if direct_join && rank != 0 {
6323 &self.ranks[0]
6324 } else {
6325 engine
6326 };
6327 let _main = partial_engine.gpu.enter_main()?;
6328 let stream = partial_engine.stream();
6329 let mut rank_raw = Vec::with_capacity(blocks_per_rank);
6330 for partial in &o_partials[rank] {
6331 let (ptr, _guard) = partial.device_ptr(&stream);
6332 rank_raw.push(ptr);
6333 }
6334 raw_o_partials.push(rank_raw);
6335 }
6336 let root = &self.ranks[0];
6337 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
6338 let _main = root.gpu.enter_main()?;
6339 (
6340 root.uninit(o_out)?,
6341 root.uninit(o_out)?,
6342 root.uninit(o_out)?,
6343 root.htod(&vec![0.0f32; o_out])?,
6344 root.uninit(ranks * local_kv_dim)?,
6345 root.uninit(ranks * local_kv_dim)?,
6346 root.ctx().new_event(None)?,
6347 root.ctx().new_event(None)?,
6348 )
6349 };
6350 let (raw_peer_partial, raw_k_shadow, raw_v_shadow) = {
6351 let _main = root.gpu.enter_main()?;
6352 let stream = root.stream();
6353 let (peer, _peer_guard) = peer_partial.device_ptr(&stream);
6354 let (k, _k_guard) = k_shadow.device_ptr(&stream);
6355 let (v, _v_guard) = v_shadow.device_ptr(&stream);
6356 (peer, k, v)
6357 };
6358 let (gate_e, ev_entry) = {
6359 let _main = e.gpu.enter_main()?;
6360 (e.uninit(heads)?, e.ctx().new_event(None)?)
6361 };
6362 let raw_attn_in = Vec::new();
6363 let raw_pos = Vec::new();
6364 guard.push(StepTpDecodeV2Ws {
6365 tcol_q: Vec::new(),
6366 tcol_k: Vec::new(),
6367 tcol_v: Vec::new(),
6368 tcol_g: Vec::new(),
6369 tcol_in: Vec::new(),
6370 tcol_cap: 0,
6371 w8_aq: Vec::new(),
6372 w8_ad: Vec::new(),
6373 w8_in: 0,
6374 w8o_aq: Vec::new(),
6375 w8o_ad: Vec::new(),
6376 w8o_in: 0,
6377 w8t_aq: Vec::new(),
6378 w8t_ad: Vec::new(),
6379 w8t_in: 0,
6380 w8t_oaq: Vec::new(),
6381 w8t_oad: Vec::new(),
6382 w8t_oin: 0,
6383 w8t_cap: 0,
6384 fa2_q: Vec::new(),
6385 fa2_gate: Vec::new(),
6386 fa2_gated: Vec::new(),
6387 fa2_cap: 0,
6388 rope_k_t: Vec::new(),
6389 rope_ctr_t: Vec::new(),
6390 rope_pos_t: Vec::new(),
6391 rows_tabs: Vec::new(),
6392 rows_tab_t: Vec::new(),
6393 rows_tab_shadow: Vec::new(),
6394 tcol_gated: Vec::new(),
6395 tcol_opart: Vec::new(),
6396 tcol_opeer: None,
6397 tcol_omix: None,
6398 tcol_ocap: 0,
6399 q_raw,
6400 k_raw,
6401 v_raw,
6402 q,
6403 k,
6404 pos,
6405 fuse_ctr,
6406 gate,
6407 attn_out,
6408 gated,
6409 o_partials,
6410 raw_o_partials,
6411 raw_k,
6412 raw_v_raw,
6413 ev_rank,
6414 peer_partial,
6415 reduce_a,
6416 reduce_b,
6417 zeros,
6418 k_shadow,
6419 v_shadow,
6420 ev_refresh,
6421 ev_oproj,
6422 gate_e,
6423 attn_in: Vec::new(),
6424 h_stage: None,
6425 pos_stage: None,
6426 raw_h_stage: 0,
6427 raw_pos_stage: 0,
6428 raw_attn_in,
6429 raw_pos,
6430 raw_o_partial1: 0,
6431 raw_peer_partial,
6432 raw_k1: 0,
6433 raw_v1: 0,
6434 raw_k_shadow,
6435 raw_v_shadow,
6436 raw_mixed_stage_e: 0,
6437 raw_reduce_a: 0,
6438 raw_shadow_stage_e: (0, 0),
6439 ev_entry,
6440 e_device: e.ctx().ordinal(),
6441 local_q_dim,
6442 local_kv_dim,
6443 heads,
6444 o_out,
6445 o_block_cols,
6446 blocks_per_rank,
6447 });
6448 eprintln!(
6449 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
6450 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
6451 residency=persistent ordering=evented performance_claim=false"
6452 );
6453 Ok(guard.len() - 1)
6454 }
6455
6456 #[allow(clippy::too_many_arguments)]
6464 #[allow(clippy::too_many_arguments)]
6469 pub fn decode_v2_input_qkv_tcol(
6470 &self,
6471 ws_index: usize,
6472 e: &Engine,
6473 h_t: &CudaSlice<f32>,
6474 t: usize,
6475 q_m: &ResidentBf16ColumnParallel,
6476 k_m: &ResidentBf16ColumnParallel,
6477 v_m: &ResidentBf16ColumnParallel,
6478 gate_shards: Option<StepTpGateShards<'_>>,
6479 ) -> Result<(), Box<dyn std::error::Error>> {
6480 let ranks = self.ranks.len();
6481 let mut guard = self
6482 .decode_v2
6483 .lock()
6484 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6485 let ws = guard
6486 .get_mut(ws_index)
6487 .ok_or("step TP decode v2 workspace index out of range")?;
6488 let in_f = q_m.in_features;
6489 if h_t.len() < t * in_f || t == 0 || t > 32 {
6490 return Err("decode_v2_input_qkv_tcol geometry".into());
6491 }
6492 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6494 ws.tcol_q.clear();
6495 ws.tcol_k.clear();
6496 ws.tcol_v.clear();
6497 ws.tcol_g.clear();
6498 ws.tcol_in.clear();
6499 for engine in &self.ranks {
6500 let _m = engine.gpu.enter_main()?;
6501 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
6502 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
6503 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
6504 ws.tcol_g
6505 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
6506 ws.tcol_in.push(engine.uninit(32 * in_f)?);
6507 }
6508 ws.tcol_cap = 32;
6509 }
6510 use cudarc::driver::DevicePtr;
6512 let raw_src = {
6513 let _main = e.gpu.enter_main()?;
6514 let stream = e.stream();
6515 let (p, _g) = h_t.device_ptr(&stream);
6516 ws.ev_entry.record(&stream)?;
6517 p
6518 };
6519 for rank in 0..ranks {
6520 let engine = &self.ranks[rank];
6521 let _main = engine.gpu.enter_main()?;
6522 engine.stream().wait(&ws.ev_entry)?;
6523 let raw_dst = {
6524 let stream = engine.stream();
6525 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
6526 p
6527 };
6528 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
6529 let out_g = match &gate_shards {
6530 Some(_) => ws.heads / ranks,
6531 None => 0,
6532 };
6533 match (
6534 &q_m.ranks[rank].weight,
6535 &k_m.ranks[rank].weight,
6536 &v_m.ranks[rank].weight,
6537 ) {
6538 (
6539 ResidentBf16Weight::Bf16(wq),
6540 ResidentBf16Weight::Bf16(wk),
6541 ResidentBf16Weight::Bf16(wv),
6542 ) => {
6543 let wg = match &gate_shards {
6544 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
6545 Some(StepTpGateShards::F32(_)) => {
6546 return Err(
6547 "tcol verify: gate shard class does not match bf16 QKV".into()
6548 );
6549 }
6550 None => wq,
6551 };
6552 let StepTpDecodeV2Ws {
6553 tcol_q,
6554 tcol_k,
6555 tcol_v,
6556 tcol_g,
6557 tcol_in,
6558 local_q_dim,
6559 local_kv_dim,
6560 w8t_aq,
6561 w8t_ad,
6562 w8t_in,
6563 w8t_cap,
6564 ..
6565 } = &mut *ws;
6566 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6569 let refk = *REFK
6570 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
6571 if refk {
6572 let lq = *local_q_dim;
6573 let lkv = *local_kv_dim;
6574 let mut hrow = engine.uninit(in_f)?;
6575 let mut qr = engine.uninit(lq)?;
6576 let mut kr = engine.uninit(lkv)?;
6577 let mut vr = engine.uninit(lkv)?;
6578 let mut gr = engine.uninit(out_g.max(1))?;
6579 for c in 0..t {
6580 {
6581 let mut dst = hrow.slice_mut(0..in_f);
6582 engine.stream().memcpy_dtod(
6583 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
6584 &mut dst,
6585 )?;
6586 }
6587 engine.matvec_bf16_qkvg_into(
6588 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
6589 lq, lkv, out_g,
6590 )?;
6591 let stream = engine.stream();
6592 {
6593 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
6594 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
6595 }
6596 {
6597 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
6598 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
6599 }
6600 {
6601 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
6602 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
6603 }
6604 if out_g > 0 {
6605 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
6606 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
6607 }
6608 }
6609 } else if crate::step_tp_w8_on()
6610 && q_m.ranks[rank].q8.is_some()
6611 && k_m.ranks[rank].q8.is_some()
6612 && v_m.ranks[rank].q8.is_some()
6613 && in_f.is_multiple_of(32)
6614 {
6615 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
6621 w8t_aq.clear();
6622 w8t_ad.clear();
6623 for e_rank in &self.ranks {
6624 let _m = e_rank.gpu.enter_main()?;
6625 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6626 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6627 }
6628 *w8t_in = in_f;
6629 *w8t_cap = 32;
6630 }
6631 engine.quantize_q8_1_into(
6632 &tcol_in[rank],
6633 t,
6634 in_f,
6635 &mut w8t_aq[rank],
6636 &mut w8t_ad[rank],
6637 )?;
6638 engine.qmatvec_q8_0_qkv_rp_t_into(
6639 q_m.ranks[rank].q8.as_ref().unwrap(),
6640 k_m.ranks[rank].q8.as_ref().unwrap(),
6641 v_m.ranks[rank].q8.as_ref().unwrap(),
6642 &w8t_aq[rank],
6643 &w8t_ad[rank],
6644 &mut tcol_q[rank],
6645 &mut tcol_k[rank],
6646 &mut tcol_v[rank],
6647 in_f,
6648 *local_q_dim,
6649 *local_kv_dim,
6650 t,
6651 )?;
6652 if out_g > 0 {
6653 engine.matvec_bf16_rows_into(
6654 wg,
6655 &tcol_in[rank],
6656 &mut tcol_g[rank],
6657 in_f,
6658 out_g,
6659 t,
6660 )?;
6661 }
6662 } else {
6663 engine.matvec_bf16_qkvg_tcol_into(
6664 wq,
6665 wk,
6666 wv,
6667 wg,
6668 &tcol_in[rank],
6669 &mut tcol_q[rank],
6670 &mut tcol_k[rank],
6671 &mut tcol_v[rank],
6672 &mut tcol_g[rank],
6673 in_f,
6674 *local_q_dim,
6675 *local_kv_dim,
6676 out_g,
6677 t,
6678 )?;
6679 }
6680 }
6681 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
6682 }
6683 }
6684 Ok(())
6685 }
6686
6687 pub(crate) fn decode_v2_oproj_tcol_eligible(
6691 &self,
6692 ws: &StepTpDecodeV2Ws,
6693 o_m: &ResidentStepBf16RowParallel,
6694 ) -> bool {
6695 self.ranks.len() == 2
6696 && ws.blocks_per_rank == 4
6697 && step_tp_qkv_fused_enabled().unwrap_or(false)
6698 && no_local_shadow_on()
6699 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
6700 && o_m
6701 .ranks
6702 .iter()
6703 .flatten()
6704 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
6705 }
6706
6707 pub(crate) fn decode_v2_stash_fa2(
6712 &self,
6713 ws: &mut StepTpDecodeV2Ws,
6714 e: &Engine,
6715 col: usize,
6716 ) -> Result<(), Box<dyn std::error::Error>> {
6717 let ranks = self.ranks.len();
6718 if col >= 32 {
6719 return Err("decode_v2_stash_fa2 column out of range".into());
6720 }
6721 let lq = ws.local_q_dim;
6722 let lg = (ws.heads / ranks).max(1);
6723 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6724 ws.fa2_q.clear();
6725 ws.fa2_gate.clear();
6726 ws.fa2_gated.clear();
6727 ws.rope_k_t.clear();
6728 ws.rope_ctr_t.clear();
6729 ws.rope_pos_t.clear();
6730 ws.rows_tab_t.clear();
6731 for engine in &self.ranks {
6732 let _m = engine.gpu.enter_main()?;
6733 ws.fa2_q.push(engine.uninit(32 * lq)?);
6734 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6735 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6736 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
6737 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6738 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6739 ws.rows_tab_t
6740 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6741 }
6742 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6743 ws.fa2_cap = 32;
6744 }
6745 for rank in 0..ranks {
6746 let engine = &self.ranks[rank];
6747 let _main = engine.gpu.enter_main()?;
6748 {
6749 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
6750 engine
6751 .stream()
6752 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
6753 }
6754 {
6755 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
6756 engine
6757 .stream()
6758 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
6759 }
6760 ws.ev_rank[rank].record(&engine.stream())?;
6761 }
6762 {
6763 let _main = e.gpu.enter_main()?;
6764 for ev in ws.ev_rank.iter() {
6765 e.stream().wait(ev)?;
6766 }
6767 }
6768 Ok(())
6769 }
6770
6771 #[allow(clippy::too_many_arguments)]
6778 #[allow(dead_code)] pub(crate) fn decode_v2_spec_fa2_join(
6780 &self,
6781 ws_index: usize,
6782 e: &Engine,
6783 o_m: &ResidentStepBf16RowParallel,
6784 kv: &ResidentTpKvCache,
6785 head_dim: usize,
6786 window: usize,
6787 bucket_max: usize,
6788 scale: f32,
6789 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6790 let ranks = self.ranks.len();
6791 static ONCE: std::sync::Once = std::sync::Once::new();
6793 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
6794 {
6795 let mut guard = self
6796 .decode_v2
6797 .lock()
6798 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6799 let ws = guard
6800 .get_mut(ws_index)
6801 .ok_or("step TP decode v2 workspace index out of range")?;
6802 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6803 return Err("spec fa2 join without stashed columns".into());
6804 }
6805 let lq = ws.local_q_dim;
6806 let local_heads = (ws.heads / ranks).max(1);
6807 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6808 let capacity = kv.physical_capacity();
6809 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6810 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6812 ws.tcol_gated.clear();
6813 ws.tcol_opart.clear();
6814 for engine in &self.ranks {
6815 let _m = engine.gpu.enter_main()?;
6816 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6817 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6818 }
6819 let root = &self.ranks[0];
6820 let _m = root.gpu.enter_main()?;
6821 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6822 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6823 ws.tcol_ocap = 32;
6824 }
6825 for rank in 0..ranks {
6826 let engine = &self.ranks[rank];
6827 let _main = engine.gpu.enter_main()?;
6828 let rank_cache = kv
6829 .rank(rank)
6830 .ok_or("spec fa2 join lost its KV cache rank")?;
6831 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6832 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6833 {
6834 let StepTpDecodeV2Ws {
6835 fa2_q,
6836 fa2_gate,
6837 fa2_gated,
6838 ..
6839 } = &mut *ws;
6840 engine.fa_decode_dcw2(
6841 &fa2_q[rank],
6842 &k_ring,
6843 &v_ring,
6844 &mut fa2_gated[rank],
6845 head_dim,
6846 local_heads,
6847 local_kv_heads,
6848 rank_cache.len_d(),
6849 rank_cache.base_d(),
6850 window,
6851 bucket_max,
6852 scale,
6853 k_tok_bytes,
6854 v_tok_bytes,
6855 &fa2_gate[rank],
6856 )?;
6857 }
6858 let StepTpDecodeV2Ws {
6861 fa2_gated,
6862 tcol_gated,
6863 ..
6864 } = &mut *ws;
6865 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6866 engine
6867 .stream()
6868 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6869 }
6870 }
6871 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6872 }
6873
6874 #[allow(clippy::too_many_arguments)]
6884 pub(crate) fn decode_v2_rope_fa_rows(
6885 &self,
6886 ws_index: usize,
6887 e: &Engine,
6888 o_m: &ResidentStepBf16RowParallel,
6889 session_parts: &[Vec<[u64; 4]>],
6890 tab_keys: &[u64],
6891 positions: &[i32],
6892 stage_pos: bool,
6893 same_session: bool,
6894 q_norms: &[CudaSlice<f32>],
6895 k_norms: &[CudaSlice<f32>],
6896 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6897 t: usize,
6898 head_dim: usize,
6899 n_rot: usize,
6900 window: usize,
6901 max_ns: usize,
6902 scale: f32,
6903 k_tok_bytes: usize,
6904 v_tok_bytes: usize,
6905 eps: f32,
6906 rope_base: f32,
6907 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6908 use cudarc::driver::DevicePtr;
6909 let ranks = self.ranks.len();
6910 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6911 return Err("rope fa rows geometry".into());
6912 }
6913 {
6914 let mut guard = self
6915 .decode_v2
6916 .lock()
6917 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6918 let ws = guard
6919 .get_mut(ws_index)
6920 .ok_or("step TP decode v2 workspace index out of range")?;
6921 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6922 return Err("rope fa rows without tcol slabs".into());
6923 }
6924 let lq = ws.local_q_dim;
6925 let lkv = ws.local_kv_dim;
6926 let lg = (ws.heads / ranks).max(1);
6927 let local_heads = (ws.heads / ranks).max(1);
6928 let local_kv_heads = (lkv / head_dim).max(1);
6929 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6931 ws.fa2_q.clear();
6932 ws.fa2_gate.clear();
6933 ws.fa2_gated.clear();
6934 ws.rope_k_t.clear();
6935 ws.rope_ctr_t.clear();
6936 ws.rope_pos_t.clear();
6937 ws.rows_tab_t.clear();
6938 for engine in &self.ranks {
6939 let _m = engine.gpu.enter_main()?;
6940 ws.fa2_q.push(engine.uninit(32 * lq)?);
6941 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6942 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6943 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6944 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6945 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6946 ws.rows_tab_t
6947 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6948 }
6949 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6950 ws.fa2_cap = 32;
6951 }
6952 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6953 ws.tcol_gated.clear();
6954 ws.tcol_opart.clear();
6955 for engine in &self.ranks {
6956 let _m = engine.gpu.enter_main()?;
6957 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6958 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6959 }
6960 let root = &self.ranks[0];
6961 let _m = root.gpu.enter_main()?;
6962 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6963 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6964 ws.tcol_ocap = 32;
6965 }
6966 for rank in 0..ranks {
6967 let engine = &self.ranks[rank];
6968 let _main = engine.gpu.enter_main()?;
6969 if stage_pos {
6970 let host: Vec<i32> = positions[..t].to_vec();
6971 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6972 engine.stream().memcpy_htod(&host, &mut view)?;
6973 }
6974 let ctr_base = {
6993 let s = engine.stream();
6994 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6995 p
6996 };
6997 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6998 if rows_tab_stale_scan() {
7004 if ws.rows_tab_shadow.len() != ranks {
7005 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
7006 }
7007 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
7008 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank])
7009 && prev != &host
7010 {
7011 let words = ["k", "v", "len", "base", "ctr", "back"];
7012 let moved: Vec<String> = (0..host.len())
7013 .filter(|&i| prev.get(i) != Some(&host[i]))
7014 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
7015 .collect();
7016 let stale =
7017 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
7018 eprintln!(
7019 "[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",
7020 tab_keys[rank],
7021 moved.join(",")
7022 );
7023 }
7024 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
7025 }
7026 let legacy_memo = !rows_tab_restage_on();
7027 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
7028 let tab = engine.stream().clone_htod(&host)?;
7029 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
7030 }
7031 if !legacy_memo {
7032 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
7033 engine.stream().memcpy_htod(&host, &mut view)?;
7034 }
7035 let StepTpDecodeV2Ws {
7036 tcol_q,
7037 tcol_k,
7038 tcol_v,
7039 tcol_g,
7040 fa2_q,
7041 fa2_gated,
7042 rope_k_t,
7043 rope_pos_t,
7044 rows_tabs,
7045 rows_tab_t,
7046 ..
7047 } = &mut *ws;
7048 let tab = if legacy_memo {
7049 rows_tabs[rank]
7050 .get(&tab_keys[rank])
7051 .ok_or("rows tab memo lost its entry")?
7052 } else {
7053 &rows_tab_t[rank]
7054 };
7055 engine.qk_norm_rope_append_inc_dcw_rows(
7056 &tcol_q[rank],
7057 &tcol_k[rank],
7058 &tcol_v[rank],
7059 &q_norms[rank],
7060 &k_norms[rank],
7061 &mut fa2_q[rank],
7062 &mut rope_k_t[rank],
7063 tab,
7064 &rope_pos_t[rank],
7065 same_session,
7066 t,
7067 lkv,
7068 lkv,
7069 k_tok_bytes,
7070 v_tok_bytes,
7071 head_dim,
7072 n_rot,
7073 local_heads,
7074 local_kv_heads,
7075 eps,
7076 rope_base,
7077 1.0,
7078 rope_freqs[rank],
7079 )?;
7080 engine.fa_decode_dcw_rows(
7081 &fa2_q[rank],
7082 tab,
7083 &mut fa2_gated[rank],
7084 t,
7085 head_dim,
7086 local_heads,
7087 local_kv_heads,
7088 window,
7089 max_ns,
7090 scale,
7091 k_tok_bytes,
7092 v_tok_bytes,
7093 &tcol_g[rank],
7094 )?;
7095 let StepTpDecodeV2Ws {
7096 fa2_gated,
7097 tcol_gated,
7098 ..
7099 } = &mut *ws;
7100 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
7101 engine
7102 .stream()
7103 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
7104 }
7105 }
7106 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
7107 }
7108
7109 #[allow(clippy::too_many_arguments)]
7116 pub(crate) fn decode_v2_fa_rows_join(
7117 &self,
7118 ws_index: usize,
7119 e: &Engine,
7120 o_m: &ResidentStepBf16RowParallel,
7121 tabs: &[&crate::CudaSlice<u64>],
7122 t: usize,
7123 head_dim: usize,
7124 window: usize,
7125 max_ns: usize,
7126 scale: f32,
7127 k_tok_bytes: usize,
7128 v_tok_bytes: usize,
7129 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
7130 let ranks = self.ranks.len();
7131 if tabs.len() != ranks {
7132 return Err("fa rows join needs one table per rank".into());
7133 }
7134 {
7135 let mut guard = self
7136 .decode_v2
7137 .lock()
7138 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
7139 let ws = guard
7140 .get_mut(ws_index)
7141 .ok_or("step TP decode v2 workspace index out of range")?;
7142 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
7143 return Err("fa rows join without stashed rows".into());
7144 }
7145 let lq = ws.local_q_dim;
7146 let local_heads = (ws.heads / ranks).max(1);
7147 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
7148 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
7149 ws.tcol_gated.clear();
7150 ws.tcol_opart.clear();
7151 for engine in &self.ranks {
7152 let _m = engine.gpu.enter_main()?;
7153 ws.tcol_gated.push(engine.uninit(32 * lq)?);
7154 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
7155 }
7156 let root = &self.ranks[0];
7157 let _m = root.gpu.enter_main()?;
7158 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
7159 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
7160 ws.tcol_ocap = 32;
7161 }
7162 for rank in 0..ranks {
7163 let engine = &self.ranks[rank];
7164 let _main = engine.gpu.enter_main()?;
7165 {
7166 let StepTpDecodeV2Ws {
7167 fa2_q,
7168 fa2_gate,
7169 fa2_gated,
7170 ..
7171 } = &mut *ws;
7172 engine.fa_decode_dcw_rows(
7173 &fa2_q[rank],
7174 tabs[rank],
7175 &mut fa2_gated[rank],
7176 t,
7177 head_dim,
7178 local_heads,
7179 local_kv_heads,
7180 window,
7181 max_ns,
7182 scale,
7183 k_tok_bytes,
7184 v_tok_bytes,
7185 &fa2_gate[rank],
7186 )?;
7187 }
7188 let StepTpDecodeV2Ws {
7189 fa2_gated,
7190 tcol_gated,
7191 ..
7192 } = &mut *ws;
7193 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
7194 engine
7195 .stream()
7196 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
7197 }
7198 }
7199 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
7200 }
7201
7202 pub(crate) fn decode_v2_stash_gated(
7207 &self,
7208 ws: &mut StepTpDecodeV2Ws,
7209 e: &Engine,
7210 col: usize,
7211 ) -> Result<(), Box<dyn std::error::Error>> {
7212 let ranks = self.ranks.len();
7213 if col >= 32 {
7217 return Err("decode_v2_stash_gated column out of range".into());
7218 }
7219 let lq = ws.local_q_dim;
7220 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
7221 ws.tcol_gated.clear();
7222 ws.tcol_opart.clear();
7223 for engine in &self.ranks {
7224 let _m = engine.gpu.enter_main()?;
7225 ws.tcol_gated.push(engine.uninit(32 * lq)?);
7226 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
7227 }
7228 let root = &self.ranks[0];
7229 let _m = root.gpu.enter_main()?;
7230 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
7231 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
7232 ws.tcol_ocap = 32;
7233 }
7234 for rank in 0..ranks {
7235 let engine = &self.ranks[rank];
7236 let _main = engine.gpu.enter_main()?;
7237 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
7238 engine
7239 .stream()
7240 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
7241 ws.ev_rank[rank].record(&engine.stream())?;
7245 }
7246 {
7247 let _main = e.gpu.enter_main()?;
7248 for ev in ws.ev_rank.iter() {
7249 e.stream().wait(ev)?;
7250 }
7251 }
7252 Ok(())
7253 }
7254
7255 pub(crate) fn decode_v2_oproj_tcol(
7261 &self,
7262 ws_index: usize,
7263 e: &Engine,
7264 o_m: &ResidentStepBf16RowParallel,
7265 t: usize,
7266 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7267 let ranks = self.ranks.len();
7268 let mut guard = self
7269 .decode_v2
7270 .lock()
7271 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
7272 let ws = guard
7273 .get_mut(ws_index)
7274 .ok_or("step TP decode v2 workspace index out of range")?;
7275 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
7276 return Err("decode_v2_oproj_tcol geometry".into());
7277 }
7278 for rank in 0..ranks {
7279 let engine = &self.ranks[rank];
7280 let _main = engine.gpu.enter_main()?;
7281 let mut weights = Vec::with_capacity(4);
7282 for block in 0..4 {
7283 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7284 return Err("tcol o_proj requires bf16-resident O blocks".into());
7285 };
7286 weights.push(weight);
7287 }
7288 {
7289 let StepTpDecodeV2Ws {
7290 tcol_gated,
7291 tcol_opart,
7292 local_q_dim,
7293 o_block_cols,
7294 o_out,
7295 w8t_oaq,
7296 w8t_oad,
7297 w8t_oin,
7298 w8t_cap,
7299 ..
7300 } = &mut *ws;
7301 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7304 let refk = *REFK
7305 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
7306 if refk {
7307 let lq = *local_q_dim;
7308 let mut xr = engine.uninit(lq)?;
7309 let mut yr = engine.uninit(*o_out)?;
7310 for c in 0..t {
7311 {
7312 let mut dst = xr.slice_mut(0..lq);
7313 engine.stream().memcpy_dtod(
7314 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
7315 &mut dst,
7316 )?;
7317 }
7318 engine.matvec_bf16_b4_into(
7319 [weights[0], weights[1], weights[2], weights[3]],
7320 &xr,
7321 &mut yr,
7322 *o_block_cols,
7323 *o_out,
7324 )?;
7325 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
7326 engine
7327 .stream()
7328 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
7329 }
7330 } else if crate::step_tp_w8_on()
7331 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
7332 && (4 * *o_block_cols) % 32 == 0
7333 {
7334 let in_f = 4 * *o_block_cols;
7338 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
7339 w8t_oaq.clear();
7340 w8t_oad.clear();
7341 for e_rank in &self.ranks {
7342 let _m = e_rank.gpu.enter_main()?;
7343 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
7344 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
7345 }
7346 *w8t_oin = in_f;
7347 *w8t_cap = (*w8t_cap).max(32);
7348 }
7349 engine.quantize_q8_1_into(
7350 &tcol_gated[rank],
7351 t,
7352 in_f,
7353 &mut w8t_oaq[rank],
7354 &mut w8t_oad[rank],
7355 )?;
7356 engine.qmatvec_q8_0_b4_rp_t_into(
7357 [
7358 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7359 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7360 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7361 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7362 ],
7363 &w8t_oaq[rank],
7364 &w8t_oad[rank],
7365 &mut tcol_opart[rank],
7366 *o_block_cols,
7367 *o_out,
7368 t,
7369 )?;
7370 } else {
7371 engine.matvec_bf16_b4_tcol_into(
7372 [weights[0], weights[1], weights[2], weights[3]],
7373 &tcol_gated[rank],
7374 &mut tcol_opart[rank],
7375 *o_block_cols,
7376 *o_out,
7377 t,
7378 )?;
7379 }
7380 }
7381 if rank != 0 {
7382 ws.ev_rank[rank].record(&engine.stream())?;
7383 }
7384 }
7385 let root = &self.ranks[0];
7386 {
7387 let _main = root.gpu.enter_main()?;
7388 for ev in ws.ev_rank.iter().skip(1) {
7389 root.stream().wait(ev)?;
7390 }
7391 {
7392 let StepTpDecodeV2Ws {
7393 tcol_opart,
7394 tcol_opeer,
7395 tcol_omix,
7396 o_out,
7397 ..
7398 } = &mut *ws;
7399 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
7400 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
7401 {
7402 let mut dst = opeer.slice_mut(0..t * *o_out);
7403 root.stream()
7404 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
7405 }
7406 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
7409 }
7410 ws.ev_oproj.record(&root.stream())?;
7411 }
7412 let _main = e.gpu.enter_main()?;
7413 e.stream().wait(&ws.ev_oproj)?;
7414 let mut out = e.uninit(t * ws.o_out)?;
7415 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
7416 e.stream().memcpy_dtod(
7417 &omix.slice(0..t * ws.o_out),
7418 &mut out.slice_mut(0..t * ws.o_out),
7419 )?;
7420 Ok(out)
7421 }
7422
7423 #[allow(clippy::too_many_arguments)] pub(crate) fn decode_v2_input_qkv(
7425 &self,
7426 ws: &mut StepTpDecodeV2Ws,
7427 e: &Engine,
7428 h: &CudaSlice<f32>,
7429 pos_d: &CudaSlice<i32>,
7430 gate_raw: Option<&CudaSlice<f32>>,
7431 gate_shards: Option<StepTpGateShards<'_>>,
7432 decode_input: &mut ResidentReplicatedDeviceRows,
7433 q_m: &ResidentBf16ColumnParallel,
7434 k_m: &ResidentBf16ColumnParallel,
7435 v_m: &ResidentBf16ColumnParallel,
7436 q_norm: &[CudaSlice<f32>],
7437 k_norm: &[CudaSlice<f32>],
7438 head_dim: usize,
7439 n_rot: usize,
7440 rope_base: f32,
7441 rope_freqs: &[Option<&CudaSlice<f32>>],
7442 rms_eps: f32,
7443 has_gate: bool,
7444 defer_norm_rope: bool,
7445 tcol_col: Option<usize>,
7446 ) -> Result<(), Box<dyn std::error::Error>> {
7447 let ranks = self.ranks.len();
7448 validate_replicated_device_rows(&self.ranks, decode_input)?;
7449 let gate_sources = usize::from(gate_raw.is_some()) + usize::from(gate_shards.is_some());
7450 if decode_input.tokens != 1
7451 || decode_input.width != q_m.in_features
7452 || pos_d.len() != 1
7453 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
7454 || (has_gate && gate_sources != 1)
7455 || (!has_gate && gate_sources != 0)
7456 || gate_shards.as_ref().is_some_and(|shards| match shards {
7457 StepTpGateShards::F32(shards) => shards.len() != ranks,
7458 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
7459 })
7460 || q_norm.len() != ranks
7461 || k_norm.len() != ranks
7462 || rope_freqs.len() != ranks
7463 || e.ctx().ordinal() != ws.e_device
7464 {
7465 return Err("step TP decode v2 input geometry mismatch".into());
7466 }
7467
7468 let qkv_fused = step_tp_qkv_fused_enabled()?;
7469 if gate_shards.is_some() && !qkv_fused {
7470 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
7471 }
7472 let values = decode_input.width;
7473 if h.len() != values {
7474 return Err(format!(
7475 "step TP decode v2 hidden width {} != replicated width {values}",
7476 h.len()
7477 )
7478 .into());
7479 }
7480
7481 if qkv_fused {
7482 if ws.h_stage.is_none() {
7486 use cudarc::driver::DevicePtr;
7487 let _main = e.gpu.enter_main()?;
7488 let h_stage = e.uninit(values)?;
7489 let pos_stage = e.htod_i32(&[0])?;
7490 {
7491 let stream = e.stream();
7492 let (hp, _g0) = h_stage.device_ptr(&stream);
7493 let (pp, _g1) = pos_stage.device_ptr(&stream);
7494 ws.raw_h_stage = hp;
7495 ws.raw_pos_stage = pp;
7496 }
7497 ws.h_stage = Some(h_stage);
7498 ws.pos_stage = Some(pos_stage);
7499 for rank in 0..ranks {
7500 use cudarc::driver::DevicePtr;
7501 let engine = &self.ranks[rank];
7502 let _rmain = engine.gpu.enter_main()?;
7503 let attn_in = engine.uninit(values)?;
7504 let (dp, pp) = {
7505 let stream = engine.stream();
7506 let (dp, _g2) = attn_in.device_ptr(&stream);
7507 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
7508 (dp, pp)
7509 };
7510 ws.raw_attn_in.push(dp);
7511 ws.raw_pos.push(pp);
7512 ws.attn_in.push(attn_in);
7513 }
7514 {
7515 use cudarc::driver::DevicePtr;
7516 let root = &self.ranks[0];
7517 let _rmain = root.gpu.enter_main()?;
7518 let stream = root.stream();
7519 let (a, _g) = ws.peer_partial.device_ptr(&stream);
7520 let (b, _g) = ws.k_shadow.device_ptr(&stream);
7521 let (c, _g) = ws.v_shadow.device_ptr(&stream);
7522 ws.raw_peer_partial = a;
7523 ws.raw_k_shadow = b;
7524 ws.raw_v_shadow = c;
7525 }
7526 {
7527 use cudarc::driver::DevicePtr;
7528 let rank1 = &self.ranks[1];
7529 let _rmain = rank1.gpu.enter_main()?;
7530 let stream = rank1.stream();
7531 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
7532 let (b, _g) = ws.k[1].device_ptr(&stream);
7533 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
7534 ws.raw_o_partial1 = a;
7535 ws.raw_k1 = b;
7536 ws.raw_v1 = c;
7537 }
7538 }
7539 {
7540 let _main = e.gpu.enter_main()?;
7541 {
7542 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
7545 let mut dst = h_stage.slice_mut(0..values);
7546 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
7547 }
7548 {
7549 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
7550 let mut dst = pos_stage.slice_mut(0..1);
7551 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
7552 }
7553 ws.ev_entry.record(&e.stream())?;
7554 }
7555 for rank in 0..ranks {
7556 let engine = &self.ranks[rank];
7557 let _main = engine.gpu.enter_main()?;
7558 engine.stream().wait(&ws.ev_entry)?;
7559 }
7560 } else {
7561 {
7563 let _main = e.gpu.enter_main()?;
7564 if let Some(gate_raw) = gate_raw {
7565 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
7566 e.stream()
7567 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
7568 }
7569 ws.ev_entry.record(&e.stream())?;
7570 }
7571 {
7572 let root = &self.ranks[0];
7573 let _main = root.gpu.enter_main()?;
7574 root.stream().wait(&ws.ev_entry)?;
7575 let mut destination = decode_input.ranks[0].slice_mut(0..values);
7576 root.stream()
7577 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
7578 ws.ev_refresh.record(&root.stream())?;
7579 }
7580 for rank in 1..ranks {
7581 let engine = &self.ranks[rank];
7582 let _main = engine.gpu.enter_main()?;
7583 engine.stream().wait(&ws.ev_refresh)?;
7584 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
7585 let mut destination = peer_rows[0].slice_mut(0..values);
7586 engine
7587 .stream()
7588 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
7589 }
7590 }
7591 for rank in 0..ranks {
7592 self.decode_v2_input_qkv_rank(
7593 ws,
7594 pos_d,
7595 decode_input,
7596 q_m,
7597 k_m,
7598 v_m,
7599 q_norm,
7600 k_norm,
7601 head_dim,
7602 n_rot,
7603 rope_base,
7604 rope_freqs,
7605 rms_eps,
7606 gate_shards.as_ref(),
7607 has_gate,
7608 qkv_fused,
7609 defer_norm_rope,
7610 rank,
7611 tcol_col,
7612 )?;
7613 }
7614 Ok(())
7615 }
7616
7617 #[allow(clippy::too_many_arguments)]
7620 pub(crate) fn decode_v2_input_qkv_rank(
7621 &self,
7622 ws: &mut StepTpDecodeV2Ws,
7623 pos_d: &CudaSlice<i32>,
7624 decode_input: &mut ResidentReplicatedDeviceRows,
7625 q_m: &ResidentBf16ColumnParallel,
7626 k_m: &ResidentBf16ColumnParallel,
7627 v_m: &ResidentBf16ColumnParallel,
7628 q_norm: &[CudaSlice<f32>],
7629 k_norm: &[CudaSlice<f32>],
7630 head_dim: usize,
7631 n_rot: usize,
7632 rope_base: f32,
7633 rope_freqs: &[Option<&CudaSlice<f32>>],
7634 rms_eps: f32,
7635 gate_shards: Option<&StepTpGateShards<'_>>,
7636 has_gate: bool,
7637 qkv_fused: bool,
7638 defer_norm_rope: bool,
7639 rank: usize,
7640 tcol_col: Option<usize>,
7641 ) -> Result<(), Box<dyn std::error::Error>> {
7642 let ranks = self.ranks.len();
7643 let local_heads = ws.local_q_dim / head_dim;
7644 let local_kv_heads = ws.local_kv_dim / head_dim;
7645 let engine = &self.ranks[rank];
7646 let _main = engine.gpu.enter_main()?;
7647 let ws_e_device = ws.e_device;
7648 if qkv_fused && tcol_col.is_some() {
7653 #[allow(clippy::unnecessary_unwrap)]
7654 let c = tcol_col.expect("checked");
7656 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
7657 return Err("tcol select without precompute".into());
7658 }
7659 if engine.ctx().ordinal() != ws_e_device {
7663 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7664 }
7665 let StepTpDecodeV2Ws {
7666 tcol_q,
7667 tcol_k,
7668 tcol_v,
7669 tcol_g,
7670 q_raw,
7671 k_raw,
7672 v_raw,
7673 gate,
7674 local_q_dim,
7675 local_kv_dim,
7676 heads,
7677 ..
7678 } = &mut *ws;
7679 let lg = *heads / ranks;
7680 let stream = engine.stream();
7681 {
7682 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
7683 stream.memcpy_dtod(
7684 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
7685 &mut dst,
7686 )?;
7687 }
7688 {
7689 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
7690 stream.memcpy_dtod(
7691 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7692 &mut dst,
7693 )?;
7694 }
7695 {
7696 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
7697 stream.memcpy_dtod(
7698 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7699 &mut dst,
7700 )?;
7701 }
7702 if has_gate && lg > 0 {
7703 let mut dst = gate[rank].slice_mut(0..lg);
7704 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
7705 }
7706 if !defer_norm_rope {
7707 } else {
7711 return Ok(());
7712 }
7713 }
7714 if qkv_fused {
7715 let same_dev = engine.ctx().ordinal() == ws.e_device;
7720 if !same_dev {
7721 raw_copy_bytes(
7722 ws.raw_attn_in[rank],
7723 ws.raw_h_stage,
7724 q_m.in_features * 4,
7725 engine,
7726 )?;
7727 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7728 }
7729 let StepTpDecodeV2Ws {
7730 q_raw,
7731 k_raw,
7732 v_raw,
7733 gate,
7734 gate_e,
7735 attn_in,
7736 h_stage,
7737 heads,
7738 local_q_dim,
7739 local_kv_dim,
7740 w8_aq,
7741 w8_ad,
7742 w8_in,
7743 ..
7744 } = &mut *ws;
7745 let input_ref: &CudaSlice<f32> = if same_dev {
7746 h_stage
7747 .as_ref()
7748 .ok_or("step TP decode v2 stage not armed")?
7749 } else {
7750 &attn_in[rank]
7751 };
7752 match (
7753 &q_m.ranks[rank].weight,
7754 &k_m.ranks[rank].weight,
7755 &v_m.ranks[rank].weight,
7756 ) {
7757 (
7758 ResidentBf16Weight::F32(wq),
7759 ResidentBf16Weight::F32(wk),
7760 ResidentBf16Weight::F32(wv),
7761 ) => {
7762 let (wg, out_g) = match &gate_shards {
7763 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
7764 Some(StepTpGateShards::Bf16(_)) => {
7765 return Err("step TP decode v2 gate shard class does not \
7766 match the F32 projections"
7767 .into());
7768 }
7769 None => (&*gate_e, 0),
7771 };
7772 engine.matvec_f32_qkv_into(
7773 wq,
7774 wk,
7775 wv,
7776 wg,
7777 input_ref,
7778 &mut q_raw[rank],
7779 &mut k_raw[rank],
7780 &mut v_raw[rank],
7781 &mut gate[rank],
7782 q_m.in_features,
7783 *local_q_dim,
7784 *local_kv_dim,
7785 out_g,
7786 )?;
7787 }
7788 (
7789 ResidentBf16Weight::Bf16(wq),
7790 ResidentBf16Weight::Bf16(wk),
7791 ResidentBf16Weight::Bf16(wv),
7792 ) => {
7793 let (wg, out_g) = match &gate_shards {
7794 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
7795 Some(StepTpGateShards::F32(_)) => {
7796 return Err("step TP decode v2 gate shard class does not \
7797 match the bf16 projections"
7798 .into());
7799 }
7800 None => (wq, 0),
7801 };
7802 let in_f = q_m.in_features;
7809 let q8_ready = crate::step_tp_w8_on()
7810 && q_m.ranks[rank].q8.is_some()
7811 && k_m.ranks[rank].q8.is_some()
7812 && v_m.ranks[rank].q8.is_some();
7813 if q8_ready {
7814 if *w8_in != in_f || w8_aq.len() != ranks {
7815 w8_aq.clear();
7816 w8_ad.clear();
7817 for e_rank in &self.ranks {
7818 let _m = e_rank.gpu.enter_main()?;
7819 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7820 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7821 }
7822 *w8_in = in_f;
7823 }
7824 engine.quantize_q8_1_into(
7825 input_ref,
7826 1,
7827 in_f,
7828 &mut w8_aq[rank],
7829 &mut w8_ad[rank],
7830 )?;
7831 engine.qmatvec_q8_0_qkv_rp_into(
7836 q_m.ranks[rank].q8.as_ref().unwrap(),
7837 k_m.ranks[rank].q8.as_ref().unwrap(),
7838 v_m.ranks[rank].q8.as_ref().unwrap(),
7839 &w8_aq[rank],
7840 &w8_ad[rank],
7841 &mut q_raw[rank],
7842 &mut k_raw[rank],
7843 &mut v_raw[rank],
7844 in_f,
7845 *local_q_dim,
7846 *local_kv_dim,
7847 )?;
7848 if out_g > 0 {
7849 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7850 }
7851 } else {
7852 engine.matvec_bf16_qkvg_into(
7853 wq,
7854 wk,
7855 wv,
7856 wg,
7857 input_ref,
7858 &mut q_raw[rank],
7859 &mut k_raw[rank],
7860 &mut v_raw[rank],
7861 &mut gate[rank],
7862 q_m.in_features,
7863 *local_q_dim,
7864 *local_kv_dim,
7865 out_g,
7866 )?;
7867 }
7868 }
7869 _ => {
7870 return Err("step TP decode v2 QKV projections mix residency classes".into());
7871 }
7872 }
7873 } else {
7874 for (matrix, local_out, raw) in [
7875 (q_m, ws.local_q_dim, &mut ws.q_raw),
7876 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7877 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7878 ] {
7879 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7880 return Err("step TP decode v2 lost its F32 projection residency".into());
7881 };
7882 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7883 engine.linear_f32_resident_canonical_rows_t1_into(
7884 &decode_input.ranks[rank],
7885 values_w,
7886 &mut raw[rank],
7887 matrix.in_features,
7888 local_out,
7889 chunk_rows,
7890 )?;
7891 }
7892 }
7893 if qkv_fused && defer_norm_rope {
7894 } else if qkv_fused {
7896 let StepTpDecodeV2Ws {
7899 q_raw,
7900 k_raw,
7901 q,
7902 k,
7903 pos,
7904 pos_stage,
7905 ..
7906 } = &mut *ws;
7907 let same_dev = engine.ctx().ordinal() == ws_e_device;
7908 let pos_ref: &CudaSlice<i32> = if same_dev {
7909 pos_stage
7910 .as_ref()
7911 .ok_or("step TP decode v2 pos stage not armed")?
7912 } else {
7913 &pos[rank]
7914 };
7915 engine.qk_norm_rope_into(
7916 &q_raw[rank],
7917 &k_raw[rank],
7918 &q_norm[rank],
7919 &k_norm[rank],
7920 &mut q[rank],
7921 &mut k[rank],
7922 pos_ref,
7923 head_dim,
7924 n_rot,
7925 local_heads,
7926 local_kv_heads,
7927 rms_eps,
7928 rope_base,
7929 1.0,
7930 rope_freqs[rank],
7931 )?;
7932 } else {
7933 engine.rms_norm(
7934 &ws.q_raw[rank],
7935 &q_norm[rank],
7936 &mut ws.q[rank],
7937 head_dim,
7938 local_heads,
7939 rms_eps,
7940 )?;
7941 engine.rms_norm(
7942 &ws.k_raw[rank],
7943 &k_norm[rank],
7944 &mut ws.k[rank],
7945 head_dim,
7946 local_kv_heads,
7947 rms_eps,
7948 )?;
7949 {
7950 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7951 engine
7952 .stream()
7953 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7954 }
7955 engine.rope_neox2(
7956 &mut ws.q[rank],
7957 &mut ws.k[rank],
7958 &ws.pos[rank],
7959 head_dim,
7960 n_rot,
7961 local_heads,
7962 local_kv_heads,
7963 1,
7964 rope_base,
7965 1.0,
7966 rope_freqs[rank],
7967 )?;
7968 }
7969 if has_gate && gate_shards.is_none() {
7970 let gate_start = rank * (ws.heads / ranks);
7971 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7972 engine.stream().memcpy_dtod(
7973 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7974 &mut gate_dst,
7975 )?;
7976 }
7977 Ok(())
7978 }
7979
7980 pub(crate) fn decode_v2_finish_rank_partial(
7984 &self,
7985 ws: &mut StepTpDecodeV2Ws,
7986 o_m: &ResidentStepBf16RowParallel,
7987 o_fused: bool,
7988 rank: usize,
7989 ) -> Result<(), Box<dyn std::error::Error>> {
7990 let engine = &self.ranks[rank];
7991 let _main = engine.gpu.enter_main()?;
7992 if o_fused {
7993 let StepTpDecodeV2Ws {
7994 gated,
7995 o_partials,
7996 o_block_cols,
7997 o_out,
7998 w8o_aq,
7999 w8o_ad,
8000 w8o_in,
8001 ..
8002 } = &mut *ws;
8003 let all_f32 = o_m.ranks[rank]
8004 .iter()
8005 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
8006 if all_f32 {
8007 let mut weights = Vec::with_capacity(4);
8008 for block in 0..4 {
8009 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
8010 unreachable!("all_f32 checked above");
8011 };
8012 weights.push(weight);
8013 }
8014 engine.matvec_f32_b4_into(
8015 [weights[0], weights[1], weights[2], weights[3]],
8016 &gated[rank],
8017 &mut o_partials[rank][0],
8018 *o_block_cols,
8019 *o_out,
8020 )?;
8021 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
8022 let in_f = 4 * *o_block_cols;
8027 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
8028 w8o_aq.clear();
8029 w8o_ad.clear();
8030 for e_rank in &self.ranks {
8031 let _m = e_rank.gpu.enter_main()?;
8032 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
8033 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
8034 }
8035 *w8o_in = in_f;
8036 }
8037 engine.quantize_q8_1_into(
8038 &gated[rank],
8039 1,
8040 in_f,
8041 &mut w8o_aq[rank],
8042 &mut w8o_ad[rank],
8043 )?;
8044 engine.qmatvec_q8_0_b4_rp_into(
8045 [
8046 o_m.ranks[rank][0].q8.as_ref().unwrap(),
8047 o_m.ranks[rank][1].q8.as_ref().unwrap(),
8048 o_m.ranks[rank][2].q8.as_ref().unwrap(),
8049 o_m.ranks[rank][3].q8.as_ref().unwrap(),
8050 ],
8051 &w8o_aq[rank],
8052 &w8o_ad[rank],
8053 &mut o_partials[rank][0],
8054 *o_block_cols,
8055 *o_out,
8056 )?;
8057 } else {
8058 let mut weights = Vec::with_capacity(4);
8059 for block in 0..4 {
8060 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
8061 return Err("step TP decode v2 O projections mix residency classes".into());
8062 };
8063 weights.push(weight);
8064 }
8065 engine.matvec_bf16_b4_into(
8066 [weights[0], weights[1], weights[2], weights[3]],
8067 &gated[rank],
8068 &mut o_partials[rank][0],
8069 *o_block_cols,
8070 *o_out,
8071 )?;
8072 }
8073 } else {
8074 for block in 0..ws.blocks_per_rank {
8075 let x =
8076 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
8077 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
8078 match &o_m.ranks[rank][block].weight {
8079 ResidentBf16Weight::F32(weight) => {
8080 let w = weight.slice(0..weight.len());
8081 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
8082 }
8083 ResidentBf16Weight::Bf16(weight) => {
8084 engine.matvec_bf16_views_into(
8085 weight,
8086 &x,
8087 &mut y,
8088 ws.o_block_cols,
8089 ws.o_out,
8090 )?;
8091 }
8092 }
8093 }
8094 }
8095 Ok(())
8096 }
8097
8098 pub(crate) fn decode_v2_finish(
8106 &self,
8107 ws: &mut StepTpDecodeV2Ws,
8108 e: &Engine,
8109 o_m: &ResidentStepBf16RowParallel,
8110 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8111 let ranks = self.ranks.len();
8112 if e.ctx().ordinal() != ws.e_device {
8113 return Err("step TP decode v2 finish engine changed".into());
8114 }
8115 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
8120
8121 for rank in 0..ranks {
8124 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
8125 if rank == 0 {
8126 continue;
8129 }
8130 let engine = &self.ranks[rank];
8131 let _main = engine.gpu.enter_main()?;
8132 ws.ev_rank[rank].record(&engine.stream())?;
8133 }
8134
8135 let root = &self.ranks[0];
8137 #[allow(unused_assignments)]
8138 let mut final_in_a = false;
8139 {
8140 let _main = root.gpu.enter_main()?;
8141 for ev in ws.ev_rank.iter().skip(1) {
8142 root.stream().wait(ev)?;
8143 }
8144 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
8145 ws.ev_oproj.record(&root.stream())?;
8151 let _main = e.gpu.enter_main()?;
8152 e.stream().wait(&ws.ev_oproj)?;
8153 let mut output = e.uninit(ws.o_out)?;
8154 if oproj_tail_on() && oproj_tail_eligible() {
8155 use cudarc::driver::DevicePtr;
8158 let stream = e.stream();
8159 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
8160 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
8161 set_oproj_tail((p0, p1));
8162 return Ok(output);
8163 }
8164 e.add(
8165 &ws.o_partials[0][0],
8166 &ws.o_partials[1][0],
8167 &mut output,
8168 ws.o_out,
8169 )?;
8170 return Ok(output);
8171 }
8172 if o_fused {
8173 self.decode_v2_finish_root_fused(ws)?;
8174 ws.ev_oproj.record(&root.stream())?;
8175 let _main = e.gpu.enter_main()?;
8176 e.stream().wait(&ws.ev_oproj)?;
8177 let mut output = e.uninit(ws.o_out)?;
8178 e.stream().memcpy_dtod(
8179 &ws.reduce_a.slice(0..ws.o_out),
8180 &mut output.slice_mut(0..ws.o_out),
8181 )?;
8182 return Ok(output);
8183 }
8184 let mut first = true;
8185 let mut current_is_a = false;
8186 for rank in 0..ranks {
8187 for block in 0..ws.blocks_per_rank {
8188 let use_peer = rank != 0;
8189 if use_peer {
8190 raw_copy_bytes(
8191 ws.raw_peer_partial,
8192 ws.raw_o_partials[rank][block],
8193 ws.o_out * std::mem::size_of::<f32>(),
8194 root,
8195 )?;
8196 }
8197 match (first, current_is_a, use_peer) {
8199 (true, _, true) => {
8200 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
8201 }
8202 (true, _, false) => root.add(
8203 &ws.zeros,
8204 &ws.o_partials[0][block],
8205 &mut ws.reduce_a,
8206 ws.o_out,
8207 )?,
8208 (false, true, true) => {
8209 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
8210 }
8211 (false, true, false) => root.add(
8212 &ws.reduce_a,
8213 &ws.o_partials[0][block],
8214 &mut ws.reduce_b,
8215 ws.o_out,
8216 )?,
8217 (false, false, true) => {
8218 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
8219 }
8220 (false, false, false) => root.add(
8221 &ws.reduce_b,
8222 &ws.o_partials[0][block],
8223 &mut ws.reduce_a,
8224 ws.o_out,
8225 )?,
8226 }
8227 current_is_a = first || !current_is_a;
8228 first = false;
8229 }
8230 }
8231 final_in_a = current_is_a;
8232
8233 if !no_local_shadow_on() {
8234 let bytes = ws.local_kv_dim * std::mem::size_of::<f32>();
8235 for rank in 0..ranks {
8236 let offset = rank * bytes;
8237 raw_copy_bytes(ws.raw_k_shadow + offset as u64, ws.raw_k[rank], bytes, root)?;
8238 raw_copy_bytes(
8239 ws.raw_v_shadow + offset as u64,
8240 ws.raw_v_raw[rank],
8241 bytes,
8242 root,
8243 )?;
8244 }
8245 }
8246 ws.ev_oproj.record(&root.stream())?;
8247 }
8248
8249 let _main = e.gpu.enter_main()?;
8253 e.stream().wait(&ws.ev_oproj)?;
8254 let mut output = e.uninit(ws.o_out)?;
8255 let source = if final_in_a {
8256 &ws.reduce_a
8257 } else {
8258 &ws.reduce_b
8259 };
8260 e.stream().memcpy_dtod(
8261 &source.slice(0..ws.o_out),
8262 &mut output.slice_mut(0..ws.o_out),
8263 )?;
8264 Ok(output)
8265 }
8266
8267 #[allow(clippy::too_many_arguments)] pub fn run_routed_experts(
8269 &self,
8270 experts: &ResidentExpertParallel,
8271 input: &[f32],
8272 tokens: usize,
8273 selected: &[usize],
8274 route_weights: &[f32],
8275 experts_per_token: usize,
8276 activation_limit: Option<f32>,
8277 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8278 validate_step_expert_activation_limit(activation_limit)?;
8279 validate_ep_residency(&self.ranks, experts)?;
8280 validate_activations(input, tokens, experts.input_width)?;
8281 let pairs = tokens
8282 .checked_mul(experts_per_token)
8283 .ok_or("EP route count overflow")?;
8284 if selected.len() != pairs || route_weights.len() != pairs {
8285 return Err(format!(
8286 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
8287 {experts_per_token} ({pairs})",
8288 selected.len(),
8289 route_weights.len(),
8290 )
8291 .into());
8292 }
8293 if !route_weights.iter().all(|weight| weight.is_finite()) {
8294 return Err("EP route weights contain a non-finite value".into());
8295 }
8296 if self.native_p2p {
8297 return self.run_routed_experts_native(
8298 experts,
8299 input,
8300 tokens,
8301 selected,
8302 route_weights,
8303 experts_per_token,
8304 activation_limit,
8305 );
8306 }
8307
8308 let mut output = vec![0.0f32; tokens * experts.input_width];
8309 let per_rank = experts.expert_count / experts.ranks.len();
8310 for token in 0..tokens {
8311 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8312 for slot in 0..experts_per_token {
8313 let pair = token * experts_per_token + slot;
8314 let expert = selected[pair];
8315 if expert >= experts.expert_count {
8316 return Err(format!(
8317 "EP selected expert {expert} outside 0..{}",
8318 experts.expert_count
8319 )
8320 .into());
8321 }
8322 let owner = expert / per_rank;
8323 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8324 let rank = &experts.ranks[owner];
8325 let engine = &self.ranks[owner];
8326 let gate =
8327 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
8328 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
8329 let activated: Vec<f32> = gate
8330 .iter()
8331 .zip(&up)
8332 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8333 .collect();
8334 debug_assert_eq!(activated.len(), experts.expert_width);
8335 let down =
8336 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
8337 let weight = route_weights[pair];
8338 for (sum, value) in output
8339 [token * experts.input_width..(token + 1) * experts.input_width]
8340 .iter_mut()
8341 .zip(down)
8342 {
8343 *sum += weight * value;
8344 }
8345 }
8346 }
8347 Ok(output)
8348 }
8349
8350 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native(
8352 &self,
8353 experts: &ResidentExpertParallel,
8354 input: &[f32],
8355 tokens: usize,
8356 selected: &[usize],
8357 route_weights: &[f32],
8358 experts_per_token: usize,
8359 activation_limit: Option<f32>,
8360 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8361 if !self.native_p2p || self.ranks.len() < 2 {
8362 return Err("native EP execution requires at least two P2P ranks".into());
8363 }
8364 if self.ep_device_arithmetic {
8365 return self.run_routed_experts_native_device(
8366 experts,
8367 input,
8368 tokens,
8369 selected,
8370 route_weights,
8371 experts_per_token,
8372 activation_limit,
8373 );
8374 }
8375 let mut output = vec![0.0f32; tokens * experts.input_width];
8376 let per_rank = experts.expert_count / experts.ranks.len();
8377 for token in 0..tokens {
8378 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8379 let mut rank_inputs = (0..self.ranks.len())
8380 .map(|_| None)
8381 .collect::<Vec<Option<CudaSlice<f32>>>>();
8382 rank_inputs[0] = Some({
8383 let root = &self.ranks[0];
8384 let _main = root.gpu.enter_main()?;
8385 root.htod(input_row)?
8386 });
8387
8388 for slot in 0..experts_per_token {
8389 let pair = token * experts_per_token + slot;
8390 let expert = selected[pair];
8391 if expert >= experts.expert_count {
8392 return Err(format!(
8393 "EP selected expert {expert} outside 0..{}",
8394 experts.expert_count
8395 )
8396 .into());
8397 }
8398 let owner = expert / per_rank;
8399 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8400 if rank_inputs[owner].is_none() {
8401 let peer_input = {
8402 let root_input = rank_inputs[0]
8403 .as_ref()
8404 .ok_or("native EP lost its root input")?;
8405 let engine = &self.ranks[owner];
8406 let _main = engine.gpu.enter_main()?;
8407 let mut peer_input = engine.uninit(experts.input_width)?;
8408 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8409 peer_input
8410 };
8411 rank_inputs[owner] = Some(peer_input);
8412 }
8413
8414 let rank = &experts.ranks[owner];
8415 let engine = &self.ranks[owner];
8416 let owner_input = rank_inputs[owner]
8417 .as_ref()
8418 .ok_or("native EP owner input is absent after dispatch")?;
8419 let gate = run_resident_bank_expert_device(
8420 engine,
8421 &rank.gate,
8422 local_expert,
8423 owner_input,
8424 1,
8425 )?;
8426 let up = run_resident_bank_expert_device(
8427 engine,
8428 &rank.up,
8429 local_expert,
8430 owner_input,
8431 1,
8432 )?;
8433 let (gate, up) = {
8434 let _main = engine.gpu.enter_main()?;
8435 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
8436 };
8437 let activated = gate
8438 .iter()
8439 .zip(&up)
8440 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8441 .collect::<Vec<_>>();
8442 debug_assert_eq!(activated.len(), experts.expert_width);
8443 let activated = {
8444 let _main = engine.gpu.enter_main()?;
8445 engine.htod(&activated)?
8446 };
8447 let down = run_resident_bank_expert_device(
8448 engine,
8449 &rank.down,
8450 local_expert,
8451 &activated,
8452 1,
8453 )?;
8454 let down = if owner == 0 {
8455 let _main = engine.gpu.enter_main()?;
8456 engine.dtoh(&down)?
8457 } else {
8458 let root = &self.ranks[0];
8459 let _main = root.gpu.enter_main()?;
8460 let mut root_down = root.uninit(experts.input_width)?;
8461 root.stream().memcpy_dtod(&down, &mut root_down)?;
8462 root.dtoh(&root_down)?
8463 };
8464 let weight = route_weights[pair];
8465 for (sum, value) in output
8466 [token * experts.input_width..(token + 1) * experts.input_width]
8467 .iter_mut()
8468 .zip(down)
8469 {
8470 *sum += weight * value;
8471 }
8472 }
8473 }
8474 Ok(output)
8475 }
8476
8477 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native_device(
8479 &self,
8480 experts: &ResidentExpertParallel,
8481 input: &[f32],
8482 tokens: usize,
8483 selected: &[usize],
8484 route_weights: &[f32],
8485 experts_per_token: usize,
8486 activation_limit: Option<f32>,
8487 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8488 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
8489 return Err(
8490 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
8491 );
8492 }
8493 let mut output = Vec::with_capacity(tokens * experts.input_width);
8494 let per_rank = experts.expert_count / experts.ranks.len();
8495 let root = &self.ranks[0];
8496 for token in 0..tokens {
8497 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8498 let mut rank_inputs = (0..self.ranks.len())
8499 .map(|_| None)
8500 .collect::<Vec<Option<CudaSlice<f32>>>>();
8501 rank_inputs[0] = Some({
8502 let _main = root.gpu.enter_main()?;
8503 root.htod(input_row)?
8504 });
8505 let mut root_output = {
8506 let _main = root.gpu.enter_main()?;
8507 root.zeros(experts.input_width)?
8508 };
8509 let mut remote_down_keepalive = Vec::new();
8510
8511 for slot in 0..experts_per_token {
8512 let pair = token * experts_per_token + slot;
8513 let expert = selected[pair];
8514 if expert >= experts.expert_count {
8515 return Err(format!(
8516 "EP selected expert {expert} outside 0..{}",
8517 experts.expert_count
8518 )
8519 .into());
8520 }
8521 let owner = expert / per_rank;
8522 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8523 if rank_inputs[owner].is_none() {
8524 let peer_input = {
8525 let root_input = rank_inputs[0]
8526 .as_ref()
8527 .ok_or("native EP lost its root input")?;
8528 let engine = &self.ranks[owner];
8529 let _main = engine.gpu.enter_main()?;
8530 let mut peer_input = engine.uninit(experts.input_width)?;
8531 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8532 peer_input
8533 };
8534 rank_inputs[owner] = Some(peer_input);
8535 }
8536
8537 let rank = &experts.ranks[owner];
8538 let engine = &self.ranks[owner];
8539 let owner_input = rank_inputs[owner]
8540 .as_ref()
8541 .ok_or("native EP owner input is absent after dispatch")?;
8542 let gate = run_resident_bank_expert_device(
8543 engine,
8544 &rank.gate,
8545 local_expert,
8546 owner_input,
8547 1,
8548 )?;
8549 let up = run_resident_bank_expert_device(
8550 engine,
8551 &rank.up,
8552 local_expert,
8553 owner_input,
8554 1,
8555 )?;
8556 let activated = {
8557 let _main = engine.gpu.enter_main()?;
8558 let mut activated = engine.uninit(experts.expert_width)?;
8559 if let Some(limit) = activation_limit {
8560 engine.silu_clamped_mul_host_expf(
8561 &gate,
8562 &up,
8563 limit,
8564 &mut activated,
8565 experts.expert_width,
8566 )?;
8567 } else {
8568 engine.silu_mul_host_expf(
8569 &gate,
8570 &up,
8571 &mut activated,
8572 experts.expert_width,
8573 )?;
8574 }
8575 activated
8576 };
8577 let down = run_resident_bank_expert_device(
8578 engine,
8579 &rank.down,
8580 local_expert,
8581 &activated,
8582 1,
8583 )?;
8584 let root_down = if owner == 0 {
8585 down
8586 } else {
8587 let _main = root.gpu.enter_main()?;
8588 let mut root_down = root.uninit(experts.input_width)?;
8589 root.stream().memcpy_dtod(&down, &mut root_down)?;
8590 remote_down_keepalive.push(down);
8594 root_down
8595 };
8596 let _main = root.gpu.enter_main()?;
8597 let mut destination = root_output.slice_mut(0..experts.input_width);
8598 root.axpy_host_into(
8599 &root_down.slice(0..root_down.len()),
8600 route_weights[pair],
8601 &mut destination,
8602 experts.input_width,
8603 )?;
8604 }
8605
8606 let _main = root.gpu.enter_main()?;
8607 let root_output = root.dtoh(&root_output)?;
8608 drop(remote_down_keepalive);
8609 output.extend(root_output);
8610 }
8611 Ok(output)
8612 }
8613}
8614
8615#[allow(clippy::manual_is_multiple_of)] fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8617 if matrix.out_features % tp != 0 {
8618 return Err(format!(
8619 "column-parallel out_features {} is not divisible by TP={tp}",
8620 matrix.out_features
8621 ));
8622 }
8623 let local_out = matrix.out_features / tp;
8624 if !local_out.is_multiple_of(FP8_BLOCK) {
8625 return Err(format!(
8626 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
8627 E4M3 scale block"
8628 ));
8629 }
8630 Ok(())
8631}
8632
8633#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
8635 if !matches!(tp, 1 | 2 | 4 | 8) {
8636 return Err(format!(
8637 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8638 ));
8639 }
8640 if out_features == 0 || !out_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8641 return Err(format!(
8642 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
8643 ));
8644 }
8645 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
8646 let local_out = out_features / tp;
8647 if local_out % canonical_rows != 0 {
8648 return Err(format!(
8649 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
8650 {canonical_rows}-row chunks"
8651 ));
8652 }
8653 Ok(canonical_rows)
8654}
8655
8656#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
8658 if !matches!(tp, 1 | 2 | 4 | 8) {
8659 return Err(format!(
8660 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8661 ));
8662 }
8663 if in_features == 0 || !in_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8664 return Err(format!(
8665 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
8666 ));
8667 }
8668 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
8669 let local_in = in_features / tp;
8670 if local_in % canonical_cols != 0 {
8671 return Err(format!(
8672 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
8673 {canonical_cols}-column chunks"
8674 ));
8675 }
8676 Ok(canonical_cols)
8677}
8678
8679#[allow(clippy::manual_is_multiple_of)] fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8681 if matrix.in_features % tp != 0 {
8682 return Err(format!(
8683 "row-parallel in_features {} is not divisible by TP={tp}",
8684 matrix.in_features
8685 ));
8686 }
8687 let local_in = matrix.in_features / tp;
8688 if !local_in.is_multiple_of(FP8_BLOCK) {
8689 return Err(format!(
8690 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
8691 E4M3 scale block"
8692 ));
8693 }
8694 Ok(())
8695}
8696
8697fn upload_rank(
8698 engine: &Engine,
8699 matrix: E4m3BlockMatrix<'_>,
8700) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
8701 let _main = engine.gpu.enter_main()?;
8702 matrix.validate()?;
8703 Ok(ResidentE4m3Rank {
8704 codes: engine.htod_bytes(matrix.codes)?,
8705 scales: engine.htod(matrix.scales)?,
8706 out_features: matrix.out_features,
8707 in_features: matrix.in_features,
8708 })
8709}
8710
8711fn upload_bf16_rank(
8712 engine: &Engine,
8713 matrix: Bf16Matrix<'_>,
8714 f32_mirror: bool,
8715) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
8716 let _main = engine.gpu.enter_main()?;
8717 matrix.validate()?;
8718 let bytes = engine.htod_bytes(matrix.bytes)?;
8719 let weight = if f32_mirror {
8720 let values = matrix
8721 .out_features
8722 .checked_mul(matrix.in_features)
8723 .ok_or("resident BF16 mirror element count overflow")?;
8724 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
8725 } else {
8726 ResidentBf16Weight::Bf16(bytes)
8727 };
8728 let q8 = if crate::step_tp_w8_on() && matrix.in_features.is_multiple_of(32) {
8732 if let ResidentBf16Weight::Bf16(bytes) = &weight {
8733 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
8740 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
8741 engine.encode_q8_0_from_bf16(
8742 bytes,
8743 &mut interleaved,
8744 matrix.in_features,
8745 matrix.out_features,
8746 )?;
8747 let mirror =
8748 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
8749 Some(mirror)
8750 } else {
8751 None
8752 }
8753 } else {
8754 None
8755 };
8756 Ok(ResidentBf16Rank {
8757 weight,
8758 out_features: matrix.out_features,
8759 in_features: matrix.in_features,
8760 q8,
8761 })
8762}
8763
8764fn upload_expert_bank_rank(
8765 engine: &Engine,
8766 bank: E4m3ExpertBank<'_>,
8767 expert_range: Range<usize>,
8768) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8769 let _main = engine.gpu.enter_main()?;
8770 bank.validate()?;
8771 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
8772 return Err(format!(
8773 "invalid EP expert range {expert_range:?} for {} experts",
8774 bank.expert_count
8775 )
8776 .into());
8777 }
8778 let code_stride = bank.out_features * bank.in_features;
8779 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
8780 Ok(ResidentE4m3ExpertBankRank {
8781 codes: engine.htod_bytes(
8782 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
8783 )?,
8784 scales: engine.htod(
8785 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
8786 )?,
8787 expert_range,
8788 out_features: bank.out_features,
8789 in_features: bank.in_features,
8790 code_stride,
8791 scale_stride,
8792 k_blocks: None,
8793 })
8794}
8795
8796#[allow(clippy::manual_is_multiple_of)] fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8798 if bank.out_features % tp != 0 {
8799 return Err(format!(
8800 "TP expert output width {} is not divisible by TP={tp}",
8801 bank.out_features
8802 ));
8803 }
8804 let local_out = bank.out_features / tp;
8805 if !local_out.is_multiple_of(FP8_BLOCK) {
8806 return Err(format!(
8807 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
8808 ));
8809 }
8810 Ok(())
8811}
8812
8813#[allow(clippy::manual_is_multiple_of)] fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8815 if bank.in_features % tp != 0 {
8816 return Err(format!(
8817 "TP expert input width {} is not divisible by TP={tp}",
8818 bank.in_features
8819 ));
8820 }
8821 let local_in = bank.in_features / tp;
8822 if !local_in.is_multiple_of(FP8_BLOCK) {
8823 return Err(format!(
8824 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
8825 ));
8826 }
8827 Ok(())
8828}
8829
8830fn upload_column_bank_rank(
8831 engine: &Engine,
8832 bank: E4m3ExpertBank<'_>,
8833 tp: usize,
8834 rank: usize,
8835) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8836 let _main = engine.gpu.enter_main()?;
8837 let packed = pack_column_bank_rank(bank, tp, rank)?;
8838 Ok(ResidentE4m3ExpertBankRank {
8839 codes: engine.htod_bytes(&packed.codes)?,
8840 scales: engine.htod(&packed.scales)?,
8841 expert_range: packed.expert_range,
8842 out_features: packed.out_features,
8843 in_features: packed.in_features,
8844 code_stride: packed.code_stride,
8845 scale_stride: packed.scale_stride,
8846 k_blocks: packed.k_blocks,
8847 })
8848}
8849
8850fn pack_column_bank_rank(
8851 bank: E4m3ExpertBank<'_>,
8852 tp: usize,
8853 rank: usize,
8854) -> Result<PackedE4m3ExpertBankRank, String> {
8855 bank.validate()?;
8856 validate_column_bank_shape(bank, tp)?;
8857 if rank >= tp {
8858 return Err(format!("TP rank {rank} outside 0..{tp}"));
8859 }
8860 let local_out = bank.out_features / tp;
8861 let full_code_stride = bank.out_features * bank.in_features;
8862 let local_code_stride = local_out * bank.in_features;
8863 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8864 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8865 let local_scale_rows = local_out / FP8_BLOCK;
8866 let local_scale_stride = local_scale_rows * scale_cols;
8867 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8868 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8869 let row_start = rank * local_out;
8870 let scale_row_start = rank * local_scale_rows;
8871 for expert in 0..bank.expert_count {
8872 let code_start = expert * full_code_stride + row_start * bank.in_features;
8873 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8874 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8875 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8876 }
8877 Ok(PackedE4m3ExpertBankRank {
8878 codes,
8879 scales,
8880 expert_range: 0..bank.expert_count,
8881 out_features: local_out,
8882 in_features: bank.in_features,
8883 code_stride: local_code_stride,
8884 scale_stride: local_scale_stride,
8885 k_blocks: None,
8886 })
8887}
8888
8889fn upload_row_bank_rank(
8890 engine: &Engine,
8891 bank: E4m3ExpertBank<'_>,
8892 tp: usize,
8893 rank: usize,
8894) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8895 let _main = engine.gpu.enter_main()?;
8896 let packed = pack_row_bank_rank(bank, tp, rank)?;
8897 Ok(ResidentE4m3ExpertBankRank {
8898 codes: engine.htod_bytes(&packed.codes)?,
8899 scales: engine.htod(&packed.scales)?,
8900 expert_range: packed.expert_range,
8901 out_features: packed.out_features,
8902 in_features: packed.in_features,
8903 code_stride: packed.code_stride,
8904 scale_stride: packed.scale_stride,
8905 k_blocks: packed.k_blocks,
8906 })
8907}
8908
8909fn pack_row_bank_rank(
8910 bank: E4m3ExpertBank<'_>,
8911 tp: usize,
8912 rank: usize,
8913) -> Result<PackedE4m3ExpertBankRank, String> {
8914 bank.validate()?;
8915 validate_row_bank_shape(bank, tp)?;
8916 if rank >= tp {
8917 return Err(format!("TP rank {rank} outside 0..{tp}"));
8918 }
8919 let local_in = bank.in_features / tp;
8920 let full_code_stride = bank.out_features * bank.in_features;
8921 let local_code_stride = bank.out_features * local_in;
8922 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8923 let local_scale_cols = local_in / FP8_BLOCK;
8924 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8925 let full_scale_stride = scale_rows * full_scale_cols;
8926 let local_scale_stride = scale_rows * local_scale_cols;
8927 let global_block_start = rank * local_scale_cols;
8928 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8929 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8930 for expert in 0..bank.expert_count {
8931 let expert_code_start = expert * full_code_stride;
8932 let expert_scale_start = expert * full_scale_stride;
8933 for local_block in 0..local_scale_cols {
8934 let global_block = global_block_start + local_block;
8935 let column_start = global_block * FP8_BLOCK;
8936 for row in 0..bank.out_features {
8937 let start = expert_code_start + row * bank.in_features + column_start;
8938 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8939 }
8940 for row in 0..scale_rows {
8941 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8942 }
8943 }
8944 }
8945 Ok(PackedE4m3ExpertBankRank {
8946 codes,
8947 scales,
8948 expert_range: 0..bank.expert_count,
8949 out_features: bank.out_features,
8950 in_features: local_in,
8951 code_stride: local_code_stride,
8952 scale_stride: local_scale_stride,
8953 k_blocks: Some(local_scale_cols),
8954 })
8955}
8956
8957fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8958 if engines.len() != ranks.len() {
8959 return Err(format!(
8960 "resident TP rank count {} != runtime rank count {}",
8961 ranks.len(),
8962 engines.len()
8963 ));
8964 }
8965 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8966 let device = engine.ctx().ordinal();
8967 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8968 return Err(format!(
8969 "resident TP rank {rank} is not owned by runtime device {device}"
8970 ));
8971 }
8972 }
8973 Ok(())
8974}
8975
8976fn validate_tp_bank_residency(
8977 engines: &[Engine],
8978 experts: &ResidentTpExpertBank,
8979) -> Result<(), String> {
8980 if engines.len() != experts.gate.len()
8981 || engines.len() != experts.up.len()
8982 || engines.len() != experts.down.len()
8983 {
8984 return Err(format!(
8985 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8986 experts.gate.len(),
8987 experts.up.len(),
8988 experts.down.len(),
8989 engines.len()
8990 ));
8991 }
8992 for (rank, engine) in engines.iter().enumerate() {
8993 let device = engine.ctx().ordinal();
8994 for (projection, bank) in [
8995 ("gate", &experts.gate[rank]),
8996 ("up", &experts.up[rank]),
8997 ("down", &experts.down[rank]),
8998 ] {
8999 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
9000 return Err(format!(
9001 "resident TP rank {rank} {projection} bank is not owned by runtime device \
9002 {device}"
9003 ));
9004 }
9005 }
9006 }
9007 Ok(())
9008}
9009
9010fn validate_ep_residency(
9011 engines: &[Engine],
9012 experts: &ResidentExpertParallel,
9013) -> Result<(), String> {
9014 if engines.len() != experts.ranks.len() {
9015 return Err(format!(
9016 "resident EP rank count {} != runtime rank count {}",
9017 experts.ranks.len(),
9018 engines.len()
9019 ));
9020 }
9021 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
9022 let device = engine.ctx().ordinal();
9023 for (projection, bank) in [
9024 ("gate", &resident.gate),
9025 ("up", &resident.up),
9026 ("down", &resident.down),
9027 ] {
9028 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
9029 return Err(format!(
9030 "resident EP rank {rank} {projection} bank is not owned by runtime device \
9031 {device}"
9032 ));
9033 }
9034 }
9035 }
9036 Ok(())
9037}
9038
9039fn run_rank(
9040 engine: &Engine,
9041 matrix: E4m3BlockMatrix<'_>,
9042 activations: &[f32],
9043 tokens: usize,
9044) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9045 let _main = engine.gpu.enter_main()?;
9046 let codes = engine.htod_bytes(matrix.codes)?;
9047 let scales = engine.htod(matrix.scales)?;
9048 let activations = engine.htod(activations)?;
9049 let output = engine.qmatvec_mmq_fp8_blk(
9050 &codes,
9051 &scales,
9052 &activations,
9053 tokens,
9054 matrix.in_features,
9055 matrix.out_features,
9056 )?;
9057 engine.dtoh(&output)
9058}
9059
9060fn run_resident_rank(
9061 engine: &Engine,
9062 matrix: &ResidentE4m3Rank,
9063 activations: &[f32],
9064 tokens: usize,
9065) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9066 let _main = engine.gpu.enter_main()?;
9067 let activations = engine.htod(activations)?;
9068 let output = engine.qmatvec_mmq_fp8_blk(
9069 &matrix.codes,
9070 &matrix.scales,
9071 &activations,
9072 tokens,
9073 matrix.in_features,
9074 matrix.out_features,
9075 )?;
9076 engine.dtoh(&output)
9077}
9078
9079fn run_resident_bf16_rank(
9080 engine: &Engine,
9081 matrix: &ResidentBf16Rank,
9082 activations: &[f32],
9083 tokens: usize,
9084 canonical_chunk_rows: Option<usize>,
9085) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9086 let _main = engine.gpu.enter_main()?;
9087 let activations = engine.htod(activations)?;
9088 let output = run_resident_bf16_rank_device(
9089 engine,
9090 matrix,
9091 &activations,
9092 tokens,
9093 canonical_chunk_rows,
9094 false,
9095 )?;
9096 engine.dtoh(&output)
9097}
9098
9099fn run_resident_bf16_rank_device(
9100 engine: &Engine,
9101 matrix: &ResidentBf16Rank,
9102 activations: &CudaSlice<f32>,
9103 tokens: usize,
9104 canonical_chunk_rows: Option<usize>,
9105 strided_chunk_output: bool,
9106) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9107 let _main = engine.gpu.enter_main()?;
9108 if activations.ordinal() != engine.ctx().ordinal() {
9109 return Err(format!(
9110 "resident BF16 activation device {} != rank device {}",
9111 activations.ordinal(),
9112 engine.ctx().ordinal()
9113 )
9114 .into());
9115 }
9116 if activations.len() != tokens * matrix.in_features {
9117 return Err(format!(
9118 "resident BF16 activation count {} != {tokens}x{}",
9119 activations.len(),
9120 matrix.in_features
9121 )
9122 .into());
9123 }
9124 match (&matrix.weight, canonical_chunk_rows) {
9125 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
9126 .linear_bf16_resident_canonical_rows(
9127 activations,
9128 bytes,
9129 tokens,
9130 matrix.in_features,
9131 matrix.out_features,
9132 rows,
9133 ),
9134 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
9135 activations,
9136 bytes,
9137 tokens,
9138 matrix.in_features,
9139 matrix.out_features,
9140 ),
9141 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
9142 .linear_f32_resident_canonical_rows_strided(
9143 activations,
9144 values,
9145 tokens,
9146 matrix.in_features,
9147 matrix.out_features,
9148 rows,
9149 ),
9150 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
9151 activations,
9152 values,
9153 tokens,
9154 matrix.in_features,
9155 matrix.out_features,
9156 rows,
9157 ),
9158 (ResidentBf16Weight::F32(values), None) => engine.linear(
9159 activations,
9160 values,
9161 tokens,
9162 matrix.in_features,
9163 matrix.out_features,
9164 ),
9165 }
9166}
9167
9168fn validate_resident_bf16_ranks(
9169 engines: &[Engine],
9170 ranks: &[ResidentBf16Rank],
9171) -> Result<(), String> {
9172 if engines.len() != ranks.len() {
9173 return Err(format!(
9174 "resident BF16 TP rank count {} != runtime rank count {}",
9175 ranks.len(),
9176 engines.len(),
9177 ));
9178 }
9179 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
9180 let device = engine.ctx().ordinal();
9181 if matrix.weight.ordinal() != device {
9182 return Err(format!(
9183 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
9184 ));
9185 }
9186 }
9187 Ok(())
9188}
9189
9190fn validate_step_bf16_row_residency(
9191 engines: &[Engine],
9192 matrix: &ResidentStepBf16RowParallel,
9193) -> Result<(), String> {
9194 if engines.len() != matrix.ranks.len() {
9195 return Err(format!(
9196 "resident Step BF16 row rank count {} != runtime rank count {}",
9197 matrix.ranks.len(),
9198 engines.len(),
9199 ));
9200 }
9201 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
9202 if matrix.canonical_chunk_cols != canonical_cols {
9203 return Err(format!(
9204 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
9205 matrix.canonical_chunk_cols
9206 ));
9207 }
9208 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
9209 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
9210 if blocks.len() != blocks_per_rank {
9211 return Err(format!(
9212 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
9213 blocks.len()
9214 ));
9215 }
9216 let device = engine.ctx().ordinal();
9217 for (block, resident) in blocks.iter().enumerate() {
9218 if resident.weight.ordinal() != device
9219 || resident.in_features != canonical_cols
9220 || resident.out_features != matrix.out_features
9221 {
9222 return Err(format!(
9223 "resident Step BF16 row rank {rank} block {block} has inconsistent \
9224 device or geometry"
9225 ));
9226 }
9227 }
9228 }
9229 Ok(())
9230}
9231
9232fn validate_replicated_device_rows(
9233 engines: &[Engine],
9234 rows: &ResidentReplicatedDeviceRows,
9235) -> Result<(), String> {
9236 let rank_lengths = rows
9237 .ranks
9238 .iter()
9239 .map(|rank_rows| rank_rows.len())
9240 .collect::<Vec<_>>();
9241 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
9242 if rows
9243 .ranks
9244 .iter()
9245 .zip(engines)
9246 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
9247 {
9248 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
9249 }
9250 Ok(())
9251}
9252
9253fn replicated_device_row_values(
9254 tokens: usize,
9255 width: usize,
9256 expected_ranks: usize,
9257 rank_lengths: &[usize],
9258) -> Result<usize, String> {
9259 let values = tokens
9260 .checked_mul(width)
9261 .ok_or("replicated device row size overflow")?;
9262 if tokens == 0
9263 || width == 0
9264 || expected_ranks == 0
9265 || rank_lengths.len() != expected_ranks
9266 || rank_lengths.iter().any(|&rank_len| rank_len != values)
9267 {
9268 return Err(format!(
9269 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
9270 tokens,
9271 width,
9272 rank_lengths.len(),
9273 expected_ranks
9274 ));
9275 }
9276 Ok(values)
9277}
9278
9279fn replicated_device_row_source_values(
9280 tokens: usize,
9281 width: usize,
9282 source_len: usize,
9283 source_device: usize,
9284 root_device: usize,
9285) -> Result<usize, String> {
9286 let values = tokens
9287 .checked_mul(width)
9288 .ok_or("replicated device row size overflow")?;
9289 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
9290 return Err(format!(
9291 "replicated device row source has inconsistent geometry/device \
9292 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
9293 ));
9294 }
9295 Ok(values)
9296}
9297
9298#[allow(clippy::manual_is_multiple_of)] fn bf16_column_shard(
9300 matrix: Bf16Matrix<'_>,
9301 tp: usize,
9302 rank: usize,
9303) -> Result<Bf16Matrix<'_>, String> {
9304 matrix.validate()?;
9305 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
9306 return Err(format!(
9307 "invalid BF16 column shard out={} TP={tp} rank={rank}",
9308 matrix.out_features
9309 ));
9310 }
9311 let local_out = matrix.out_features / tp;
9312 let row_bytes = matrix.in_features * 2;
9313 let start = rank * local_out * row_bytes;
9314 Ok(Bf16Matrix {
9315 bytes: &matrix.bytes[start..start + local_out * row_bytes],
9316 out_features: local_out,
9317 in_features: matrix.in_features,
9318 })
9319}
9320
9321#[allow(clippy::manual_is_multiple_of)] fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
9323 matrix.validate()?;
9324 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
9325 return Err(format!(
9326 "invalid BF16 row shard in={} TP={tp} rank={rank}",
9327 matrix.in_features
9328 ));
9329 }
9330 let local_in = matrix.in_features / tp;
9331 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
9332 for row in 0..matrix.out_features {
9333 let start = (row * matrix.in_features + rank * local_in) * 2;
9334 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
9335 }
9336 Ok(bytes)
9337}
9338
9339fn bf16_row_block(
9340 matrix: Bf16Matrix<'_>,
9341 col_start: usize,
9342 block_cols: usize,
9343) -> Result<Vec<u8>, String> {
9344 matrix.validate()?;
9345 let col_end = col_start
9346 .checked_add(block_cols)
9347 .ok_or("BF16 row block column overflow")?;
9348 if block_cols == 0 || col_end > matrix.in_features {
9349 return Err(format!(
9350 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
9351 matrix.in_features
9352 ));
9353 }
9354 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
9355 for row in 0..matrix.out_features {
9356 let start = (row * matrix.in_features + col_start) * 2;
9357 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
9358 }
9359 Ok(bytes)
9360}
9361
9362fn run_resident_bank_expert(
9363 engine: &Engine,
9364 bank: &ResidentE4m3ExpertBankRank,
9365 local_expert: usize,
9366 activations: &[f32],
9367 tokens: usize,
9368) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9369 let _main = engine.gpu.enter_main()?;
9370 if bank.k_blocks.is_some() {
9371 return Err("block-major TP row bank requires canonical block execution".into());
9372 }
9373 let local_count = bank.expert_range.end - bank.expert_range.start;
9374 if local_expert >= local_count {
9375 return Err(format!(
9376 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
9377 bank.expert_range
9378 )
9379 .into());
9380 }
9381 validate_activations(activations, tokens, bank.in_features)?;
9382 let activations = engine.htod(activations)?;
9383 let weight = bank
9384 .codes
9385 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9386 let scales = bank
9387 .scales
9388 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9389 let input = activations.slice(0..activations.len());
9390 let output = engine.qmatvec_mmq_fp8_blk_view(
9391 &weight,
9392 &scales,
9393 &input,
9394 tokens,
9395 bank.in_features,
9396 bank.out_features,
9397 )?;
9398 engine.dtoh(&output)
9399}
9400
9401fn run_resident_bank_expert_block(
9402 engine: &Engine,
9403 bank: &ResidentE4m3ExpertBankRank,
9404 local_expert: usize,
9405 block: usize,
9406 activations: &[f32],
9407) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9408 let _main = engine.gpu.enter_main()?;
9409 let local_count = bank.expert_range.end - bank.expert_range.start;
9410 if local_expert >= local_count {
9411 return Err(format!(
9412 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9413 bank.expert_range
9414 )
9415 .into());
9416 }
9417 let blocks = bank
9418 .k_blocks
9419 .ok_or("TP row bank is not packed in native K-block order")?;
9420 if block >= blocks {
9421 return Err(format!("TP row block {block} outside 0..{blocks}").into());
9422 }
9423 validate_activations(activations, 1, FP8_BLOCK)?;
9424 let block_code_stride = bank.out_features * FP8_BLOCK;
9425 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9426 if bank.in_features != blocks * FP8_BLOCK
9427 || bank.code_stride != blocks * block_code_stride
9428 || bank.scale_stride != blocks * block_scale_stride
9429 {
9430 return Err("TP row bank block-major geometry is inconsistent".into());
9431 }
9432
9433 let expert_code_start = local_expert * bank.code_stride;
9434 let expert_scale_start = local_expert * bank.scale_stride;
9435 let weight = bank.codes.slice(
9436 expert_code_start + block * block_code_stride
9437 ..expert_code_start + (block + 1) * block_code_stride,
9438 );
9439 let scales = bank.scales.slice(
9440 expert_scale_start + block * block_scale_stride
9441 ..expert_scale_start + (block + 1) * block_scale_stride,
9442 );
9443 let activations = engine.htod(activations)?;
9444 let input = activations.slice(0..activations.len());
9445 let output = engine.qmatvec_mmq_fp8_blk_view(
9446 &weight,
9447 &scales,
9448 &input,
9449 1,
9450 FP8_BLOCK,
9451 bank.out_features,
9452 )?;
9453 engine.dtoh(&output)
9454}
9455
9456fn run_resident_bank_expert_device(
9457 engine: &Engine,
9458 bank: &ResidentE4m3ExpertBankRank,
9459 local_expert: usize,
9460 activations: &CudaSlice<f32>,
9461 tokens: usize,
9462) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9463 let _main = engine.gpu.enter_main()?;
9464 if bank.k_blocks.is_some() {
9465 return Err("block-major TP row bank requires canonical block execution".into());
9466 }
9467 let local_count = bank.expert_range.end - bank.expert_range.start;
9468 if local_expert >= local_count {
9469 return Err(format!(
9470 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9471 bank.expert_range
9472 )
9473 .into());
9474 }
9475 let expected = tokens
9476 .checked_mul(bank.in_features)
9477 .ok_or("native TP activation size overflow")?;
9478 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
9479 return Err(format!(
9480 "native TP activation len/device {}/{} != expected {expected}/{}",
9481 activations.len(),
9482 activations.ordinal(),
9483 engine.ctx().ordinal()
9484 )
9485 .into());
9486 }
9487 let weight = bank
9488 .codes
9489 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9490 let scales = bank
9491 .scales
9492 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9493 let input = activations.slice(0..activations.len());
9494 engine.qmatvec_mmq_fp8_blk_view(
9495 &weight,
9496 &scales,
9497 &input,
9498 tokens,
9499 bank.in_features,
9500 bank.out_features,
9501 )
9502}
9503
9504fn run_resident_bank_expert_block_device(
9505 engine: &Engine,
9506 bank: &ResidentE4m3ExpertBankRank,
9507 local_expert: usize,
9508 block: usize,
9509 activations: &cudarc::driver::CudaView<'_, f32>,
9510) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9511 let _main = engine.gpu.enter_main()?;
9512 let local_count = bank.expert_range.end - bank.expert_range.start;
9513 if local_expert >= local_count {
9514 return Err(format!(
9515 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9516 bank.expert_range
9517 )
9518 .into());
9519 }
9520 let blocks = bank
9521 .k_blocks
9522 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
9523 if block >= blocks {
9524 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
9525 }
9526 let activation_device = activations.stream().context().ordinal();
9527 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
9528 return Err(format!(
9529 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
9530 activations.len(),
9531 activation_device,
9532 engine.ctx().ordinal()
9533 )
9534 .into());
9535 }
9536 let block_code_stride = bank.out_features * FP8_BLOCK;
9537 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9538 if bank.in_features != blocks * FP8_BLOCK
9539 || bank.code_stride != blocks * block_code_stride
9540 || bank.scale_stride != blocks * block_scale_stride
9541 {
9542 return Err("native TP row bank block-major geometry is inconsistent".into());
9543 }
9544 let expert_code_start = local_expert * bank.code_stride;
9545 let expert_scale_start = local_expert * bank.scale_stride;
9546 let weight = bank.codes.slice(
9547 expert_code_start + block * block_code_stride
9548 ..expert_code_start + (block + 1) * block_code_stride,
9549 );
9550 let scales = bank.scales.slice(
9551 expert_scale_start + block * block_scale_stride
9552 ..expert_scale_start + (block + 1) * block_scale_stride,
9553 );
9554 engine.qmatvec_mmq_fp8_blk_view(
9555 &weight,
9556 &scales,
9557 activations,
9558 1,
9559 FP8_BLOCK,
9560 bank.out_features,
9561 )
9562}
9563
9564pub(crate) fn grant_peer_access(
9583 accessor: &Engine,
9584 owner: &Engine,
9585 label: &str,
9586) -> Result<(), Box<dyn std::error::Error>> {
9587 let (a_dev, o_dev) = (accessor.ctx().ordinal(), owner.ctx().ordinal());
9588 let mut can_access = 0;
9589 unsafe {
9590 cudarc::driver::sys::cuDeviceCanAccessPeer(
9591 &mut can_access,
9592 accessor.ctx().cu_device(),
9593 owner.ctx().cu_device(),
9594 )
9595 .result()?;
9596 }
9597 if can_access == 0 {
9598 return Err(
9599 format!("{label} requires P2P, but dev{a_dev} cannot access dev{o_dev}").into(),
9600 );
9601 }
9602 accessor.ctx().bind_to_thread()?;
9603 let rc = unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(owner.ctx().cu_ctx(), 0) };
9604 use cudarc::driver::sys::cudaError_enum as E;
9605 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
9606 return Err(format!(
9607 "{label} cuCtxEnablePeerAccess(dev{a_dev} -> dev{o_dev}) failed: {rc:?}"
9608 )
9609 .into());
9610 }
9611 let device = cudarc::driver::result::device::get(o_dev as i32)?;
9612 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
9613 unsafe {
9614 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
9615 }
9616 let desc = cudarc::driver::sys::CUmemAccessDesc {
9617 location: cudarc::driver::sys::CUmemLocation {
9618 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
9619 id: a_dev as i32,
9620 },
9621 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
9622 };
9623 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
9624 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
9625 return Err(format!(
9626 "{label} cuMemPoolSetAccess(dev{o_dev} pool -> dev{a_dev}) failed: {rc:?}"
9627 )
9628 .into());
9629 }
9630 Ok(())
9631}
9632
9633fn configure_native_p2p(
9634 ranks: &[Engine],
9635 devices: &[usize],
9636) -> Result<(), Box<dyn std::error::Error>> {
9637 if ranks.len() != devices.len() || ranks.len() < 2 {
9638 return Err("native TP P2P setup requires matching multi-rank devices".into());
9639 }
9640 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
9641 if engine.ctx().ordinal() != device {
9642 return Err(format!(
9643 "native TP rank {rank} context device {} != requested device {device}",
9644 engine.ctx().ordinal()
9645 )
9646 .into());
9647 }
9648 }
9649
9650 for src in 0..ranks.len() {
9651 for dst in 0..ranks.len() {
9652 if src == dst {
9653 continue;
9654 }
9655 grant_peer_access(&ranks[src], &ranks[dst], "native TP")?;
9656 }
9657 }
9658
9659 for src in 0..ranks.len() {
9660 for dst in 0..ranks.len() {
9661 if src == dst {
9662 continue;
9663 }
9664 for &words in NATIVE_P2P_PROBE_WORDS {
9665 let expected = (0..words)
9666 .map(|index| {
9667 (index as u32)
9668 .wrapping_mul(0x9e37_79b9)
9669 .wrapping_add(((src as u32) << 16) | dst as u32)
9670 })
9671 .collect::<Vec<_>>();
9672 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
9673 let source = ranks[src].htod_u32_v(&expected)?;
9674 let mut destination = ranks[dst].htod_u32_v(&poison)?;
9675 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
9676 let actual = ranks[dst].dtoh_u32(&destination)?;
9677 if actual != expected {
9678 let mismatches = actual
9679 .iter()
9680 .zip(&expected)
9681 .filter(|(actual, expected)| actual != expected)
9682 .count();
9683 return Err(format!(
9684 "native TP peer probe dev{}->dev{} failed at {} bytes: \
9685 {mismatches}/{} words differ",
9686 devices[src],
9687 devices[dst],
9688 words * std::mem::size_of::<u32>(),
9689 expected.len()
9690 )
9691 .into());
9692 }
9693 }
9694 }
9695 }
9696 ranks[0].ctx().bind_to_thread()?;
9697 eprintln!(
9698 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
9699 directions={} byte_ladder={:?} mismatches=0",
9700 ranks.len() * (ranks.len() - 1),
9701 NATIVE_P2P_PROBE_WORDS
9702 .iter()
9703 .map(|words| words * std::mem::size_of::<u32>())
9704 .collect::<Vec<_>>(),
9705 );
9706 Ok(())
9707}
9708
9709fn validate_activations(
9710 activations: &[f32],
9711 tokens: usize,
9712 in_features: usize,
9713) -> Result<(), String> {
9714 let expected = tokens
9715 .checked_mul(in_features)
9716 .ok_or_else(|| "activation size overflow".to_string())?;
9717 if activations.len() != expected {
9718 return Err(format!(
9719 "activation count {} != {tokens}x{in_features} ({expected})",
9720 activations.len()
9721 ));
9722 }
9723 if !activations.iter().all(|value| value.is_finite()) {
9724 return Err("activations contain a non-finite value".to_string());
9725 }
9726 Ok(())
9727}
9728
9729fn column_shard(
9730 matrix: E4m3BlockMatrix<'_>,
9731 tp: usize,
9732 rank: usize,
9733) -> Result<E4m3BlockMatrix<'_>, String> {
9734 let local_out = matrix.out_features / tp;
9735 let row_start = rank * local_out;
9736 let code_start = row_start * matrix.in_features;
9737 let code_end = code_start + local_out * matrix.in_features;
9738 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9739 let local_scale_rows = local_out / FP8_BLOCK;
9740 let scale_start = rank * local_scale_rows * scale_cols;
9741 let scale_end = scale_start + local_scale_rows * scale_cols;
9742 Ok(E4m3BlockMatrix {
9743 codes: &matrix.codes[code_start..code_end],
9744 scales: &matrix.scales[scale_start..scale_end],
9745 out_features: local_out,
9746 in_features: matrix.in_features,
9747 })
9748}
9749
9750fn row_shard(
9751 matrix: E4m3BlockMatrix<'_>,
9752 tp: usize,
9753 rank: usize,
9754) -> Result<(Vec<u8>, Vec<f32>), String> {
9755 let local_in = matrix.in_features / tp;
9756 let col_start = rank * local_in;
9757 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
9758 for row in 0..matrix.out_features {
9759 let start = row * matrix.in_features + col_start;
9760 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
9761 }
9762
9763 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
9764 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9765 let local_scale_cols = local_in / FP8_BLOCK;
9766 let scale_col_start = rank * local_scale_cols;
9767 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
9768 for row in 0..scale_rows {
9769 let start = row * scale_cols + scale_col_start;
9770 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
9771 }
9772 Ok((codes, scales))
9773}
9774
9775fn activation_shard(
9776 activations: &[f32],
9777 tokens: usize,
9778 in_features: usize,
9779 tp: usize,
9780 rank: usize,
9781) -> Vec<f32> {
9782 let local_in = in_features / tp;
9783 let col_start = rank * local_in;
9784 let mut shard = Vec::with_capacity(tokens * local_in);
9785 for token in 0..tokens {
9786 let start = token * in_features + col_start;
9787 shard.extend_from_slice(&activations[start..start + local_in]);
9788 }
9789 shard
9790}
9791
9792#[derive(Clone, Copy)]
9812pub struct Nvfp4BlockMatrix<'a> {
9813 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
9817 pub in_features: usize,
9818}
9819
9820impl Nvfp4BlockMatrix<'_> {
9821 pub fn validate(&self) -> Result<(), String> {
9822 if self.in_features == 0 || self.out_features == 0 {
9823 return Err("NVFP4 matrix has a zero dimension".to_string());
9824 }
9825 if !self.in_features.is_multiple_of(64) {
9826 return Err(format!(
9827 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
9828 self.in_features
9829 ));
9830 }
9831 if self.codes.len() != self.out_features * self.in_features / 2 {
9832 return Err(format!(
9833 "NVFP4 code bytes {} != {}x{}/2",
9834 self.codes.len(),
9835 self.out_features,
9836 self.in_features
9837 ));
9838 }
9839 if self.scales.len() != self.out_features * self.in_features / 16 {
9840 return Err(format!(
9841 "NVFP4 scale bytes {} != {}x{}/16",
9842 self.scales.len(),
9843 self.out_features,
9844 self.in_features
9845 ));
9846 }
9847 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9848 return Err(format!(
9849 "NVFP4 macro scale {} is not finite-positive",
9850 self.macro_scale
9851 ));
9852 }
9853 Ok(())
9854 }
9855}
9856
9857#[derive(Clone, Copy)]
9859pub struct Nvfp4ExpertBank<'a> {
9860 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9864 pub out_features: usize,
9865 pub in_features: usize,
9866}
9867
9868impl Nvfp4ExpertBank<'_> {
9869 pub fn validate(&self) -> Result<(), String> {
9870 if self.expert_count == 0 {
9871 return Err("NVFP4 expert bank is empty".to_string());
9872 }
9873 if self.macros.len() != self.expert_count {
9874 return Err(format!(
9875 "NVFP4 bank macros {} != expert count {}",
9876 self.macros.len(),
9877 self.expert_count
9878 ));
9879 }
9880 self.expert(0).map(|_| ())
9881 }
9882
9883 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9884 if expert >= self.expert_count {
9885 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9886 }
9887 let code_stride = self.out_features * self.in_features / 2;
9888 let scale_stride = self.out_features * self.in_features / 16;
9889 if self.codes.len() != self.expert_count * code_stride
9890 || self.scales.len() != self.expert_count * scale_stride
9891 {
9892 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9893 }
9894 let matrix = Nvfp4BlockMatrix {
9895 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9896 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9897 macro_scale: self.macros[expert],
9898 out_features: self.out_features,
9899 in_features: self.in_features,
9900 };
9901 matrix.validate()?;
9902 Ok(matrix)
9903 }
9904}
9905
9906pub struct ResidentNvfp4Rank {
9908 blocks: crate::CudaSlice<u8>,
9909 macro_scale: f32,
9910 out_features: usize,
9911 in_features: usize,
9912 row_bytes: usize,
9913}
9914
9915pub struct ResidentNvfp4ColumnParallel {
9916 ranks: Vec<ResidentNvfp4Rank>,
9917 pub out_features: usize,
9918 pub in_features: usize,
9919}
9920
9921pub struct ResidentNvfp4RowParallel {
9922 ranks: Vec<ResidentNvfp4Rank>,
9923 pub out_features: usize,
9924 pub in_features: usize,
9925}
9926
9927pub struct ResidentTpNvfp4Expert {
9928 gate: ResidentNvfp4ColumnParallel,
9929 up: ResidentNvfp4ColumnParallel,
9930 down: ResidentNvfp4RowParallel,
9931 pub input_width: usize,
9932 pub expert_width: usize,
9933}
9934
9935pub struct ResidentNvfp4ColumnBankRank {
9939 bank: crate::CudaSlice<u8>,
9943 expert_bytes: usize,
9944 local_out: usize,
9945 in_features: usize,
9946 row_bytes: usize,
9947 slot_major: bool,
9954}
9955
9956impl ResidentNvfp4ColumnBankRank {
9957 fn host_canonical_expert(
9962 &self,
9963 engine: &Engine,
9964 expert: usize,
9965 activations: &crate::CudaSlice<f32>,
9966 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9967 let w = self.expert(expert);
9968 if self.slot_major {
9969 engine.qmatvec_nvfp4_fast_v2(
9970 &w,
9971 activations,
9972 1,
9973 self.in_features,
9974 self.local_out,
9975 self.row_bytes,
9976 )
9977 } else {
9978 engine.qmatvec_nvfp4_fast(
9979 &w,
9980 activations,
9981 1,
9982 self.in_features,
9983 self.local_out,
9984 self.row_bytes,
9985 )
9986 }
9987 }
9988
9989 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9990 self.bank
9991 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9992 }
9993}
9994
9995pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
10001
10002pub struct ResidentNvfp4RowBankRank {
10003 bank: crate::CudaSlice<u8>,
10005 expert_bytes: usize,
10006 device_rank: usize, out_features: usize,
10008 local_in: usize,
10009 row_bytes: usize,
10010 slot_major: bool,
10012}
10013
10014impl ResidentNvfp4RowBankRank {
10015 fn host_canonical_expert(
10018 &self,
10019 engine: &Engine,
10020 expert: usize,
10021 activations: &crate::CudaSlice<f32>,
10022 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10023 let w = self.expert(expert);
10024 if self.slot_major {
10025 engine.qmatvec_nvfp4_fast_v2(
10026 &w,
10027 activations,
10028 1,
10029 self.local_in,
10030 self.out_features,
10031 self.row_bytes,
10032 )
10033 } else {
10034 engine.qmatvec_nvfp4_fast(
10035 &w,
10036 activations,
10037 1,
10038 self.local_in,
10039 self.out_features,
10040 self.row_bytes,
10041 )
10042 }
10043 }
10044
10045 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
10046 self.bank
10047 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
10048 }
10049}
10050
10051impl ResidentNvfp4TensorParallel {
10052 pub(crate) fn device_workspace_handle(
10053 &self,
10054 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
10055 &self.device_workspace
10056 }
10057}
10058
10059pub struct ResidentNvfp4TensorParallel {
10060 gate: Vec<ResidentNvfp4ColumnBankRank>,
10061 up: Vec<ResidentNvfp4ColumnBankRank>,
10062 down: Vec<ResidentNvfp4RowBankRank>,
10063 macros_gate: Vec<f32>,
10064 macros_up: Vec<f32>,
10065 macros_down: Vec<f32>,
10066 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
10070 macros_up_dev: Vec<crate::CudaSlice<f32>>,
10071 macros_down_dev: Vec<crate::CudaSlice<f32>>,
10072 pub expert_count: usize,
10073 pub input_width: usize,
10074 pub expert_width: usize,
10075 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
10078 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
10082 pub(crate) ep2: bool,
10086}
10087
10088struct RoutesGraph {
10095 exec: cudarc::driver::sys::CUgraphExec,
10096 parent: cudarc::driver::sys::CUgraph,
10097 _children: Vec<cudarc::driver::CudaGraph>,
10098}
10099unsafe impl Send for RoutesGraph {}
10102
10103impl Drop for RoutesGraph {
10104 fn drop(&mut self) {
10105 unsafe {
10106 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
10107 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
10108 }
10109 }
10110}
10111
10112impl Nvfp4DeviceRoutesWorkspace {
10113 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
10114 self.in_stage_e.as_ref()
10115 }
10116 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
10117 self.in_stage_e.as_mut()
10118 }
10119 #[allow(dead_code)] pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
10121 self.out_stage_e.as_mut()
10122 }
10123 pub(crate) fn arm_stages(
10125 &mut self,
10126 e: &Engine,
10127 width: usize,
10128 n_sel: usize,
10129 ) -> Result<(), Box<dyn std::error::Error>> {
10130 let _main = e.gpu.enter_main()?;
10131 if self.in_stage_e.is_none() {
10132 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
10133 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
10134 }
10135 if self.dev_route_e.is_none() {
10136 self.dev_route_e = Some((
10137 e.htod_i32(&vec![0i32; n_sel])?,
10138 e.htod(&vec![0.0f32; n_sel])?,
10139 ));
10140 }
10141 Ok(())
10142 }
10143
10144 pub(crate) fn in_and_out_stages_mut(
10146 &mut self,
10147 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
10148 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
10149 (Some(input), Some(output)) => Some((input, output)),
10150 _ => None,
10151 }
10152 }
10153 pub(crate) fn dev_route_e_mut(
10154 &mut self,
10155 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
10156 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
10157 }
10158}
10159
10160pub struct Nvfp4DeviceRoutesWorkspace {
10161 gate_out: Vec<crate::CudaSlice<f32>>,
10164 up_out: Vec<crate::CudaSlice<f32>>,
10165 act_q: Vec<crate::CudaSlice<i8>>,
10166 act_d: Vec<crate::CudaSlice<f32>>,
10167 sel: Vec<crate::CudaSlice<i32>>,
10168 partial: Vec<crate::CudaSlice<f32>>,
10169 accumulator: Vec<crate::CudaSlice<f32>>,
10170 combine_w: Vec<crate::CudaSlice<f32>>,
10172 route_w: Vec<crate::CudaSlice<f32>>,
10175 in_q: Vec<crate::CudaSlice<i8>>,
10178 in_d: Vec<crate::CudaSlice<f32>>,
10179 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
10183 prestaged: bool,
10186 rank1_routed: bool,
10189 fence_flags_raw: u64,
10193 fence_ticket: u32,
10194 ev_input: Option<(CudaEvent, usize)>,
10196 in_stage_e: Option<crate::CudaSlice<f32>>,
10199 out_stage_e: Option<crate::CudaSlice<f32>>,
10200 routes_graph: Option<RoutesGraph>,
10201 raw_dev_route_e: Option<(u64, u64)>,
10203 raw_combine: Option<(u64, u64, u64, u64)>,
10204 raw_input: Vec<u64>,
10205 raw_sel: Vec<u64>,
10206 raw_route_w: Vec<u64>,
10207 remote: crate::CudaSlice<f32>,
10208 combined: crate::CudaSlice<f32>,
10209 n_sel: usize,
10210 input: Vec<crate::CudaSlice<f32>>,
10214 ev_rank: Vec<CudaEvent>,
10215 ev_done: Option<CudaEvent>,
10216 ev_entry: Option<(CudaEvent, usize)>,
10217}
10218
10219struct ResidentNvfp4EpRank {
10221 gate: crate::CudaSlice<u8>,
10222 up: crate::CudaSlice<u8>,
10223 down: crate::CudaSlice<u8>,
10224 gate_expert_bytes: usize,
10225 down_expert_bytes: usize,
10226 macros_gate: crate::CudaSlice<f32>,
10227 macros_up: crate::CudaSlice<f32>,
10228 macros_down: crate::CudaSlice<f32>,
10229 expert_range: Range<usize>,
10230}
10231
10232struct Nvfp4EpDeviceWorkspace {
10233 input: Vec<crate::CudaSlice<f32>>,
10234 input_bf16: Vec<crate::CudaSlice<u8>>,
10235 input_q8: Vec<crate::CudaSlice<i8>>,
10236 input_q8_scales: Vec<crate::CudaSlice<f32>>,
10237 sel: Vec<crate::CudaSlice<i32>>,
10238 token_rows: Vec<crate::CudaSlice<i32>>,
10239 global_pairs: Vec<crate::CudaSlice<i32>>,
10240 route_w: Vec<crate::CudaSlice<f32>>,
10241 gate_out: Vec<crate::CudaSlice<f32>>,
10242 up_out: Vec<crate::CudaSlice<f32>>,
10243 activation_bf16: Vec<crate::CudaSlice<u8>>,
10244 activation_q8: Vec<crate::CudaSlice<i8>>,
10245 activation_q8_scales: Vec<crate::CudaSlice<f32>>,
10246 slot_rows: crate::CudaSlice<f32>,
10247 slot_rows_raw: u64,
10248 route_weights: crate::CudaSlice<f32>,
10249 graph_input: crate::CudaSlice<f32>,
10250 graph_output: crate::CudaSlice<f32>,
10251 graph_routes: Option<(u64, u64)>,
10252 graphs: Vec<Option<RoutesGraph>>,
10253 ev_entry: CudaEvent,
10254 ev_entry_device: usize,
10255 ev_rank: Vec<CudaEvent>,
10256 phase_events: Option<Nvfp4EpPhaseEvents>,
10257 capacity_tokens: usize,
10258 experts_per_token: usize,
10259}
10260
10261struct Nvfp4EpPhaseEvents {
10262 head: Vec<CudaEvent>,
10263 copy_done: Vec<CudaEvent>,
10264 gate_up_done: Vec<CudaEvent>,
10265 activation_done: Vec<CudaEvent>,
10266 down_done: Vec<CudaEvent>,
10267}
10268
10269pub(crate) const NVFP4_EP_DEVICE_BATCH_CAP: usize = 128;
10270pub(crate) const NVFP4_EP_DEVICE_ROUTER_BATCH_CAP: usize = 32;
10271pub(crate) const NVFP4_EP_Q8_BATCH_CAP: usize = 32;
10272const NVFP4_EP_GRAPH_BATCH_CAP: usize = 1;
10273
10274fn nvfp4_ep_active_input_values(
10275 input_values: usize,
10276 tokens: usize,
10277 input_width: usize,
10278) -> Result<usize, String> {
10279 if !(1..=NVFP4_EP_DEVICE_BATCH_CAP).contains(&tokens) {
10280 return Err(format!(
10281 "W4A16 NVFP4 device EP batch {tokens} is outside 1..={NVFP4_EP_DEVICE_BATCH_CAP}"
10282 ));
10283 }
10284 let active_values = tokens
10285 .checked_mul(input_width)
10286 .ok_or("W4A16 NVFP4 device EP active input size overflows usize")?;
10287 if input_values < active_values {
10288 return Err(format!(
10289 "W4A16 NVFP4 device EP input {input_values} is smaller than active \
10290 tokens {tokens} x width {input_width} ({active_values})"
10291 ));
10292 }
10293 Ok(active_values)
10294}
10295
10296pub struct ResidentNvfp4ExpertParallel {
10297 ranks: Vec<ResidentNvfp4EpRank>,
10298 macros_gate: Vec<f32>,
10299 macros_up: Vec<f32>,
10300 macros_down: Vec<f32>,
10301 pub expert_count: usize,
10302 pub input_width: usize,
10303 pub expert_width: usize,
10304 gate_row_bytes: usize,
10305 down_row_bytes: usize,
10306 device_workspace: std::sync::Mutex<Option<Nvfp4EpDeviceWorkspace>>,
10307}
10308
10309fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
10310 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
10311 matrix.codes,
10312 matrix.scales,
10313 matrix.out_features,
10314 matrix.in_features,
10315 )
10316}
10317
10318fn nvfp4_row_bytes(in_features: usize) -> usize {
10319 in_features / 64 * 36 }
10321
10322pub(crate) fn fuse_rope_append_on() -> bool {
10328 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10329 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
10330}
10331
10332pub(crate) fn no_local_shadow_on() -> bool {
10333 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10334 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
10335}
10336
10337pub fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
10353 assert_eq!(
10359 in_features % 64,
10360 0,
10361 "v2 permute needs whole 64-element superblocks, got in_features={in_features}"
10362 );
10363 let row_bytes = nvfp4_row_bytes(in_features);
10364 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
10365 let n_slots = in_features / 32;
10366 let mut out = Vec::with_capacity(v1.len());
10367 for row in 0..out_features {
10368 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
10369 for g in 0..n_slots {
10370 let (sblk, h) = (g / 2, g % 2);
10371 let b = &r[sblk * 36..sblk * 36 + 36];
10372 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
10373 }
10374 for g in 0..n_slots {
10375 let (sblk, h) = (g / 2, g % 2);
10376 let b = &r[sblk * 36..sblk * 36 + 36];
10377 out.push(b[2 * h]);
10378 out.push(b[2 * h + 1]);
10379 }
10380 }
10381 out
10382}
10383
10384fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
10388 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
10389 let v1 = nvfp4_repack_matrix(matrix);
10390 if slot_major {
10391 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
10392 } else {
10393 v1
10394 }
10395}
10396
10397#[allow(clippy::manual_is_multiple_of)] fn nvfp4_column_shard<'a>(
10401 matrix: Nvfp4BlockMatrix<'a>,
10402 tp: usize,
10403 rank: usize,
10404) -> Result<Nvfp4BlockMatrix<'a>, String> {
10405 if matrix.out_features % tp != 0 {
10406 return Err(format!(
10407 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
10408 matrix.out_features
10409 ));
10410 }
10411 let local_out = matrix.out_features / tp;
10412 let code_row = matrix.in_features / 2;
10413 let scale_row = matrix.in_features / 16;
10414 Ok(Nvfp4BlockMatrix {
10415 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
10416 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
10417 macro_scale: matrix.macro_scale,
10418 out_features: local_out,
10419 in_features: matrix.in_features,
10420 })
10421}
10422
10423#[allow(clippy::manual_is_multiple_of)] fn nvfp4_row_shard(
10427 matrix: Nvfp4BlockMatrix<'_>,
10428 tp: usize,
10429 rank: usize,
10430) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
10431 if matrix.in_features % tp != 0 {
10432 return Err(format!(
10433 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
10434 matrix.in_features
10435 ));
10436 }
10437 let local_in = matrix.in_features / tp;
10438 if !local_in.is_multiple_of(64) {
10439 return Err(format!(
10440 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
10441 ));
10442 }
10443 let code_row = matrix.in_features / 2;
10444 let scale_row = matrix.in_features / 16;
10445 let local_code = local_in / 2;
10446 let local_scale = local_in / 16;
10447 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
10448 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
10449 for row in 0..matrix.out_features {
10450 let code_start = row * code_row + rank * local_code;
10451 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
10452 let scale_start = row * scale_row + rank * local_scale;
10453 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
10454 }
10455 Ok((codes, scales, local_in))
10456}
10457
10458fn run_rank_nvfp4(
10462 engine: &Engine,
10463 matrix: Nvfp4BlockMatrix<'_>,
10464 activations: &[f32],
10465 tokens: usize,
10466) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10467 matrix.validate()?;
10468 validate_activations(activations, tokens, matrix.in_features)?;
10469 let _main = engine.gpu.enter_main()?;
10470 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
10471 let activations = engine.htod(activations)?;
10472 let output = engine.qmatvec_nvfp4_fast(
10473 &blocks.slice(0..blocks.len()),
10474 &activations,
10475 tokens,
10476 matrix.in_features,
10477 matrix.out_features,
10478 nvfp4_row_bytes(matrix.in_features),
10479 )?;
10480 engine.dtoh(&output)
10481}
10482
10483fn upload_rank_nvfp4(
10484 engine: &Engine,
10485 matrix: Nvfp4BlockMatrix<'_>,
10486) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
10487 matrix.validate()?;
10488 let _main = engine.gpu.enter_main()?;
10489 Ok(ResidentNvfp4Rank {
10490 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
10491 macro_scale: matrix.macro_scale,
10492 out_features: matrix.out_features,
10493 in_features: matrix.in_features,
10494 row_bytes: nvfp4_row_bytes(matrix.in_features),
10495 })
10496}
10497
10498fn run_resident_rank_nvfp4(
10499 engine: &Engine,
10500 rank: &ResidentNvfp4Rank,
10501 activations: &[f32],
10502 tokens: usize,
10503) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10504 validate_activations(activations, tokens, rank.in_features)?;
10505 let _main = engine.gpu.enter_main()?;
10506 let activations = engine.htod(activations)?;
10507 let output = engine.qmatvec_nvfp4_fast(
10508 &rank.blocks.slice(0..rank.blocks.len()),
10509 &activations,
10510 tokens,
10511 rank.in_features,
10512 rank.out_features,
10513 rank.row_bytes,
10514 )?;
10515 engine.dtoh(&output)
10516}
10517
10518fn apply_macro(values: &mut [f32], macro_scale: f32) {
10519 for value in values.iter_mut() {
10520 *value *= macro_scale;
10521 }
10522}
10523
10524impl TpE4m3HostBounce {
10525 pub fn full_nvfp4(
10527 &self,
10528 matrix: Nvfp4BlockMatrix<'_>,
10529 activations: &[f32],
10530 tokens: usize,
10531 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10532 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
10533 apply_macro(&mut output, matrix.macro_scale);
10534 Ok(output)
10535 }
10536
10537 pub fn column_parallel_nvfp4(
10540 &self,
10541 matrix: Nvfp4BlockMatrix<'_>,
10542 activations: &[f32],
10543 tokens: usize,
10544 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
10545 matrix.validate()?;
10546 validate_activations(activations, tokens, matrix.in_features)?;
10547 let tp = self.ranks.len();
10548 let local_out = matrix.out_features / tp;
10549 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10550 let mut rank_outputs = Vec::with_capacity(tp);
10551 for (rank_index, rank) in self.ranks.iter().enumerate() {
10552 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
10553 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
10554 let row_start = rank_index * local_out;
10555 for token in 0..tokens {
10556 gathered[token * matrix.out_features + row_start
10557 ..token * matrix.out_features + row_start + local_out]
10558 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10559 }
10560 rank_outputs.push(output);
10561 }
10562 apply_macro(&mut gathered, matrix.macro_scale);
10563 Ok(ColumnParallelResult {
10564 gathered,
10565 rank_outputs,
10566 })
10567 }
10568
10569 pub fn row_parallel_nvfp4(
10572 &self,
10573 matrix: Nvfp4BlockMatrix<'_>,
10574 activations: &[f32],
10575 tokens: usize,
10576 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
10577 matrix.validate()?;
10578 validate_activations(activations, tokens, matrix.in_features)?;
10579 let tp = self.ranks.len();
10580 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10581 let mut rank_partials = Vec::with_capacity(tp);
10582 for (rank_index, rank) in self.ranks.iter().enumerate() {
10583 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
10584 let local_activations =
10585 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10586 let shard = Nvfp4BlockMatrix {
10587 codes: &codes,
10588 scales: &scales,
10589 macro_scale: matrix.macro_scale,
10590 out_features: matrix.out_features,
10591 in_features: local_in,
10592 };
10593 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
10594 for (sum, value) in reduced.iter_mut().zip(&partial) {
10595 *sum += *value;
10596 }
10597 rank_partials.push(partial);
10598 }
10599 apply_macro(&mut reduced, matrix.macro_scale);
10600 Ok(RowParallelResult {
10601 reduced,
10602 rank_partials,
10603 })
10604 }
10605
10606 pub fn upload_expert_nvfp4(
10607 &self,
10608 gate: Nvfp4BlockMatrix<'_>,
10609 up: Nvfp4BlockMatrix<'_>,
10610 down: Nvfp4BlockMatrix<'_>,
10611 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
10612 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10613 return Err("NVFP4 TP expert gate/up dimensions differ".into());
10614 }
10615 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10616 return Err(format!(
10617 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
10618 down.out_features, down.in_features, gate.out_features, gate.in_features
10619 )
10620 .into());
10621 }
10622 let tp = self.ranks.len();
10623 let mut gate_ranks = Vec::with_capacity(tp);
10624 let mut up_ranks = Vec::with_capacity(tp);
10625 let mut down_ranks = Vec::with_capacity(tp);
10626 for (rank_index, engine) in self.ranks.iter().enumerate() {
10627 gate_ranks.push(upload_rank_nvfp4(
10628 engine,
10629 nvfp4_column_shard(gate, tp, rank_index)?,
10630 )?);
10631 up_ranks.push(upload_rank_nvfp4(
10632 engine,
10633 nvfp4_column_shard(up, tp, rank_index)?,
10634 )?);
10635 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
10636 down_ranks.push(upload_rank_nvfp4(
10637 engine,
10638 Nvfp4BlockMatrix {
10639 codes: &codes,
10640 scales: &scales,
10641 macro_scale: down.macro_scale,
10642 out_features: down.out_features,
10643 in_features: local_in,
10644 },
10645 )?);
10646 }
10647 Ok(ResidentTpNvfp4Expert {
10648 gate: ResidentNvfp4ColumnParallel {
10649 ranks: gate_ranks,
10650 out_features: gate.out_features,
10651 in_features: gate.in_features,
10652 },
10653 up: ResidentNvfp4ColumnParallel {
10654 ranks: up_ranks,
10655 out_features: up.out_features,
10656 in_features: up.in_features,
10657 },
10658 down: ResidentNvfp4RowParallel {
10659 ranks: down_ranks,
10660 out_features: down.out_features,
10661 in_features: down.in_features,
10662 },
10663 input_width: gate.in_features,
10664 expert_width: gate.out_features,
10665 })
10666 }
10667
10668 fn column_parallel_resident_nvfp4(
10669 &self,
10670 matrix: &ResidentNvfp4ColumnParallel,
10671 activations: &[f32],
10672 tokens: usize,
10673 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10674 validate_activations(activations, tokens, matrix.in_features)?;
10675 let local_out = matrix.out_features / self.ranks.len();
10676 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10677 let mut macro_scale = None;
10678 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10679 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
10680 let row_start = rank_index * local_out;
10681 for token in 0..tokens {
10682 gathered[token * matrix.out_features + row_start
10683 ..token * matrix.out_features + row_start + local_out]
10684 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10685 }
10686 macro_scale = Some(shard.macro_scale);
10687 }
10688 apply_macro(
10689 &mut gathered,
10690 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
10691 );
10692 Ok(gathered)
10693 }
10694
10695 fn row_parallel_resident_nvfp4(
10696 &self,
10697 matrix: &ResidentNvfp4RowParallel,
10698 activations: &[f32],
10699 tokens: usize,
10700 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10701 validate_activations(activations, tokens, matrix.in_features)?;
10702 let tp = self.ranks.len();
10703 let local_in = matrix.in_features / tp;
10704 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10705 let mut macro_scale = None;
10706 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10707 if shard.in_features != local_in {
10708 return Err(format!(
10709 "NVFP4 resident row shard in_features {} != expected {local_in}",
10710 shard.in_features
10711 )
10712 .into());
10713 }
10714 let local_activations =
10715 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10716 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
10717 for (sum, value) in reduced.iter_mut().zip(&partial) {
10718 *sum += *value;
10719 }
10720 macro_scale = Some(shard.macro_scale);
10721 }
10722 apply_macro(
10723 &mut reduced,
10724 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
10725 );
10726 Ok(reduced)
10727 }
10728
10729 pub fn run_expert_nvfp4(
10730 &self,
10731 expert: &ResidentTpNvfp4Expert,
10732 input: &[f32],
10733 tokens: usize,
10734 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10735 validate_activations(input, tokens, expert.input_width)?;
10736 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
10737 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
10738 let activated: Vec<f32> = gate
10739 .iter()
10740 .zip(&up)
10741 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
10742 .collect();
10743 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
10744 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
10745 }
10746
10747 #[allow(clippy::manual_is_multiple_of)] pub fn upload_tensor_parallel_nvfp4(
10750 &self,
10751 gate: Nvfp4ExpertBank<'_>,
10752 up: Nvfp4ExpertBank<'_>,
10753 down: Nvfp4ExpertBank<'_>,
10754 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
10755 gate.validate()?;
10756 up.validate()?;
10757 down.validate()?;
10758 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10759 return Err("NVFP4 TP gate/up/down expert counts differ".into());
10760 }
10761 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10762 return Err("NVFP4 TP gate/up dimensions differ".into());
10763 }
10764 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10765 return Err(format!(
10766 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
10767 down.out_features, down.in_features, gate.out_features, gate.in_features
10768 )
10769 .into());
10770 }
10771 let tp = self.ranks.len();
10772 if gate.out_features % tp != 0 {
10773 return Err(format!(
10774 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
10775 gate.out_features
10776 )
10777 .into());
10778 }
10779 if !down.in_features.is_multiple_of(NVFP4_CANONICAL_ROW_SHARDS)
10780 || !(down.in_features / NVFP4_CANONICAL_ROW_SHARDS).is_multiple_of(64)
10781 {
10782 return Err(format!(
10783 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
10784 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
10785 down.in_features
10786 )
10787 .into());
10788 }
10789 if tp > NVFP4_CANONICAL_ROW_SHARDS {
10790 return Err(format!(
10791 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
10792 ({NVFP4_CANONICAL_ROW_SHARDS})"
10793 )
10794 .into());
10795 }
10796
10797 let ep2 = step_nvfp4_ep2_on() && tp == 2;
10798 let slot_major = ep2 || bank_slot_major_on();
10803 eprintln!(
10811 "[nvfp4-bank] layout={} source={} tp={tp} experts={} in_f={} out_f={}",
10812 if slot_major {
10813 "slot-major"
10814 } else {
10815 "block-nvfp4-v1"
10816 },
10817 if ep2 {
10822 "ep2-always"
10823 } else {
10824 bank_slot_major_source().1
10825 },
10826 gate.expert_count,
10827 gate.in_features,
10828 gate.out_features
10829 );
10830 let mut gate_ranks = Vec::with_capacity(tp);
10831 let mut up_ranks = Vec::with_capacity(tp);
10832 let mut macros_gate_dev = Vec::with_capacity(tp);
10833 let mut macros_up_dev = Vec::with_capacity(tp);
10834 let mut macros_down_dev = Vec::with_capacity(tp);
10835 for (rank_index, engine) in self.ranks.iter().enumerate() {
10836 let _main = engine.gpu.enter_main()?;
10837 let mut gate_host: Vec<u8> = Vec::new();
10843 let mut up_host: Vec<u8> = Vec::new();
10844 let mut owned = 0usize;
10845 for expert in 0..gate.expert_count {
10846 if ep2 {
10847 if expert % 2 != rank_index {
10848 continue;
10849 }
10850 owned += 1;
10851 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10852 gate.expert(expert)?,
10853 slot_major,
10854 ));
10855 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10856 up.expert(expert)?,
10857 slot_major,
10858 ));
10859 } else {
10860 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
10861 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, slot_major));
10862 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
10863 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, slot_major));
10864 }
10865 }
10866 let bank_experts = if ep2 { owned } else { gate.expert_count };
10867 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
10868 let up_expert_bytes = up_host.len() / bank_experts.max(1);
10869 let local_out = if ep2 {
10870 gate.out_features
10871 } else {
10872 gate.out_features / tp
10873 };
10874 gate_ranks.push(ResidentNvfp4ColumnBankRank {
10875 bank: engine.htod_bytes(&gate_host)?,
10876 expert_bytes: gate_expert_bytes,
10877 local_out,
10878 in_features: gate.in_features,
10879 row_bytes: nvfp4_row_bytes(gate.in_features),
10880 slot_major,
10881 });
10882 up_ranks.push(ResidentNvfp4ColumnBankRank {
10883 bank: engine.htod_bytes(&up_host)?,
10884 expert_bytes: up_expert_bytes,
10885 local_out,
10886 in_features: up.in_features,
10887 row_bytes: nvfp4_row_bytes(up.in_features),
10888 slot_major,
10889 });
10890 macros_gate_dev.push(engine.htod(gate.macros)?);
10891 macros_up_dev.push(engine.htod(up.macros)?);
10892 macros_down_dev.push(engine.htod(down.macros)?);
10893 }
10894 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
10898 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
10899 let device_rank = shard_index % tp;
10900 let engine = &self.ranks[device_rank];
10901 let _main = engine.gpu.enter_main()?;
10902 let mut down_host: Vec<u8> = Vec::new();
10903 let mut owned = 0usize;
10904 for expert in 0..down.expert_count {
10905 let down_matrix = down.expert(expert)?;
10906 if ep2 {
10907 if expert % 2 != device_rank {
10910 continue;
10911 }
10912 owned += 1;
10913 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix, slot_major));
10914 } else {
10915 let (codes, scales, local_in) =
10916 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
10917 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10918 Nvfp4BlockMatrix {
10919 codes: &codes,
10920 scales: &scales,
10921 macro_scale: down_matrix.macro_scale,
10922 out_features: down_matrix.out_features,
10923 in_features: local_in,
10924 },
10925 slot_major,
10926 ));
10927 }
10928 }
10929 let bank_experts = if ep2 { owned } else { down.expert_count };
10930 let down_expert_bytes = down_host.len() / bank_experts.max(1);
10931 let local_in = if ep2 {
10932 down.in_features
10933 } else {
10934 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
10935 };
10936 down_ranks.push(ResidentNvfp4RowBankRank {
10937 bank: engine.htod_bytes(&down_host)?,
10938 expert_bytes: down_expert_bytes,
10939 device_rank,
10940 out_features: down.out_features,
10941 local_in,
10942 row_bytes: nvfp4_row_bytes(local_in),
10943 slot_major,
10944 });
10945 }
10946 Ok(ResidentNvfp4TensorParallel {
10947 gate: gate_ranks,
10948 up: up_ranks,
10949 down: down_ranks,
10950 macros_gate: gate.macros.to_vec(),
10951 macros_up: up.macros.to_vec(),
10952 macros_down: down.macros.to_vec(),
10953 macros_gate_dev,
10954 macros_up_dev,
10955 macros_down_dev,
10956 expert_count: gate.expert_count,
10957 input_width: gate.in_features,
10958 expert_width: gate.out_features,
10959 device_workspace: std::sync::Mutex::new(None),
10960 prime_tables: std::sync::Mutex::new(Vec::new()),
10961 ep2,
10962 })
10963 }
10964
10965 fn run_full_bank_expert_nvfp4(
10969 &self,
10970 ranks: &[ResidentNvfp4ColumnBankRank],
10971 macros: &[f32],
10972 expert: usize,
10973 input: &[f32],
10974 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10975 let owner = expert & 1;
10976 let slot = expert >> 1;
10977 let bank = ranks
10978 .get(owner)
10979 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
10980 let engine = &self.ranks[owner];
10981 let _main = engine.gpu.enter_main()?;
10982 let activations = engine.htod(input)?;
10983 let output = bank.host_canonical_expert(engine, slot, &activations)?;
10984 let mut out = engine.dtoh(&output)?;
10985 apply_macro(&mut out, macros[expert]);
10986 Ok(out)
10987 }
10988
10989 fn run_full_down_expert_nvfp4(
10992 &self,
10993 shards: &[ResidentNvfp4RowBankRank],
10994 macros: &[f32],
10995 expert: usize,
10996 input: &[f32],
10997 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10998 let owner = expert & 1;
10999 let slot = expert >> 1;
11000 let shard = shards
11001 .get(owner)
11002 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
11003 let engine = &self.ranks[owner];
11004 let _main = engine.gpu.enter_main()?;
11005 let activations = engine.htod(input)?;
11006 let output = shard.host_canonical_expert(engine, slot, &activations)?;
11007 let mut out = engine.dtoh(&output)?;
11008 apply_macro(&mut out, macros[expert]);
11009 Ok(out)
11010 }
11011
11012 fn run_column_bank_expert_nvfp4(
11013 &self,
11014 ranks: &[ResidentNvfp4ColumnBankRank],
11015 macros: &[f32],
11016 expert: usize,
11017 input: &[f32],
11018 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11019 let local_out = ranks
11020 .first()
11021 .ok_or("NVFP4 TP column bank has no ranks")?
11022 .local_out;
11023 let mut gathered = vec![0.0f32; local_out * ranks.len()];
11024 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
11025 let _main = engine.gpu.enter_main()?;
11026 let activations = engine.htod(input)?;
11027 let output = bank.host_canonical_expert(engine, expert, &activations)?;
11028 let output = engine.dtoh(&output)?;
11029 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
11030 }
11031 apply_macro(&mut gathered, macros[expert]);
11032 Ok(gathered)
11033 }
11034
11035 fn run_row_bank_expert_nvfp4(
11039 &self,
11040 shards: &[ResidentNvfp4RowBankRank],
11041 macros: &[f32],
11042 expert: usize,
11043 input: &[f32],
11044 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11045 let out_features = shards
11046 .first()
11047 .ok_or("NVFP4 TP row bank has no canonical shards")?
11048 .out_features;
11049 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
11050 let mut reduced = vec![0.0f32; out_features];
11051 for (shard_index, shard) in shards.iter().enumerate() {
11052 let engine = self
11053 .ranks
11054 .get(shard.device_rank)
11055 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
11056 let _main = engine.gpu.enter_main()?;
11057 let local_activations =
11058 activation_shard(input, 1, in_features, shards.len(), shard_index);
11059 let activations = engine.htod(&local_activations)?;
11060 let output = shard.host_canonical_expert(engine, expert, &activations)?;
11061 let partial = engine.dtoh(&output)?;
11062 for (sum, value) in reduced.iter_mut().zip(&partial) {
11063 *sum += *value;
11064 }
11065 }
11066 apply_macro(&mut reduced, macros[expert]);
11067 Ok(reduced)
11068 }
11069
11070 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel_nvfp4(
11075 &self,
11076 gate: Nvfp4ExpertBank<'_>,
11077 up: Nvfp4ExpertBank<'_>,
11078 down: Nvfp4ExpertBank<'_>,
11079 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
11080 gate.validate()?;
11081 up.validate()?;
11082 down.validate()?;
11083 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
11084 return Err("NVFP4 EP gate/up/down expert counts differ".into());
11085 }
11086 if gate.in_features != up.in_features || gate.out_features != up.out_features {
11087 return Err("NVFP4 EP gate/up dimensions differ".into());
11088 }
11089 if down.in_features != gate.out_features || down.out_features != gate.in_features {
11090 return Err(format!(
11091 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
11092 down.out_features, down.in_features, gate.out_features, gate.in_features
11093 )
11094 .into());
11095 }
11096 let world = self.ranks.len();
11097 if gate.expert_count % world != 0 {
11098 return Err(format!(
11099 "NVFP4 EP expert count {} is not divisible by {world} ranks",
11100 gate.expert_count
11101 )
11102 .into());
11103 }
11104 let experts_per_rank = gate.expert_count / world;
11105 let mut ranks = Vec::with_capacity(world);
11106 for (rank_index, engine) in self.ranks.iter().enumerate() {
11107 let _main = engine.gpu.enter_main()?;
11108 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
11109 let mut gate_host = Vec::new();
11110 let mut up_host = Vec::new();
11111 let mut down_host = Vec::new();
11112 for expert in expert_range.clone() {
11113 gate_host.extend_from_slice(&nvfp4_repack_matrix(gate.expert(expert)?));
11114 up_host.extend_from_slice(&nvfp4_repack_matrix(up.expert(expert)?));
11115 down_host.extend_from_slice(&nvfp4_repack_matrix(down.expert(expert)?));
11116 }
11117 let gate_expert_bytes = gate_host.len() / experts_per_rank;
11118 let up_expert_bytes = up_host.len() / experts_per_rank;
11119 if gate_expert_bytes != up_expert_bytes {
11120 return Err("NVFP4 EP gate/up packed expert bytes differ".into());
11121 }
11122 let down_expert_bytes = down_host.len() / experts_per_rank;
11123 ranks.push(ResidentNvfp4EpRank {
11124 gate: engine.htod_bytes(&gate_host)?,
11125 up: engine.htod_bytes(&up_host)?,
11126 down: engine.htod_bytes(&down_host)?,
11127 gate_expert_bytes,
11128 down_expert_bytes,
11129 macros_gate: engine.htod(&gate.macros[expert_range.clone()])?,
11130 macros_up: engine.htod(&up.macros[expert_range.clone()])?,
11131 macros_down: engine.htod(&down.macros[expert_range.clone()])?,
11132 expert_range,
11133 });
11134 }
11135 Ok(ResidentNvfp4ExpertParallel {
11136 ranks,
11137 macros_gate: gate.macros.to_vec(),
11138 macros_up: up.macros.to_vec(),
11139 macros_down: down.macros.to_vec(),
11140 expert_count: gate.expert_count,
11141 input_width: gate.in_features,
11142 expert_width: gate.out_features,
11143 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
11144 down_row_bytes: nvfp4_row_bytes(down.in_features),
11145 device_workspace: std::sync::Mutex::new(None),
11146 })
11147 }
11148
11149 pub fn upload_expert_parallel_nvfp4_normalized(
11155 &self,
11156 gate: &crate::model::HostExps,
11157 up: &crate::model::HostExps,
11158 down: &crate::model::HostExps,
11159 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
11160 for (label, bank) in [("gate", gate), ("up", up), ("down", down)] {
11161 if bank.qtype != crate::QT_NVFP4 || !bank.is_uniform_layout() {
11162 return Err(format!(
11163 "NVFP4 EP normalized {label} bank requires one uniform NVFP4 layout, \
11164 got qtype={} uniform={}",
11165 bank.qtype,
11166 bank.is_uniform_layout()
11167 )
11168 .into());
11169 }
11170 if bank.n_expert == 0
11171 || bank.expert_stride != bank.out_f * bank.row_bytes
11172 || (0..bank.n_expert)
11173 .any(|expert| bank.expert_bytes(expert).len() != bank.expert_stride)
11174 {
11175 return Err(format!("NVFP4 EP normalized {label} bank geometry is invalid").into());
11176 }
11177 }
11178 if gate.n_expert != up.n_expert || gate.n_expert != down.n_expert {
11179 return Err("NVFP4 EP normalized gate/up/down expert counts differ".into());
11180 }
11181 if gate.in_f != up.in_f || gate.out_f != up.out_f {
11182 return Err("NVFP4 EP normalized gate/up dimensions differ".into());
11183 }
11184 if down.in_f != gate.out_f || down.out_f != gate.in_f {
11185 return Err(format!(
11186 "NVFP4 EP normalized down {}x{} does not invert gate/up {}x{}",
11187 down.out_f, down.in_f, gate.out_f, gate.in_f
11188 )
11189 .into());
11190 }
11191 let macros = |bank: &crate::model::HostExps| -> Result<Vec<f32>, String> {
11192 let values = bank
11193 .macros
11194 .clone()
11195 .unwrap_or_else(|| vec![1.0; bank.n_expert]);
11196 if values.len() != bank.n_expert
11197 || !values.iter().all(|value| value.is_finite() && *value > 0.0)
11198 {
11199 return Err("NVFP4 EP normalized macro row is not finite-positive".to_string());
11200 }
11201 Ok(values)
11202 };
11203 let macros_gate = macros(gate)?;
11204 let macros_up = macros(up)?;
11205 let macros_down = macros(down)?;
11206 let world = self.ranks.len();
11207 if !gate.n_expert.is_multiple_of(world) {
11208 return Err(format!(
11209 "NVFP4 EP normalized expert count {} is not divisible by {world} ranks",
11210 gate.n_expert
11211 )
11212 .into());
11213 }
11214 let experts_per_rank = gate.n_expert / world;
11215 let mut ranks = Vec::with_capacity(world);
11216 for (rank_index, engine) in self.ranks.iter().enumerate() {
11217 let _main = engine.gpu.enter_main()?;
11218 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
11219 let mut gate_host = Vec::with_capacity(experts_per_rank * gate.expert_stride);
11220 let mut up_host = Vec::with_capacity(experts_per_rank * up.expert_stride);
11221 let mut down_host = Vec::with_capacity(experts_per_rank * down.expert_stride);
11222 for expert in expert_range.clone() {
11223 gate_host.extend_from_slice(gate.expert_bytes(expert));
11224 up_host.extend_from_slice(up.expert_bytes(expert));
11225 down_host.extend_from_slice(down.expert_bytes(expert));
11226 }
11227 ranks.push(ResidentNvfp4EpRank {
11228 gate: engine.htod_bytes(&gate_host)?,
11229 up: engine.htod_bytes(&up_host)?,
11230 down: engine.htod_bytes(&down_host)?,
11231 gate_expert_bytes: gate.expert_stride,
11232 down_expert_bytes: down.expert_stride,
11233 macros_gate: engine.htod(¯os_gate[expert_range.clone()])?,
11234 macros_up: engine.htod(¯os_up[expert_range.clone()])?,
11235 macros_down: engine.htod(¯os_down[expert_range.clone()])?,
11236 expert_range,
11237 });
11238 }
11239 Ok(ResidentNvfp4ExpertParallel {
11240 ranks,
11241 macros_gate,
11242 macros_up,
11243 macros_down,
11244 expert_count: gate.n_expert,
11245 input_width: gate.in_f,
11246 expert_width: gate.out_f,
11247 gate_row_bytes: gate.row_bytes,
11248 down_row_bytes: down.row_bytes,
11249 device_workspace: std::sync::Mutex::new(None),
11250 })
11251 }
11252
11253 #[allow(clippy::too_many_arguments)]
11259 pub fn run_routed_experts_nvfp4(
11260 &self,
11261 experts: &ResidentNvfp4ExpertParallel,
11262 input: &[f32],
11263 tokens: usize,
11264 selected: &[usize],
11265 route_weights: &[f32],
11266 experts_per_token: usize,
11267 activation_limit: Option<f32>,
11268 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11269 validate_activations(input, tokens, experts.input_width)?;
11270 let pairs = tokens
11271 .checked_mul(experts_per_token)
11272 .ok_or("NVFP4 EP route count overflow")?;
11273 if selected.len() != pairs || route_weights.len() != pairs {
11274 return Err(format!(
11275 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
11276 {experts_per_token} ({pairs})",
11277 selected.len(),
11278 route_weights.len(),
11279 )
11280 .into());
11281 }
11282 if !route_weights.iter().all(|weight| weight.is_finite()) {
11283 return Err("NVFP4 EP route weights contain a non-finite value".into());
11284 }
11285 let experts_per_rank = experts.expert_count / experts.ranks.len();
11286 let mut output = vec![0.0f32; tokens * experts.input_width];
11287 for token in 0..tokens {
11288 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11289 for slot in 0..experts_per_token {
11290 let pair = token * experts_per_token + slot;
11291 let expert = selected[pair];
11292 if expert >= experts.expert_count {
11293 return Err(format!(
11294 "NVFP4 EP selected expert {expert} outside 0..{}",
11295 experts.expert_count
11296 )
11297 .into());
11298 }
11299 let owner = expert / experts_per_rank;
11300 let local = expert - owner * experts_per_rank;
11301 let rank = &experts.ranks[owner];
11302 let engine = &self.ranks[owner];
11303 let _main = engine.gpu.enter_main()?;
11304 let device_input = engine.htod(input_row)?;
11305 let gate_out = engine.qmatvec_nvfp4_fast(
11306 &rank.gate.slice(
11307 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
11308 ),
11309 &device_input,
11310 1,
11311 experts.input_width,
11312 experts.expert_width,
11313 experts.gate_row_bytes,
11314 )?;
11315 let up_out = engine.qmatvec_nvfp4_fast(
11316 &rank.up.slice(
11317 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
11318 ),
11319 &device_input,
11320 1,
11321 experts.input_width,
11322 experts.expert_width,
11323 experts.gate_row_bytes,
11324 )?;
11325 let mut gate_host = engine.dtoh(&gate_out)?;
11326 let mut up_host = engine.dtoh(&up_out)?;
11327 apply_macro(&mut gate_host, experts.macros_gate[expert]);
11328 apply_macro(&mut up_host, experts.macros_up[expert]);
11329 let activated: Vec<f32> = gate_host
11330 .iter()
11331 .zip(&up_host)
11332 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11333 .collect();
11334 let device_activated = engine.htod(&activated)?;
11335 let down_out = engine.qmatvec_nvfp4_fast(
11336 &rank.down.slice(
11337 local * rank.down_expert_bytes..(local + 1) * rank.down_expert_bytes,
11338 ),
11339 &device_activated,
11340 1,
11341 experts.expert_width,
11342 experts.input_width,
11343 experts.down_row_bytes,
11344 )?;
11345 let mut down_host = engine.dtoh(&down_out)?;
11346 apply_macro(&mut down_host, experts.macros_down[expert]);
11347 let weight = route_weights[pair];
11348 for (sum, value) in output
11349 [token * experts.input_width..(token + 1) * experts.input_width]
11350 .iter_mut()
11351 .zip(down_host)
11352 {
11353 *sum += weight * value;
11354 }
11355 }
11356 }
11357 Ok(output)
11358 }
11359
11360 #[allow(clippy::too_many_arguments)]
11369 pub fn run_routed_experts_nvfp4_w4a16_device_io(
11370 &self,
11371 experts: &ResidentNvfp4ExpertParallel,
11372 e: &Engine,
11373 input_dev: &crate::CudaSlice<f32>,
11374 tokens: usize,
11375 selected: &[usize],
11376 route_weights: &[f32],
11377 experts_per_token: usize,
11378 activation_limit: Option<f32>,
11379 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11380 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11384 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11385 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11386 let started = timing.then(std::time::Instant::now);
11387 if !self.native_p2p {
11388 return Err("W4A16 NVFP4 device EP requires native P2P".into());
11389 }
11390 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11391 return Err(format!(
11392 "W4A16 NVFP4 device EP root device {:?} != model engine device {}",
11393 self.devices.first(),
11394 e.ctx().ordinal()
11395 )
11396 .into());
11397 }
11398 let active_input_values =
11402 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11403 let pairs = tokens
11404 .checked_mul(experts_per_token)
11405 .ok_or("W4A16 NVFP4 device EP route count overflow")?;
11406 if selected.len() != pairs || route_weights.len() != pairs {
11407 return Err(format!(
11408 "W4A16 NVFP4 device EP routes selected={} weights={} != tokens {tokens} x \
11409 experts/token {experts_per_token} ({pairs})",
11410 selected.len(),
11411 route_weights.len(),
11412 )
11413 .into());
11414 }
11415 if !route_weights.iter().all(|weight| weight.is_finite()) {
11416 return Err("W4A16 NVFP4 device EP route weights contain a non-finite value".into());
11417 }
11418 let world = self.ranks.len();
11419 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11420 return Err(format!(
11421 "W4A16 NVFP4 device EP runtime ranks {world} != bank ranks {}",
11422 experts.ranks.len()
11423 )
11424 .into());
11425 }
11426 let owner_routes = partition_expert_owner_routes(
11427 experts.expert_count,
11428 world,
11429 tokens,
11430 experts_per_token,
11431 selected,
11432 )?;
11433
11434 let mut workspace_guard = experts
11435 .device_workspace
11436 .lock()
11437 .map_err(|_| "W4A16 NVFP4 device EP workspace lock is poisoned")?;
11438 if workspace_guard.is_none() {
11439 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11440 let capacity_pairs = capacity_tokens * experts_per_token;
11441 let mut input = Vec::with_capacity(world);
11442 let mut input_bf16 = Vec::with_capacity(world);
11443 let mut input_q8 = Vec::with_capacity(world);
11444 let mut input_q8_scales = Vec::with_capacity(world);
11445 let mut sel = Vec::with_capacity(world);
11446 let mut token_rows = Vec::with_capacity(world);
11447 let mut global_pairs = Vec::with_capacity(world);
11448 let mut route_w = Vec::with_capacity(world);
11449 let mut gate_out = Vec::with_capacity(world);
11450 let mut up_out = Vec::with_capacity(world);
11451 let mut activation_bf16 = Vec::with_capacity(world);
11452 let mut activation_q8 = Vec::with_capacity(world);
11453 let mut activation_q8_scales = Vec::with_capacity(world);
11454 let mut ev_rank = Vec::with_capacity(world);
11455 for engine in &self.ranks {
11456 let _main = engine.gpu.enter_main()?;
11457 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11458 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11459 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11460 input_q8_scales
11461 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11462 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11463 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11464 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11465 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11466 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11467 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11468 activation_bf16
11469 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11470 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11471 activation_q8_scales
11472 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11473 ev_rank.push(engine.ctx().new_event(None)?);
11474 }
11475 let _main = e.gpu.enter_main()?;
11476 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11477 let slot_rows_raw = {
11478 use cudarc::driver::DevicePtr;
11479 let stream = e.stream();
11480 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11481 pointer
11482 };
11483 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11484 input,
11485 input_bf16,
11486 input_q8,
11487 input_q8_scales,
11488 sel,
11489 token_rows,
11490 global_pairs,
11491 route_w,
11492 gate_out,
11493 up_out,
11494 activation_bf16,
11495 activation_q8,
11496 activation_q8_scales,
11497 slot_rows,
11498 slot_rows_raw,
11499 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11500 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11501 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11502 graph_routes: None,
11503 graphs: std::iter::repeat_with(|| None)
11504 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11505 .collect(),
11506 ev_entry: e.ctx().new_event(None)?,
11507 ev_entry_device: e.ctx().ordinal(),
11508 ev_rank,
11509 phase_events: None,
11510 capacity_tokens,
11511 experts_per_token,
11512 });
11513 }
11514 let workspace = workspace_guard
11515 .as_mut()
11516 .expect("W4A16 NVFP4 device EP workspace initialized above");
11517 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11518 return Err(format!(
11519 "W4A16 NVFP4 device EP workspace tokens={} experts/token={} cannot serve \
11520 tokens={tokens} experts/token={experts_per_token}",
11521 workspace.capacity_tokens, workspace.experts_per_token,
11522 )
11523 .into());
11524 }
11525 if workspace.ev_entry_device != e.ctx().ordinal() {
11526 return Err("W4A16 NVFP4 device EP model engine changed".into());
11527 }
11528
11529 {
11530 let _main = e.gpu.enter_main()?;
11531 let mut destination = workspace.route_weights.slice_mut(0..pairs);
11532 e.stream()
11533 .memcpy_htod(&route_weights[..pairs], &mut destination)?;
11534 workspace.ev_entry.record(&e.stream())?;
11535 }
11536 for (rank_index, engine) in self.ranks.iter().enumerate() {
11537 let _main = engine.gpu.enter_main()?;
11538 engine.stream().wait(&workspace.ev_entry)?;
11539 {
11540 let mut destination = workspace.input[rank_index].slice_mut(0..active_input_values);
11541 engine
11542 .stream()
11543 .memcpy_dtod(&input_dev.slice(0..active_input_values), &mut destination)?;
11544 }
11545 engine.f32_to_bf16_into(
11546 &workspace.input[rank_index],
11547 &mut workspace.input_bf16[rank_index],
11548 tokens * experts.input_width,
11549 )?;
11550 let owner = &owner_routes[rank_index];
11551 debug_assert_eq!(owner.rank, rank_index);
11552 let local_count = owner.selected.len();
11553 if local_count > 0 {
11554 let local_selected = owner
11555 .selected
11556 .iter()
11557 .map(|&expert| expert as i32)
11558 .collect::<Vec<_>>();
11559 let local_token_rows = owner
11560 .token_rows
11561 .iter()
11562 .map(|&token| token as i32)
11563 .collect::<Vec<_>>();
11564 let local_global_pairs = owner
11565 .global_pairs
11566 .iter()
11567 .map(|&pair| pair as i32)
11568 .collect::<Vec<_>>();
11569 {
11570 let mut destination = workspace.sel[rank_index].slice_mut(0..local_count);
11571 engine
11572 .stream()
11573 .memcpy_htod(&local_selected, &mut destination)?;
11574 }
11575 {
11576 let mut destination =
11577 workspace.token_rows[rank_index].slice_mut(0..local_count);
11578 engine
11579 .stream()
11580 .memcpy_htod(&local_token_rows, &mut destination)?;
11581 }
11582 {
11583 let mut destination =
11584 workspace.global_pairs[rank_index].slice_mut(0..local_count);
11585 engine
11586 .stream()
11587 .memcpy_htod(&local_global_pairs, &mut destination)?;
11588 }
11589 let rank = &experts.ranks[rank_index];
11590 engine.qmatvec_nvfp4_bf16_sel_dual_rows_into(
11591 &rank.gate,
11592 &rank.up,
11593 &workspace.sel[rank_index],
11594 &workspace.token_rows[rank_index],
11595 &workspace.input_bf16[rank_index],
11596 &mut workspace.gate_out[rank_index],
11597 &mut workspace.up_out[rank_index],
11598 local_count,
11599 experts.input_width,
11600 experts.expert_width,
11601 experts.gate_row_bytes,
11602 rank.gate_expert_bytes,
11603 tokens,
11604 )?;
11605 engine.silu_mul_scaled_host_expf_bf16_sel_into(
11606 &workspace.gate_out[rank_index],
11607 &workspace.up_out[rank_index],
11608 &rank.macros_gate,
11609 &rank.macros_up,
11610 &workspace.sel[rank_index],
11611 activation_limit,
11612 &mut workspace.activation_bf16[rank_index],
11613 experts.expert_width,
11614 local_count,
11615 )?;
11616 engine.qmatvec_nvfp4_bf16_sel_down_rows_raw(
11617 &rank.down,
11618 &workspace.sel[rank_index],
11619 &workspace.global_pairs[rank_index],
11620 &workspace.activation_bf16[rank_index],
11621 &rank.macros_down,
11622 workspace.slot_rows_raw,
11623 local_count,
11624 experts.expert_width,
11625 experts.input_width,
11626 experts.down_row_bytes,
11627 rank.down_expert_bytes,
11628 pairs,
11629 )?;
11630 }
11631 workspace.ev_rank[rank_index].record(&engine.stream())?;
11632 }
11633
11634 let output = {
11635 let _main = e.gpu.enter_main()?;
11636 for event in &workspace.ev_rank {
11637 e.stream().wait(event)?;
11638 }
11639 let mut output = e.uninit(tokens * experts.input_width)?;
11640 e.axpy_rows_seq_tokens_into(
11641 &workspace.slot_rows,
11642 &workspace.route_weights,
11643 &mut output,
11644 experts.input_width,
11645 experts_per_token,
11646 tokens,
11647 )?;
11648 output
11649 };
11650 if let Some(started) = started {
11651 use std::sync::atomic::Ordering;
11652 e.stream().synchronize()?;
11653 let elapsed = started.elapsed().as_nanos() as u64;
11654 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11655 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11656 if calls.is_multiple_of(430) {
11657 eprintln!(
11658 "[nvfp4-ep-w4a16-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11659 ns as f64 / 1.0e6,
11660 ns as f64 / calls as f64 / 1.0e3,
11661 );
11662 }
11663 }
11664 Ok(output)
11665 }
11666
11667 #[allow(clippy::too_many_arguments)]
11673 pub fn run_routed_experts_nvfp4_w4a16_device_routed(
11674 &self,
11675 experts: &ResidentNvfp4ExpertParallel,
11676 e: &Engine,
11677 input_dev: &crate::CudaSlice<f32>,
11678 selected_dev: &crate::CudaSlice<i32>,
11679 route_weights_dev: &crate::CudaSlice<f32>,
11680 tokens: usize,
11681 experts_per_token: usize,
11682 activation_limit: Option<f32>,
11683 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11684 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11685 experts,
11686 e,
11687 input_dev,
11688 selected_dev,
11689 route_weights_dev,
11690 tokens,
11691 experts_per_token,
11692 activation_limit,
11693 None,
11694 )
11695 }
11696
11697 #[allow(clippy::too_many_arguments)]
11701 pub fn run_routed_experts_nvfp4_w4a16_device_routed_prejoin(
11702 &self,
11703 experts: &ResidentNvfp4ExpertParallel,
11704 e: &Engine,
11705 input_dev: &crate::CudaSlice<f32>,
11706 selected_dev: &crate::CudaSlice<i32>,
11707 route_weights_dev: &crate::CudaSlice<f32>,
11708 tokens: usize,
11709 experts_per_token: usize,
11710 activation_limit: Option<f32>,
11711 mut pre_join: impl FnMut() -> Result<(), Box<dyn std::error::Error>>,
11712 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11713 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11714 experts,
11715 e,
11716 input_dev,
11717 selected_dev,
11718 route_weights_dev,
11719 tokens,
11720 experts_per_token,
11721 activation_limit,
11722 Some(&mut pre_join),
11723 )
11724 }
11725
11726 #[allow(clippy::too_many_arguments)]
11727 fn run_routed_experts_nvfp4_w4a16_device_routed_inner(
11728 &self,
11729 experts: &ResidentNvfp4ExpertParallel,
11730 e: &Engine,
11731 input_dev: &crate::CudaSlice<f32>,
11732 selected_dev: &crate::CudaSlice<i32>,
11733 route_weights_dev: &crate::CudaSlice<f32>,
11734 tokens: usize,
11735 experts_per_token: usize,
11736 activation_limit: Option<f32>,
11737 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11738 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11739 if !self.native_p2p {
11740 return Err("W4A16 device-routed EP requires native P2P".into());
11741 }
11742 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11743 return Err(format!(
11744 "W4A16 device-routed EP root device {:?} != model engine device {}",
11745 self.devices.first(),
11746 e.ctx().ordinal()
11747 )
11748 .into());
11749 }
11750 let active_input_values =
11751 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11752 let pairs = tokens
11753 .checked_mul(experts_per_token)
11754 .ok_or("W4A16 device-routed EP route count overflow")?;
11755 if selected_dev.len() < pairs || route_weights_dev.len() < pairs {
11756 return Err(format!(
11757 "W4A16 device-routed EP metadata selected={} weights={} < pairs={pairs}",
11758 selected_dev.len(),
11759 route_weights_dev.len(),
11760 )
11761 .into());
11762 }
11763 let world = self.ranks.len();
11764 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11765 return Err(format!(
11766 "W4A16 device-routed EP runtime ranks {world} != bank ranks {}",
11767 experts.ranks.len()
11768 )
11769 .into());
11770 }
11771
11772 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11773 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11774 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11775 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11776 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11777 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11778 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11779 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11780 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11781 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11782 let started = timing.then(std::time::Instant::now);
11783 let graph_enabled = parallel_ep_graph_enabled()?;
11784 let pair_down_enabled = parallel_ep_pair_down_enabled()?;
11785
11786 let mut workspace_guard = experts
11787 .device_workspace
11788 .lock()
11789 .map_err(|_| "W4A16 device-routed EP workspace lock is poisoned")?;
11790 if workspace_guard.is_none() {
11791 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11792 let capacity_pairs = capacity_tokens * experts_per_token;
11793 let mut input = Vec::with_capacity(world);
11794 let mut input_bf16 = Vec::with_capacity(world);
11795 let mut input_q8 = Vec::with_capacity(world);
11796 let mut input_q8_scales = Vec::with_capacity(world);
11797 let mut sel = Vec::with_capacity(world);
11798 let mut token_rows = Vec::with_capacity(world);
11799 let mut global_pairs = Vec::with_capacity(world);
11800 let mut route_w = Vec::with_capacity(world);
11801 let mut gate_out = Vec::with_capacity(world);
11802 let mut up_out = Vec::with_capacity(world);
11803 let mut activation_bf16 = Vec::with_capacity(world);
11804 let mut activation_q8 = Vec::with_capacity(world);
11805 let mut activation_q8_scales = Vec::with_capacity(world);
11806 let mut ev_rank = Vec::with_capacity(world);
11807 let mut phase_head = Vec::with_capacity(world);
11808 let mut phase_copy_done = Vec::with_capacity(world);
11809 let mut phase_gate_up_done = Vec::with_capacity(world);
11810 let mut phase_activation_done = Vec::with_capacity(world);
11811 let mut phase_down_done = Vec::with_capacity(world);
11812 for engine in &self.ranks {
11813 let _main = engine.gpu.enter_main()?;
11814 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11815 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11816 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11817 input_q8_scales
11818 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11819 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11820 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11821 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11822 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11823 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11824 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11825 activation_bf16
11826 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11827 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11828 activation_q8_scales
11829 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11830 ev_rank.push(engine.ctx().new_event(None)?);
11831 if timing {
11832 phase_head.push(
11833 engine.ctx().new_event(Some(
11834 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11835 ))?,
11836 );
11837 phase_copy_done.push(
11838 engine.ctx().new_event(Some(
11839 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11840 ))?,
11841 );
11842 phase_gate_up_done.push(
11843 engine.ctx().new_event(Some(
11844 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11845 ))?,
11846 );
11847 phase_activation_done.push(
11848 engine.ctx().new_event(Some(
11849 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11850 ))?,
11851 );
11852 phase_down_done.push(
11853 engine.ctx().new_event(Some(
11854 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11855 ))?,
11856 );
11857 }
11858 }
11859 let _main = e.gpu.enter_main()?;
11860 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11861 let slot_rows_raw = {
11862 use cudarc::driver::DevicePtr;
11863 let stream = e.stream();
11864 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11865 pointer
11866 };
11867 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11868 input,
11869 input_bf16,
11870 input_q8,
11871 input_q8_scales,
11872 sel,
11873 token_rows,
11874 global_pairs,
11875 route_w,
11876 gate_out,
11877 up_out,
11878 activation_bf16,
11879 activation_q8,
11880 activation_q8_scales,
11881 slot_rows,
11882 slot_rows_raw,
11883 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11884 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11885 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11886 graph_routes: None,
11887 graphs: std::iter::repeat_with(|| None)
11888 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11889 .collect(),
11890 ev_entry: e.ctx().new_event(None)?,
11891 ev_entry_device: e.ctx().ordinal(),
11892 ev_rank,
11893 phase_events: timing.then_some(Nvfp4EpPhaseEvents {
11894 head: phase_head,
11895 copy_done: phase_copy_done,
11896 gate_up_done: phase_gate_up_done,
11897 activation_done: phase_activation_done,
11898 down_done: phase_down_done,
11899 }),
11900 capacity_tokens,
11901 experts_per_token,
11902 });
11903 }
11904 let workspace = workspace_guard
11905 .as_mut()
11906 .expect("W4A16 device-routed EP workspace initialized above");
11907 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11908 return Err(format!(
11909 "W4A16 device-routed EP workspace tokens={} experts/token={} cannot serve \
11910 tokens={tokens} experts/token={experts_per_token}",
11911 workspace.capacity_tokens, workspace.experts_per_token,
11912 )
11913 .into());
11914 }
11915
11916 if tokens <= NVFP4_EP_Q8_BATCH_CAP && parallel_ep_q8_act_enabled()? {
11917 if graph_enabled {
11918 return Err("MEMRA_PARALLEL_EP_GRAPH=1 is exact W4A16-only; disable \
11919 MEMRA_PARALLEL_EP_Q8_ACT or the graph door"
11920 .into());
11921 }
11922 return self.run_routed_experts_nvfp4_w4a8_device_routed(
11923 experts,
11924 e,
11925 input_dev,
11926 selected_dev,
11927 route_weights_dev,
11928 workspace,
11929 tokens,
11930 experts_per_token,
11931 activation_limit,
11932 pre_join,
11933 );
11934 }
11935
11936 if graph_enabled && !timing && pre_join.is_none() && tokens <= NVFP4_EP_GRAPH_BATCH_CAP {
11937 use cudarc::driver::DevicePtr;
11938 let route_ptrs = {
11939 let stream = e.stream();
11940 let (sel_ptr, _sel_guard) = selected_dev.device_ptr(&stream);
11941 let (weight_ptr, _weight_guard) = route_weights_dev.device_ptr(&stream);
11942 (sel_ptr, weight_ptr)
11943 };
11944 if let Some(graph_exec) = workspace.graphs[tokens].as_ref().map(|graph| graph.exec) {
11945 if workspace.graph_routes != Some(route_ptrs) {
11946 return Err(format!(
11947 "W4A16 EP graph route buffers moved: built={:?} current={route_ptrs:?}",
11948 workspace.graph_routes,
11949 )
11950 .into());
11951 }
11952 let _main = e.gpu.enter_main()?;
11953 e.stream().memcpy_dtod(
11954 &input_dev.slice(0..active_input_values),
11955 &mut workspace.graph_input.slice_mut(0..active_input_values),
11956 )?;
11957 e.memset_zeros_view(
11958 &mut workspace
11959 .slot_rows
11960 .slice_mut(0..pairs * experts.input_width),
11961 )?;
11962 unsafe {
11963 let result = cudarc::driver::sys::cuGraphLaunch(
11964 graph_exec,
11965 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11966 );
11967 if result != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11968 return Err(format!("W4A16 EP graph launch: {result:?}").into());
11969 }
11970 }
11971 let mut output = e.uninit(active_input_values)?;
11972 e.stream().memcpy_dtod(
11973 &workspace.graph_output.slice(0..active_input_values),
11974 &mut output.slice_mut(0..active_input_values),
11975 )?;
11976 return Ok(output);
11977 }
11978 }
11979
11980 {
11981 let _main = e.gpu.enter_main()?;
11982 e.memset_zeros_view(
11983 &mut workspace
11984 .slot_rows
11985 .slice_mut(0..pairs * experts.input_width),
11986 )?;
11987 workspace.ev_entry.record(&e.stream())?;
11988 }
11989
11990 for (rank_index, engine) in self.ranks.iter().enumerate() {
11991 let _main = engine.gpu.enter_main()?;
11992 if let Some(events) = workspace.phase_events.as_ref() {
11993 events.head[rank_index].record(&engine.stream())?;
11994 }
11995 engine.stream().wait(&workspace.ev_entry)?;
11996 let Nvfp4EpDeviceWorkspace {
11997 input_bf16,
11998 sel,
11999 route_w,
12000 ..
12001 } = &mut *workspace;
12002 engine.nvfp4_ep_stage_inputs(
12003 input_dev,
12004 selected_dev,
12005 route_weights_dev,
12006 &mut input_bf16[rank_index],
12007 &mut sel[rank_index],
12008 &mut route_w[rank_index],
12009 active_input_values,
12010 pairs,
12011 false,
12012 )?;
12013 if let Some(events) = workspace.phase_events.as_ref() {
12014 events.copy_done[rank_index].record(&engine.stream())?;
12015 }
12016 let rank = &experts.ranks[rank_index];
12017 let owner_start = rank.expert_range.start;
12018 let owner_end = rank.expert_range.end;
12019 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12020 &rank.gate,
12021 &rank.up,
12022 &workspace.sel[rank_index],
12023 &workspace.input_bf16[rank_index],
12024 &mut workspace.gate_out[rank_index],
12025 &mut workspace.up_out[rank_index],
12026 pairs,
12027 experts_per_token,
12028 experts.input_width,
12029 experts.expert_width,
12030 owner_start,
12031 owner_end,
12032 experts.gate_row_bytes,
12033 rank.gate_expert_bytes,
12034 )?;
12035 if let Some(events) = workspace.phase_events.as_ref() {
12036 events.gate_up_done[rank_index].record(&engine.stream())?;
12037 }
12038 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12039 &workspace.gate_out[rank_index],
12040 &workspace.up_out[rank_index],
12041 &rank.macros_gate,
12042 &rank.macros_up,
12043 &workspace.sel[rank_index],
12044 owner_start,
12045 owner_end,
12046 activation_limit,
12047 &mut workspace.activation_bf16[rank_index],
12048 experts.expert_width,
12049 pairs,
12050 )?;
12051 if let Some(events) = workspace.phase_events.as_ref() {
12052 events.activation_done[rank_index].record(&engine.stream())?;
12053 }
12054 if tokens > 1 && pair_down_enabled {
12055 engine.qmatvec_nvfp4_bf16_ep_down_pairs_raw(
12056 &rank.down,
12057 &workspace.sel[rank_index],
12058 &workspace.activation_bf16[rank_index],
12059 &rank.macros_down,
12060 workspace.slot_rows_raw,
12061 pairs,
12062 experts.expert_width,
12063 experts.input_width,
12064 owner_start,
12065 owner_end,
12066 experts.down_row_bytes,
12067 rank.down_expert_bytes,
12068 )?;
12069 } else {
12070 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12071 &rank.down,
12072 &workspace.sel[rank_index],
12073 &workspace.activation_bf16[rank_index],
12074 &rank.macros_down,
12075 workspace.slot_rows_raw,
12076 pairs,
12077 experts.expert_width,
12078 experts.input_width,
12079 owner_start,
12080 owner_end,
12081 experts.down_row_bytes,
12082 rank.down_expert_bytes,
12083 )?;
12084 }
12085 if let Some(events) = workspace.phase_events.as_ref() {
12086 events.down_done[rank_index].record(&engine.stream())?;
12087 }
12088 workspace.ev_rank[rank_index].record(&engine.stream())?;
12089 }
12090
12091 if let Some(pre_join) = pre_join.as_mut() {
12092 pre_join()?;
12093 }
12094 let issue_ns_this = started
12095 .as_ref()
12096 .map(|started| started.elapsed().as_nanos() as u64);
12097 let join_started = timing.then(std::time::Instant::now);
12098 let output = {
12099 let _main = e.gpu.enter_main()?;
12100 for event in &workspace.ev_rank {
12101 e.stream().wait(event)?;
12102 }
12103 let mut output = e.uninit(tokens * experts.input_width)?;
12104 e.axpy_rows_seq_tokens_into(
12105 &workspace.slot_rows,
12106 route_weights_dev,
12107 &mut output,
12108 experts.input_width,
12109 experts_per_token,
12110 tokens,
12111 )?;
12112 output
12113 };
12114
12115 if let Some(started) = started {
12116 use std::sync::atomic::Ordering;
12117 e.stream().synchronize()?;
12118 let elapsed = started.elapsed().as_nanos() as u64;
12119 let join_ns_this = join_started
12120 .expect("timing join starts with total timing")
12121 .elapsed()
12122 .as_nanos() as u64;
12123 let mut phase_max_ms = [0.0f32; 5];
12124 if let Some(events) = workspace.phase_events.as_ref() {
12125 for rank_index in 0..world {
12126 let engine = &self.ranks[rank_index];
12127 let _main = engine.gpu.enter_main()?;
12128 phase_max_ms[0] = phase_max_ms[0]
12129 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12130 phase_max_ms[1] = phase_max_ms[1].max(
12131 events.copy_done[rank_index]
12132 .elapsed_ms(&events.gate_up_done[rank_index])?,
12133 );
12134 phase_max_ms[2] = phase_max_ms[2].max(
12135 events.gate_up_done[rank_index]
12136 .elapsed_ms(&events.activation_done[rank_index])?,
12137 );
12138 phase_max_ms[3] = phase_max_ms[3].max(
12139 events.activation_done[rank_index]
12140 .elapsed_ms(&events.down_done[rank_index])?,
12141 );
12142 phase_max_ms[4] = phase_max_ms[4]
12143 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12144 }
12145 }
12146 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12147 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12148 let issue_ns = ISSUE_NS.fetch_add(
12149 issue_ns_this.expect("timing issue starts with total timing"),
12150 Ordering::Relaxed,
12151 ) + issue_ns_this.expect("timing issue starts with total timing");
12152 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12153 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12154 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12155 let activation_ns =
12156 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12157 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12158 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12159 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12160 if calls.is_multiple_of(430) {
12161 eprintln!(
12162 "[nvfp4-ep-device-router-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12163 ns as f64 / 1.0e6,
12164 ns as f64 / calls as f64 / 1.0e3,
12165 );
12166 eprintln!(
12167 "[nvfp4-ep-device-router-phases] calls={calls} issue_us={:.1} \
12168 join_us={:.1} rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12169 activation_us={:.1} down_us={:.1}",
12170 issue_ns as f64 / calls as f64 / 1.0e3,
12171 join_ns as f64 / calls as f64 / 1.0e3,
12172 rank_span_ns as f64 / calls as f64 / 1.0e3,
12173 copy_ns as f64 / calls as f64 / 1.0e3,
12174 gate_up_ns as f64 / calls as f64 / 1.0e3,
12175 activation_ns as f64 / calls as f64 / 1.0e3,
12176 down_ns as f64 / calls as f64 / 1.0e3,
12177 );
12178 }
12179 }
12180 if graph_enabled
12181 && !timing
12182 && pre_join.is_none()
12183 && tokens <= NVFP4_EP_GRAPH_BATCH_CAP
12184 && workspace.graphs[tokens].is_none()
12185 {
12186 e.stream().synchronize()?;
12187 let graph = self.build_nvfp4_ep_routes_graph(
12188 experts,
12189 e,
12190 workspace,
12191 selected_dev,
12192 route_weights_dev,
12193 tokens,
12194 experts_per_token,
12195 activation_limit,
12196 )?;
12197 workspace.graphs[tokens] = Some(graph);
12198 eprintln!(
12199 "[parallel-ep-graph] captured devices={:?} tokens={tokens} \
12200 experts/token={experts_per_token} input=staged routes=fixed \
12201 device_arithmetic=unchanged performance_claim=false",
12202 self.devices,
12203 );
12204 }
12205 Ok(output)
12206 }
12207
12208 #[allow(clippy::too_many_arguments)]
12209 fn run_routed_experts_nvfp4_w4a8_device_routed(
12210 &self,
12211 experts: &ResidentNvfp4ExpertParallel,
12212 e: &Engine,
12213 input_dev: &crate::CudaSlice<f32>,
12214 selected_dev: &crate::CudaSlice<i32>,
12215 route_weights_dev: &crate::CudaSlice<f32>,
12216 workspace: &mut Nvfp4EpDeviceWorkspace,
12217 tokens: usize,
12218 experts_per_token: usize,
12219 activation_limit: Option<f32>,
12220 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
12221 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12222 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12223 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12224 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12225 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12226 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12227 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12228 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12229 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12230 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12231 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12232 let started = timing.then(std::time::Instant::now);
12233 let pairs = tokens
12234 .checked_mul(experts_per_token)
12235 .ok_or("W4A8 device-routed EP route count overflow")?;
12236 let input_values = tokens
12237 .checked_mul(experts.input_width)
12238 .ok_or("W4A8 device-routed EP input size overflow")?;
12239 let scope = parallel_ep_q8_scope()?.unwrap_or(ParallelEpQ8Scope::All);
12240 let gate_up_paired = parallel_ep_q8_gu_paired_enabled(true, Some(scope))?;
12241
12242 {
12243 let _main = e.gpu.enter_main()?;
12244 e.memset_zeros_view(
12245 &mut workspace
12246 .slot_rows
12247 .slice_mut(0..pairs * experts.input_width),
12248 )?;
12249 workspace.ev_entry.record(&e.stream())?;
12250 }
12251 for (rank_index, engine) in self.ranks.iter().enumerate() {
12252 let _main = engine.gpu.enter_main()?;
12253 if let Some(events) = workspace.phase_events.as_ref() {
12254 events.head[rank_index].record(&engine.stream())?;
12255 }
12256 engine.stream().wait(&workspace.ev_entry)?;
12257 let rank = &experts.ranks[rank_index];
12258 let owner_start = rank.expert_range.start;
12259 let owner_end = rank.expert_range.end;
12260 match scope {
12261 ParallelEpQ8Scope::All | ParallelEpQ8Scope::GateUp => {
12262 engine.quantize_q8_1_into(
12263 input_dev,
12264 tokens,
12265 experts.input_width,
12266 &mut workspace.input_q8[rank_index],
12267 &mut workspace.input_q8_scales[rank_index],
12268 )?;
12269 engine.moe_sel_w_mirror(
12270 selected_dev,
12271 route_weights_dev,
12272 &mut workspace.sel[rank_index],
12273 &mut workspace.route_w[rank_index],
12274 pairs,
12275 )?;
12276 if let Some(events) = workspace.phase_events.as_ref() {
12277 events.copy_done[rank_index].record(&engine.stream())?;
12278 }
12279 if gate_up_paired {
12280 engine.qmatvec_nvfp4_q8_ep_paired_slots_into(
12281 &rank.gate,
12282 &rank.up,
12283 &workspace.sel[rank_index],
12284 &workspace.input_q8[rank_index],
12285 &workspace.input_q8_scales[rank_index],
12286 &mut workspace.gate_out[rank_index],
12287 &mut workspace.up_out[rank_index],
12288 pairs,
12289 experts_per_token,
12290 experts.input_width,
12291 experts.expert_width,
12292 owner_start,
12293 owner_end,
12294 experts.gate_row_bytes,
12295 rank.gate_expert_bytes,
12296 )?;
12297 } else {
12298 engine.qmatvec_nvfp4_q8_ep_dual_slots_into(
12299 &rank.gate,
12300 &rank.up,
12301 &workspace.sel[rank_index],
12302 &workspace.input_q8[rank_index],
12303 &workspace.input_q8_scales[rank_index],
12304 &mut workspace.gate_out[rank_index],
12305 &mut workspace.up_out[rank_index],
12306 pairs,
12307 experts_per_token,
12308 experts.input_width,
12309 experts.expert_width,
12310 owner_start,
12311 owner_end,
12312 experts.gate_row_bytes,
12313 rank.gate_expert_bytes,
12314 )?;
12315 }
12316 }
12317 ParallelEpQ8Scope::Down => {
12318 engine.nvfp4_ep_stage_inputs(
12319 input_dev,
12320 selected_dev,
12321 route_weights_dev,
12322 &mut workspace.input_bf16[rank_index],
12323 &mut workspace.sel[rank_index],
12324 &mut workspace.route_w[rank_index],
12325 input_values,
12326 pairs,
12327 false,
12328 )?;
12329 if let Some(events) = workspace.phase_events.as_ref() {
12330 events.copy_done[rank_index].record(&engine.stream())?;
12331 }
12332 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12333 &rank.gate,
12334 &rank.up,
12335 &workspace.sel[rank_index],
12336 &workspace.input_bf16[rank_index],
12337 &mut workspace.gate_out[rank_index],
12338 &mut workspace.up_out[rank_index],
12339 pairs,
12340 experts_per_token,
12341 experts.input_width,
12342 experts.expert_width,
12343 owner_start,
12344 owner_end,
12345 experts.gate_row_bytes,
12346 rank.gate_expert_bytes,
12347 )?;
12348 }
12349 }
12350 if let Some(events) = workspace.phase_events.as_ref() {
12351 events.gate_up_done[rank_index].record(&engine.stream())?;
12352 }
12353 match scope {
12354 ParallelEpQ8Scope::All | ParallelEpQ8Scope::Down => {
12355 engine.silu_mul_scaled_host_expf_q8_ep_slots_into(
12356 &workspace.gate_out[rank_index],
12357 &workspace.up_out[rank_index],
12358 &rank.macros_gate,
12359 &rank.macros_up,
12360 &workspace.sel[rank_index],
12361 owner_start,
12362 owner_end,
12363 activation_limit,
12364 &mut workspace.activation_q8[rank_index],
12365 &mut workspace.activation_q8_scales[rank_index],
12366 experts.expert_width,
12367 pairs,
12368 )?;
12369 if let Some(events) = workspace.phase_events.as_ref() {
12370 events.activation_done[rank_index].record(&engine.stream())?;
12371 }
12372 engine.qmatvec_nvfp4_q8_ep_down_slots_raw(
12373 &rank.down,
12374 &workspace.sel[rank_index],
12375 &workspace.activation_q8[rank_index],
12376 &workspace.activation_q8_scales[rank_index],
12377 &rank.macros_down,
12378 workspace.slot_rows_raw,
12379 pairs,
12380 experts.expert_width,
12381 experts.input_width,
12382 owner_start,
12383 owner_end,
12384 experts.down_row_bytes,
12385 rank.down_expert_bytes,
12386 )?;
12387 }
12388 ParallelEpQ8Scope::GateUp => {
12389 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12390 &workspace.gate_out[rank_index],
12391 &workspace.up_out[rank_index],
12392 &rank.macros_gate,
12393 &rank.macros_up,
12394 &workspace.sel[rank_index],
12395 owner_start,
12396 owner_end,
12397 activation_limit,
12398 &mut workspace.activation_bf16[rank_index],
12399 experts.expert_width,
12400 pairs,
12401 )?;
12402 if let Some(events) = workspace.phase_events.as_ref() {
12403 events.activation_done[rank_index].record(&engine.stream())?;
12404 }
12405 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12406 &rank.down,
12407 &workspace.sel[rank_index],
12408 &workspace.activation_bf16[rank_index],
12409 &rank.macros_down,
12410 workspace.slot_rows_raw,
12411 pairs,
12412 experts.expert_width,
12413 experts.input_width,
12414 owner_start,
12415 owner_end,
12416 experts.down_row_bytes,
12417 rank.down_expert_bytes,
12418 )?;
12419 }
12420 }
12421 if let Some(events) = workspace.phase_events.as_ref() {
12422 events.down_done[rank_index].record(&engine.stream())?;
12423 }
12424 workspace.ev_rank[rank_index].record(&engine.stream())?;
12425 }
12426
12427 if let Some(pre_join) = pre_join.as_mut() {
12428 pre_join()?;
12429 }
12430 let issue_ns_this = started
12431 .as_ref()
12432 .map(|started| started.elapsed().as_nanos() as u64);
12433 let join_started = timing.then(std::time::Instant::now);
12434 let output = {
12435 let _main = e.gpu.enter_main()?;
12436 for event in &workspace.ev_rank {
12437 e.stream().wait(event)?;
12438 }
12439 let mut output = e.uninit(input_values)?;
12440 e.axpy_rows_seq_tokens_into(
12441 &workspace.slot_rows,
12442 route_weights_dev,
12443 &mut output,
12444 experts.input_width,
12445 experts_per_token,
12446 tokens,
12447 )?;
12448 output
12449 };
12450 if let Some(started) = started {
12451 use std::sync::atomic::Ordering;
12452 e.stream().synchronize()?;
12453 let elapsed = started.elapsed().as_nanos() as u64;
12454 let join_ns_this = join_started
12455 .expect("timing join starts with total timing")
12456 .elapsed()
12457 .as_nanos() as u64;
12458 let mut phase_max_ms = [0.0f32; 5];
12459 if let Some(events) = workspace.phase_events.as_ref() {
12460 for rank_index in 0..self.ranks.len() {
12461 let engine = &self.ranks[rank_index];
12462 let _main = engine.gpu.enter_main()?;
12463 phase_max_ms[0] = phase_max_ms[0]
12464 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12465 phase_max_ms[1] = phase_max_ms[1].max(
12466 events.copy_done[rank_index]
12467 .elapsed_ms(&events.gate_up_done[rank_index])?,
12468 );
12469 phase_max_ms[2] = phase_max_ms[2].max(
12470 events.gate_up_done[rank_index]
12471 .elapsed_ms(&events.activation_done[rank_index])?,
12472 );
12473 phase_max_ms[3] = phase_max_ms[3].max(
12474 events.activation_done[rank_index]
12475 .elapsed_ms(&events.down_done[rank_index])?,
12476 );
12477 phase_max_ms[4] = phase_max_ms[4]
12478 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12479 }
12480 }
12481 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12482 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12483 let issue_ns = ISSUE_NS.fetch_add(
12484 issue_ns_this.expect("timing issue starts with total timing"),
12485 Ordering::Relaxed,
12486 ) + issue_ns_this.expect("timing issue starts with total timing");
12487 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12488 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12489 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12490 let activation_ns =
12491 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12492 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12493 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12494 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12495 if calls.is_multiple_of(430) {
12496 eprintln!(
12497 "[nvfp4-ep-q8-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12498 ns as f64 / 1.0e6,
12499 ns as f64 / calls as f64 / 1.0e3,
12500 );
12501 eprintln!(
12502 "[nvfp4-ep-q8-phases] calls={calls} issue_us={:.1} join_us={:.1} \
12503 rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12504 activation_us={:.1} down_us={:.1}",
12505 issue_ns as f64 / calls as f64 / 1.0e3,
12506 join_ns as f64 / calls as f64 / 1.0e3,
12507 rank_span_ns as f64 / calls as f64 / 1.0e3,
12508 copy_ns as f64 / calls as f64 / 1.0e3,
12509 gate_up_ns as f64 / calls as f64 / 1.0e3,
12510 activation_ns as f64 / calls as f64 / 1.0e3,
12511 down_ns as f64 / calls as f64 / 1.0e3,
12512 );
12513 }
12514 }
12515 static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
12516 if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
12517 let (expert_input, post_activation, numeric_class) = match scope {
12518 ParallelEpQ8Scope::All => ("q8_1", "q8_1", "w4a8-internal"),
12519 ParallelEpQ8Scope::GateUp => ("q8_1", "bf16", "w4a8-gate-up-internal"),
12520 ParallelEpQ8Scope::Down => ("bf16", "q8_1", "w4a8-down-internal"),
12521 };
12522 eprintln!(
12523 "[parallel-ep-q8] devices={:?} tokens={tokens} scope={} \
12524 expert_input={expert_input} post_activation={post_activation} \
12525 gate_up_schedule={} \
12526 external_boundary=bf16 numeric_class={numeric_class} \
12527 host_expf=true accumulation=token-slot-order performance_claim=false",
12528 self.devices,
12529 scope.label(),
12530 if gate_up_paired {
12531 "paired-cta"
12532 } else {
12533 "separate-cta"
12534 },
12535 );
12536 }
12537 Ok(output)
12538 }
12539
12540 #[allow(clippy::too_many_arguments)]
12541 fn build_nvfp4_ep_routes_graph(
12542 &self,
12543 experts: &ResidentNvfp4ExpertParallel,
12544 e: &Engine,
12545 workspace: &mut Nvfp4EpDeviceWorkspace,
12546 selected_dev: &crate::CudaSlice<i32>,
12547 route_weights_dev: &crate::CudaSlice<f32>,
12548 tokens: usize,
12549 experts_per_token: usize,
12550 activation_limit: Option<f32>,
12551 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12552 use cudarc::driver::DevicePtr;
12553 use cudarc::driver::sys;
12554
12555 fn cu_try(result: sys::CUresult, context: &str) -> Result<(), Box<dyn std::error::Error>> {
12556 if result == sys::CUresult::CUDA_SUCCESS {
12557 Ok(())
12558 } else {
12559 Err(format!("{context}: {result:?}").into())
12560 }
12561 }
12562
12563 let world = self.ranks.len();
12564 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
12565 return Err(format!(
12566 "W4A16 EP graph world {world} != expert ranks {}",
12567 experts.ranks.len()
12568 )
12569 .into());
12570 }
12571 let width = experts.input_width;
12572 if !(1..=NVFP4_EP_GRAPH_BATCH_CAP).contains(&tokens) {
12573 return Err(format!(
12574 "W4A16 EP graph tokens {tokens} outside 1..={NVFP4_EP_GRAPH_BATCH_CAP}"
12575 )
12576 .into());
12577 }
12578 let pairs = tokens
12579 .checked_mul(experts_per_token)
12580 .ok_or("W4A16 EP graph pair count overflow")?;
12581 let input_values = tokens
12582 .checked_mul(width)
12583 .ok_or("W4A16 EP graph input size overflow")?;
12584 let root_stream = e.stream();
12585 let (input_ptr, _input_guard) = workspace.graph_input.device_ptr(&root_stream);
12586 let (selected_ptr, _selected_guard) = selected_dev.device_ptr(&root_stream);
12587 let (weights_ptr, _weights_guard) = route_weights_dev.device_ptr(&root_stream);
12588 let route_ptrs = (selected_ptr, weights_ptr);
12589
12590 let mut children = Vec::with_capacity(world + 1);
12591 for rank_index in 0..world {
12592 let engine = &self.ranks[rank_index];
12593 let rank = &experts.ranks[rank_index];
12594 let owner_start = rank.expert_range.start;
12595 let owner_end = rank.expert_range.end;
12596 let _main = engine.gpu.enter_main()?;
12597 let (child, _retained) = engine.capture_graph_retained(|_| {
12598 engine.nvfp4_ep_stage_inputs_raw(
12599 input_ptr,
12600 selected_ptr,
12601 weights_ptr,
12602 &mut workspace.input_bf16[rank_index],
12603 &mut workspace.sel[rank_index],
12604 &mut workspace.route_w[rank_index],
12605 input_values,
12606 pairs,
12607 false,
12608 )?;
12609 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12610 &rank.gate,
12611 &rank.up,
12612 &workspace.sel[rank_index],
12613 &workspace.input_bf16[rank_index],
12614 &mut workspace.gate_out[rank_index],
12615 &mut workspace.up_out[rank_index],
12616 pairs,
12617 experts_per_token,
12618 width,
12619 experts.expert_width,
12620 owner_start,
12621 owner_end,
12622 experts.gate_row_bytes,
12623 rank.gate_expert_bytes,
12624 )?;
12625 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12626 &workspace.gate_out[rank_index],
12627 &workspace.up_out[rank_index],
12628 &rank.macros_gate,
12629 &rank.macros_up,
12630 &workspace.sel[rank_index],
12631 owner_start,
12632 owner_end,
12633 activation_limit,
12634 &mut workspace.activation_bf16[rank_index],
12635 experts.expert_width,
12636 pairs,
12637 )?;
12638 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12639 &rank.down,
12640 &workspace.sel[rank_index],
12641 &workspace.activation_bf16[rank_index],
12642 &rank.macros_down,
12643 workspace.slot_rows_raw,
12644 pairs,
12645 experts.expert_width,
12646 width,
12647 owner_start,
12648 owner_end,
12649 experts.down_row_bytes,
12650 rank.down_expert_bytes,
12651 )?;
12652 Ok(())
12653 })?;
12654 children.push(child);
12655 }
12656
12657 {
12658 let _main = e.gpu.enter_main()?;
12659 let (child, _retained) = e.capture_graph_retained(|_| {
12660 e.axpy_rows_seq_tokens_into(
12661 &workspace.slot_rows,
12662 route_weights_dev,
12663 &mut workspace.graph_output,
12664 width,
12665 experts_per_token,
12666 tokens,
12667 )
12668 })?;
12669 children.push(child);
12670 }
12671
12672 let mut parent: sys::CUgraph = std::ptr::null_mut();
12673 unsafe {
12674 cu_try(sys::cuGraphCreate(&mut parent, 0), "W4A16 EP cuGraphCreate")?;
12675 }
12676 let mut rank_nodes = Vec::with_capacity(world);
12677 for (rank_index, child) in children.iter().take(world).enumerate() {
12678 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12679 unsafe {
12680 cu_try(
12681 sys::cuGraphAddChildGraphNode(
12682 &mut node,
12683 parent,
12684 std::ptr::null(),
12685 0,
12686 child.cu_graph(),
12687 ),
12688 &format!("W4A16 EP graph rank {rank_index}"),
12689 )?;
12690 }
12691 rank_nodes.push(node);
12692 }
12693 let mut combine_node: sys::CUgraphNode = std::ptr::null_mut();
12694 unsafe {
12695 cu_try(
12696 sys::cuGraphAddChildGraphNode(
12697 &mut combine_node,
12698 parent,
12699 rank_nodes.as_ptr(),
12700 rank_nodes.len(),
12701 children[world].cu_graph(),
12702 ),
12703 "W4A16 EP graph combine",
12704 )?;
12705 }
12706 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12707 unsafe {
12708 cu_try(
12709 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12710 "W4A16 EP graph instantiate",
12711 )?;
12712 }
12713 workspace.graph_routes = Some(route_ptrs);
12714 Ok(RoutesGraph {
12715 exec,
12716 parent,
12717 _children: children,
12718 })
12719 }
12720
12721 pub fn run_tensor_parallel_routes_nvfp4_device(
12735 &self,
12736 experts: &ResidentNvfp4TensorParallel,
12737 input: &[f32],
12738 selected: &[usize],
12739 route_weights: &[f32],
12740 experts_per_token: usize,
12741 activation_limit: Option<f32>,
12742 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12743 validate_activations(input, 1, experts.input_width)?;
12744 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12745 return Err(format!(
12746 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
12747 selected.len(),
12748 route_weights.len(),
12749 )
12750 .into());
12751 }
12752 if !route_weights.iter().all(|weight| weight.is_finite()) {
12753 return Err("NVFP4 device route weights contain a non-finite value".into());
12754 }
12755 let world = self.ranks.len();
12756 if world != NVFP4_CANONICAL_ROW_SHARDS {
12757 return Err(format!(
12758 "NVFP4 device routes require world == canonical shard grid \
12759 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12760 )
12761 .into());
12762 }
12763 let local_out = if experts.ep2 {
12764 experts.expert_width
12765 } else {
12766 experts.expert_width / world
12767 };
12768
12769 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12773 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12774 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12775 let started = timing.then(std::time::Instant::now);
12776
12777 let n_sel = experts_per_token;
12778 let mut workspace_guard = experts
12779 .device_workspace
12780 .lock()
12781 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12782 if workspace_guard.is_none() {
12783 let mut gate_out = Vec::with_capacity(world);
12784 let mut up_out = Vec::with_capacity(world);
12785 let mut act_q = Vec::with_capacity(world);
12786 let mut act_d = Vec::with_capacity(world);
12787 let mut sel = Vec::with_capacity(world);
12788 let mut partial = Vec::with_capacity(world);
12789 let mut accumulator = Vec::with_capacity(world);
12790 let mut combine_w = Vec::with_capacity(world);
12791 let mut route_w = Vec::with_capacity(world);
12792 let mut in_q = Vec::with_capacity(world);
12793 let mut in_d = Vec::with_capacity(world);
12794 let mut input = Vec::with_capacity(world);
12795 let mut ev_rank = Vec::with_capacity(world);
12796 let moe_direct = moe_direct_on();
12797 for (rank, engine) in self.ranks.iter().enumerate() {
12798 let _main = engine.gpu.enter_main()?;
12799 gate_out.push(engine.uninit(n_sel * local_out)?);
12800 up_out.push(engine.uninit(n_sel * local_out)?);
12801 act_q.push(engine.uninit_i8(n_sel * local_out)?);
12802 act_d.push(engine.uninit(n_sel * local_out / 32)?);
12803 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
12804 partial.push(engine.uninit(n_sel * experts.input_width)?);
12805 if moe_direct && rank != 0 {
12807 let root = &self.ranks[0];
12808 let _root_main = root.gpu.enter_main()?;
12809 accumulator.push(root.zeros(experts.input_width)?);
12810 } else {
12811 accumulator.push(engine.zeros(experts.input_width)?);
12812 }
12813 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12814 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12815 in_q.push(engine.uninit_i8(experts.input_width)?);
12816 in_d.push(engine.uninit(experts.input_width / 32)?);
12817 input.push(engine.uninit(experts.input_width)?);
12818 ev_rank.push(engine.ctx().new_event(None)?);
12819 }
12820 let root = &self.ranks[0];
12821 let _main = root.gpu.enter_main()?;
12822 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
12823 prestaged: false,
12824 rank1_routed: false,
12825 ev_input: None,
12826 fence_flags_raw: 0,
12827 fence_ticket: 0,
12828 gate_out,
12829 up_out,
12830 act_q,
12831 act_d,
12832 sel,
12833 partial,
12834 accumulator,
12835 combine_w,
12836 route_w,
12837 in_q,
12838 in_d,
12839 dev_route_e: None,
12840 in_stage_e: None,
12841 out_stage_e: None,
12842 routes_graph: None,
12843 raw_dev_route_e: None,
12844 raw_combine: None,
12845 raw_input: Vec::new(),
12846 raw_sel: Vec::new(),
12847 raw_route_w: Vec::new(),
12848 remote: root.uninit(experts.input_width)?,
12849 combined: root.uninit(experts.input_width)?,
12850 n_sel,
12851 input,
12852 ev_rank,
12853 ev_done: Some(root.ctx().new_event(None)?),
12854 ev_entry: None,
12855 });
12856 }
12857 let workspace = workspace_guard
12858 .as_mut()
12859 .expect("NVFP4 device routes workspace initialized above");
12860 if experts.ep2 {
12863 return Ok(vec![0.0f32; experts.input_width]);
12864 }
12865 if workspace.n_sel != n_sel {
12866 return Err(format!(
12867 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12868 workspace.n_sel
12869 )
12870 .into());
12871 }
12872 for &expert in selected {
12873 if expert >= experts.expert_count {
12874 return Err(format!(
12875 "NVFP4 device selected expert {expert} outside 0..{}",
12876 experts.expert_count
12877 )
12878 .into());
12879 }
12880 }
12881 let sel_i32 = selected
12882 .iter()
12883 .map(|&expert| expert as i32)
12884 .collect::<Vec<_>>();
12885
12886 for (rank_index, engine) in self.ranks.iter().enumerate() {
12893 let _main = engine.gpu.enter_main()?;
12894 let device_input = engine.htod(input)?;
12895 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
12896 engine.quantize_q8_1_into(
12897 &device_input,
12898 1,
12899 experts.input_width,
12900 &mut in_q[rank_index],
12901 &mut in_d[rank_index],
12902 )?;
12903 }
12905 self.nvfp4_routes_batched_sweeps(
12906 experts,
12907 workspace,
12908 selected,
12909 route_weights,
12910 &sel_i32,
12911 local_out,
12912 n_sel,
12913 activation_limit,
12914 false,
12915 )?;
12916
12917 let root = &self.ranks[0];
12920 for engine in &self.ranks[1..] {
12921 let _main = engine.gpu.enter_main()?;
12922 engine.stream().synchronize()?;
12923 }
12924 let _main = root.gpu.enter_main()?;
12925 root.stream()
12926 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12927 root.add(
12928 &workspace.accumulator[0],
12929 &workspace.remote,
12930 &mut workspace.combined,
12931 experts.input_width,
12932 )?;
12933 let output = root.dtoh(&workspace.combined)?;
12934 if let Some(started) = started {
12935 use std::sync::atomic::Ordering;
12936 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12937 + started.elapsed().as_nanos() as u64;
12938 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12939 if calls.is_multiple_of(430) {
12940 eprintln!(
12941 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12942 ns as f64 / 1.0e6,
12943 ns as f64 / calls as f64 / 1.0e3,
12944 );
12945 }
12946 }
12947 Ok(output)
12948 }
12949
12950 #[allow(clippy::too_many_arguments)]
12955 fn nvfp4_routes_batched_sweeps(
12956 &self,
12957 experts: &ResidentNvfp4TensorParallel,
12958 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12959 selected: &[usize],
12960 route_weights: &[f32],
12961 sel_i32: &[i32],
12962 local_out: usize,
12963 n_sel: usize,
12964 activation_limit: Option<f32>,
12965 device_routed: bool,
12966 ) -> Result<(), Box<dyn std::error::Error>> {
12967 for rank_index in 0..self.ranks.len() {
12968 self.nvfp4_routes_batched_sweeps_rank(
12969 experts,
12970 workspace,
12971 selected,
12972 route_weights,
12973 sel_i32,
12974 local_out,
12975 n_sel,
12976 activation_limit,
12977 device_routed,
12978 rank_index,
12979 )?;
12980 }
12981 Ok(())
12982 }
12983
12984 #[allow(clippy::too_many_arguments)]
12987 fn nvfp4_routes_batched_sweeps_rank(
12988 &self,
12989 experts: &ResidentNvfp4TensorParallel,
12990 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12991 selected: &[usize],
12992 route_weights: &[f32],
12993 sel_i32: &[i32],
12994 local_out: usize,
12995 n_sel: usize,
12996 activation_limit: Option<f32>,
12997 device_routed: bool,
12998 rank_index: usize,
12999 ) -> Result<(), Box<dyn std::error::Error>> {
13000 {
13001 let engine = &self.ranks[rank_index];
13002 let _main = engine.gpu.enter_main()?;
13003 if experts.ep2 {
13008 if !device_routed {
13009 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
13010 }
13011 let gate_bank = &experts.gate[rank_index];
13012 let up_bank = &experts.up[rank_index];
13013 if gate_bank.local_out != experts.expert_width
13014 || gate_bank.expert_bytes != up_bank.expert_bytes
13015 {
13016 return Err("NVFP4 EP2 bank geometry drifted".into());
13017 }
13018 {
13019 let Nvfp4DeviceRoutesWorkspace {
13020 sel,
13021 gate_out,
13022 up_out,
13023 in_q,
13024 in_d,
13025 ..
13026 } = &mut *workspace;
13027 engine.qmatvec_nvfp4_sel_gu_ep_into(
13028 &gate_bank.bank,
13029 &up_bank.bank,
13030 &sel[rank_index],
13031 &in_q[rank_index],
13032 &in_d[rank_index],
13033 &mut gate_out[rank_index],
13034 &mut up_out[rank_index],
13035 n_sel,
13036 gate_bank.in_features,
13037 gate_bank.local_out,
13038 gate_bank.row_bytes,
13039 gate_bank.expert_bytes,
13040 rank_index,
13041 )?;
13042 }
13043 {
13044 let Nvfp4DeviceRoutesWorkspace {
13045 gate_out,
13046 up_out,
13047 sel,
13048 act_q,
13049 act_d,
13050 ..
13051 } = &mut *workspace;
13052 engine.silu_mul_scaled_q8_1_sel_ep_into(
13053 &gate_out[rank_index],
13054 &up_out[rank_index],
13055 &experts.macros_gate_dev[rank_index],
13056 &experts.macros_up_dev[rank_index],
13057 &sel[rank_index],
13058 activation_limit,
13059 &mut act_q[rank_index],
13060 &mut act_d[rank_index],
13061 local_out,
13062 n_sel,
13063 rank_index,
13064 )?;
13065 }
13066 let shard = &experts.down[rank_index];
13067 if shard.device_rank != rank_index || shard.local_in != local_out {
13068 return Err("NVFP4 EP2 down bank placement drifted".into());
13069 }
13070 {
13071 let Nvfp4DeviceRoutesWorkspace {
13072 sel,
13073 act_q,
13074 act_d,
13075 route_w,
13076 accumulator,
13077 ..
13078 } = &mut *workspace;
13079 engine.qmatvec_nvfp4_sel_down8_ep_into(
13080 &shard.bank,
13081 &sel[rank_index],
13082 &act_q[rank_index],
13083 &act_d[rank_index],
13084 &route_w[rank_index],
13085 &experts.macros_down_dev[rank_index],
13086 &mut accumulator[rank_index],
13087 n_sel,
13088 shard.local_in,
13089 shard.out_features,
13090 shard.row_bytes,
13091 shard.expert_bytes,
13092 local_out,
13093 local_out / 32,
13094 rank_index,
13095 )?;
13096 }
13097 return Ok(());
13098 }
13099 if !device_routed {
13100 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
13101 let folded = (0..n_sel)
13104 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
13105 .collect::<Vec<_>>();
13106 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
13107 engine.stream().memcpy_htod(&folded, &mut view)?;
13108 }
13109 let gate_bank = &experts.gate[rank_index];
13110 let up_bank = &experts.up[rank_index];
13111 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
13112 let gu_fused = sel_gu_fused_on()
13120 && gate_bank.slot_major
13121 && up_bank.slot_major
13122 && gate_bank.in_features == up_bank.in_features
13123 && gate_bank.local_out == up_bank.local_out
13124 && gate_bank.row_bytes == up_bank.row_bytes
13125 && gate_bank.expert_bytes == up_bank.expert_bytes;
13126 {
13132 static SEEN_GU: std::sync::Mutex<Vec<(bool, bool, bool)>> =
13133 std::sync::Mutex::new(Vec::new());
13134 let combo = (gu_fused, sel_gu_fused_on(), gate_bank.slot_major);
13135 let mut seen = SEEN_GU.lock().unwrap();
13136 if !seen.contains(&combo) {
13137 seen.push(combo);
13138 eprintln!(
13139 "[nvfp4-sweep] gu_fused={} door={} slot_major={} geometry_match={} \
13140 in_f={} out_f={} n_sel={n_sel}",
13141 gu_fused,
13142 sel_gu_fused_on(),
13143 gate_bank.slot_major,
13144 gate_bank.in_features == up_bank.in_features
13145 && gate_bank.local_out == up_bank.local_out
13146 && gate_bank.row_bytes == up_bank.row_bytes
13147 && gate_bank.expert_bytes == up_bank.expert_bytes,
13148 gate_bank.in_features,
13149 gate_bank.local_out
13150 );
13151 }
13152 }
13153 if gu_fused {
13154 let Nvfp4DeviceRoutesWorkspace {
13155 sel,
13156 gate_out,
13157 up_out,
13158 in_q,
13159 in_d,
13160 ..
13161 } = &mut *workspace;
13162 engine.qmatvec_nvfp4_sel_gu_into(
13163 &gate_bank.bank,
13164 &up_bank.bank,
13165 &sel[rank_index],
13166 &in_q[rank_index],
13167 &in_d[rank_index],
13168 &mut gate_out[rank_index],
13169 &mut up_out[rank_index],
13170 n_sel,
13171 gate_bank.in_features,
13172 gate_bank.local_out,
13173 gate_bank.row_bytes,
13174 gate_bank.expert_bytes,
13175 gate_bank.slot_major,
13176 )?;
13177 } else {
13178 engine.qmatvec_nvfp4_sel_into(
13179 &gate_bank.bank,
13180 &workspace.sel[rank_index],
13181 aq,
13182 ad,
13183 &mut workspace.gate_out[rank_index],
13184 n_sel,
13185 gate_bank.in_features,
13186 gate_bank.local_out,
13187 gate_bank.row_bytes,
13188 gate_bank.expert_bytes,
13189 0,
13190 0,
13191 gate_bank.slot_major,
13192 )?;
13193 engine.qmatvec_nvfp4_sel_into(
13194 &up_bank.bank,
13195 &workspace.sel[rank_index],
13196 aq,
13197 ad,
13198 &mut workspace.up_out[rank_index],
13199 n_sel,
13200 up_bank.in_features,
13201 up_bank.local_out,
13202 up_bank.row_bytes,
13203 up_bank.expert_bytes,
13204 0,
13205 0,
13206 up_bank.slot_major,
13207 )?;
13208 }
13209 {
13213 let Nvfp4DeviceRoutesWorkspace {
13214 gate_out,
13215 up_out,
13216 sel,
13217 act_q,
13218 act_d,
13219 ..
13220 } = &mut *workspace;
13221 engine.silu_mul_scaled_q8_1_sel_into(
13222 &gate_out[rank_index],
13223 &up_out[rank_index],
13224 &experts.macros_gate_dev[rank_index],
13225 &experts.macros_up_dev[rank_index],
13226 &sel[rank_index],
13227 activation_limit,
13228 &mut act_q[rank_index],
13229 &mut act_d[rank_index],
13230 local_out,
13231 n_sel,
13232 )?;
13233 }
13234 let shard = &experts.down[rank_index];
13235 if shard.device_rank != rank_index || shard.local_in != local_out {
13236 return Err(
13237 "NVFP4 device routes: down canonical shard placement drifted from \
13238 the gate/up column split"
13239 .into(),
13240 );
13241 }
13242 let down8 =
13250 device_routed && sel_down8_on() && shard.slot_major && (shard.local_in >> 5) <= 32;
13251 {
13256 static SEEN_D8: std::sync::Mutex<Vec<(bool, bool, bool, bool)>> =
13257 std::sync::Mutex::new(Vec::new());
13258 let combo = (down8, sel_down8_on(), device_routed, shard.slot_major);
13259 let mut seen = SEEN_D8.lock().unwrap();
13260 if !seen.contains(&combo) {
13261 seen.push(combo);
13262 eprintln!(
13268 "[nvfp4-sweep] down8={} door={} door_source={} device_routed={} \
13269 slot_major={} nsb={} in_class={} n_sel={n_sel}",
13270 down8,
13271 sel_down8_on(),
13272 sel_down8_source().1,
13273 device_routed,
13274 shard.slot_major,
13275 shard.local_in >> 5,
13276 (shard.local_in >> 5) <= 32
13277 );
13278 }
13279 }
13280 if down8 {
13281 let Nvfp4DeviceRoutesWorkspace {
13282 sel,
13283 act_q,
13284 act_d,
13285 route_w,
13286 accumulator,
13287 ..
13288 } = &mut *workspace;
13289 engine.qmatvec_nvfp4_sel_down8_into(
13290 &shard.bank,
13291 &sel[rank_index],
13292 &act_q[rank_index],
13293 &act_d[rank_index],
13294 &route_w[rank_index],
13295 &experts.macros_down_dev[rank_index],
13296 &mut accumulator[rank_index],
13297 n_sel,
13298 shard.local_in,
13299 shard.out_features,
13300 shard.row_bytes,
13301 shard.expert_bytes,
13302 local_out,
13303 local_out / 32,
13304 shard.slot_major,
13305 )?;
13306 } else {
13307 let Nvfp4DeviceRoutesWorkspace {
13308 sel,
13309 act_q,
13310 act_d,
13311 partial,
13312 ..
13313 } = &mut *workspace;
13314 engine.qmatvec_nvfp4_sel_into(
13315 &shard.bank,
13316 &sel[rank_index],
13317 &act_q[rank_index],
13318 &act_d[rank_index],
13319 &mut partial[rank_index],
13320 n_sel,
13321 shard.local_in,
13322 shard.out_features,
13323 shard.row_bytes,
13324 shard.expert_bytes,
13325 local_out,
13326 local_out / 32,
13327 shard.slot_major,
13328 )?;
13329 }
13330 if !down8 {
13335 let Nvfp4DeviceRoutesWorkspace {
13336 partial,
13337 combine_w,
13338 route_w,
13339 sel,
13340 accumulator,
13341 ..
13342 } = &mut *workspace;
13343 if device_routed {
13344 engine.axpy_rows_seq_md_into(
13345 &partial[rank_index],
13346 &route_w[rank_index],
13347 &experts.macros_down_dev[rank_index],
13348 &sel[rank_index],
13349 &mut accumulator[rank_index],
13350 experts.input_width,
13351 n_sel,
13352 )?;
13353 } else {
13354 engine.axpy_rows_seq_into(
13355 &partial[rank_index],
13356 &combine_w[rank_index],
13357 &mut accumulator[rank_index],
13358 experts.input_width,
13359 n_sel,
13360 )?;
13361 }
13362 }
13363 }
13364 Ok(())
13365 }
13366
13367 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_io(
13376 &self,
13377 experts: &ResidentNvfp4TensorParallel,
13378 e: &Engine,
13379 input_dev: &crate::CudaSlice<f32>,
13380 selected: &[usize],
13381 route_weights: &[f32],
13382 experts_per_token: usize,
13383 activation_limit: Option<f32>,
13384 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13385 if input_dev.len() != experts.input_width {
13386 return Err(format!(
13387 "NVFP4 device-io routes input {} != width {}",
13388 input_dev.len(),
13389 experts.input_width
13390 )
13391 .into());
13392 }
13393 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
13394 return Err(format!(
13395 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
13396 selected.len(),
13397 route_weights.len(),
13398 )
13399 .into());
13400 }
13401 if !route_weights.iter().all(|weight| weight.is_finite()) {
13402 return Err("NVFP4 device route weights contain a non-finite value".into());
13403 }
13404 let world = self.ranks.len();
13405 if world != NVFP4_CANONICAL_ROW_SHARDS {
13406 return Err(format!(
13407 "NVFP4 device routes require world == canonical shard grid \
13408 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
13409 )
13410 .into());
13411 }
13412 let local_out = experts.expert_width / world;
13413 let n_sel = experts_per_token;
13414
13415 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13416 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13417 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
13418 let started = timing.then(std::time::Instant::now);
13419
13420 let mut workspace_guard = experts
13421 .device_workspace
13422 .lock()
13423 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13424 if workspace_guard.is_none() {
13425 drop(workspace_guard);
13426 let zero = vec![0.0f32; experts.input_width];
13429 let zero_sel = vec![0usize; n_sel];
13430 let zero_w = vec![0.0f32; n_sel];
13431 let _ = self.run_tensor_parallel_routes_nvfp4_device(
13432 experts,
13433 &zero,
13434 &zero_sel,
13435 &zero_w,
13436 n_sel,
13437 activation_limit,
13438 )?;
13439 workspace_guard = experts
13440 .device_workspace
13441 .lock()
13442 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13443 }
13444 let workspace = workspace_guard
13445 .as_mut()
13446 .expect("NVFP4 device routes workspace initialized above");
13447 if workspace.n_sel != n_sel {
13448 return Err(format!(
13449 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
13450 workspace.n_sel
13451 )
13452 .into());
13453 }
13454 for &expert in selected {
13455 if expert >= experts.expert_count {
13456 return Err(format!(
13457 "NVFP4 device selected expert {expert} outside 0..{}",
13458 experts.expert_count
13459 )
13460 .into());
13461 }
13462 }
13463 let sel_i32 = selected
13464 .iter()
13465 .map(|&expert| expert as i32)
13466 .collect::<Vec<_>>();
13467
13468 if let Some((_, device)) = workspace.ev_entry.as_ref() {
13472 if *device != e.ctx().ordinal() {
13473 return Err("NVFP4 device-io routes engine changed".into());
13474 }
13475 } else {
13476 let _main = e.gpu.enter_main()?;
13477 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13478 }
13479 {
13480 let _main = e.gpu.enter_main()?;
13481 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13482 ev_entry.record(&e.stream())?;
13483 }
13484 for (rank_index, engine) in self.ranks.iter().enumerate() {
13485 let _main = engine.gpu.enter_main()?;
13486 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13487 engine.stream().wait(ev_entry)?;
13488 {
13489 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13490 engine
13491 .stream()
13492 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13493 }
13494 {
13495 let Nvfp4DeviceRoutesWorkspace {
13496 input, in_q, in_d, ..
13497 } = &mut *workspace;
13498 engine.quantize_q8_1_into(
13499 &input[rank_index],
13500 1,
13501 experts.input_width,
13502 &mut in_q[rank_index],
13503 &mut in_d[rank_index],
13504 )?;
13505 }
13506 }
13507 self.nvfp4_routes_batched_sweeps(
13508 experts,
13509 workspace,
13510 selected,
13511 route_weights,
13512 &sel_i32,
13513 local_out,
13514 n_sel,
13515 activation_limit,
13516 false,
13517 )?;
13518
13519 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
13525 let _main = engine.gpu.enter_main()?;
13526 workspace.ev_rank[rank_index].record(&engine.stream())?;
13527 }
13528 if moe_direct_on() && self.ranks.len() == 2 {
13529 {
13536 let root = &self.ranks[0];
13537 let _main = root.gpu.enter_main()?;
13538 workspace
13539 .ev_done
13540 .as_ref()
13541 .expect("device routes done event")
13542 .record(&root.stream())?;
13543 }
13544 let _main = e.gpu.enter_main()?;
13545 e.stream().wait(
13546 workspace
13547 .ev_done
13548 .as_ref()
13549 .expect("device routes done event"),
13550 )?;
13551 for ev in workspace.ev_rank.iter().skip(1) {
13552 e.stream().wait(ev)?;
13553 }
13554 let mut output = e.uninit(experts.input_width)?;
13555 e.add(
13556 &workspace.accumulator[0],
13557 &workspace.accumulator[1],
13558 &mut output,
13559 experts.input_width,
13560 )?;
13561 let output = output;
13562 if let Some(started) = started {
13563 use std::sync::atomic::Ordering;
13564 let ns = TIMING_NS
13565 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13566 + started.elapsed().as_nanos() as u64;
13567 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13568 if calls.is_multiple_of(430) {
13569 eprintln!(
13570 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13571 ns as f64 / 1.0e6,
13572 ns as f64 / calls as f64 / 1.0e3,
13573 );
13574 }
13575 }
13576 return Ok(output);
13577 }
13578 {
13579 let root = &self.ranks[0];
13580 let _main = root.gpu.enter_main()?;
13581 for ev in workspace.ev_rank.iter().skip(1) {
13582 root.stream().wait(ev)?;
13583 }
13584 root.stream()
13585 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
13586 {
13587 let Nvfp4DeviceRoutesWorkspace {
13588 accumulator,
13589 remote,
13590 combined,
13591 ..
13592 } = &mut *workspace;
13593 root.add(&accumulator[0], remote, combined, experts.input_width)?;
13594 }
13595 workspace
13596 .ev_done
13597 .as_ref()
13598 .expect("device routes done event")
13599 .record(&root.stream())?;
13600 }
13601 let output = {
13602 let _main = e.gpu.enter_main()?;
13603 e.stream().wait(
13604 workspace
13605 .ev_done
13606 .as_ref()
13607 .expect("device routes done event"),
13608 )?;
13609 let mut output = e.uninit(experts.input_width)?;
13612 e.stream().memcpy_dtod(
13613 &workspace.combined.slice(0..experts.input_width),
13614 &mut output.slice_mut(0..experts.input_width),
13615 )?;
13616 output
13617 };
13618 if let Some(started) = started {
13619 use std::sync::atomic::Ordering;
13620 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13621 + started.elapsed().as_nanos() as u64;
13622 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13623 if calls.is_multiple_of(430) {
13624 eprintln!(
13625 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13626 ns as f64 / 1.0e6,
13627 ns as f64 / calls as f64 / 1.0e3,
13628 );
13629 }
13630 }
13631 Ok(output)
13632 }
13633
13634 #[allow(clippy::too_many_arguments)]
13640 pub fn nvfp4_routes_prestage(
13645 &self,
13646 experts: &ResidentNvfp4TensorParallel,
13647 e: &Engine,
13648 input_dev: &crate::CudaSlice<f32>,
13649 ) -> Result<bool, Box<dyn std::error::Error>> {
13650 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
13651 }
13652
13653 pub fn nvfp4_routes_prestage_with(
13659 &self,
13660 experts: &ResidentNvfp4TensorParallel,
13661 e: &Engine,
13662 input_dev: &crate::CudaSlice<f32>,
13663 rank1_router: impl FnOnce(
13664 &Engine,
13665 &crate::CudaSlice<f32>,
13666 &mut crate::CudaSlice<i32>,
13667 &mut crate::CudaSlice<f32>,
13668 ) -> Result<bool, Box<dyn std::error::Error>>,
13669 ) -> Result<bool, Box<dyn std::error::Error>> {
13670 if !routes_prestage_on() || step_tp_graph_enabled()? {
13671 return Ok(false);
13672 }
13673 if input_dev.len() != experts.input_width {
13674 return Err("NVFP4 prestage input width mismatch".into());
13675 }
13676 let mut workspace_guard = experts
13677 .device_workspace
13678 .lock()
13679 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13680 let Some(workspace) = workspace_guard.as_mut() else {
13681 return Ok(false);
13682 };
13683 if workspace.ev_input.is_none() {
13684 let _main = e.gpu.enter_main()?;
13685 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13686 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
13687 return Err("NVFP4 prestage engine changed".into());
13688 }
13689 {
13690 let _main = e.gpu.enter_main()?;
13691 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13692 ev.record(&e.stream())?;
13693 }
13694 for (rank_index, engine) in self.ranks.iter().enumerate() {
13695 let _main = engine.gpu.enter_main()?;
13696 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13697 engine.stream().wait(ev)?;
13698 {
13699 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13700 engine
13701 .stream()
13702 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13703 }
13704 {
13705 let Nvfp4DeviceRoutesWorkspace {
13706 input, in_q, in_d, ..
13707 } = &mut *workspace;
13708 engine.quantize_q8_1_into(
13709 &input[rank_index],
13710 1,
13711 experts.input_width,
13712 &mut in_q[rank_index],
13713 &mut in_d[rank_index],
13714 )?;
13715 }
13716 }
13717 if self.ranks.len() == 2 {
13718 let rank1 = &self.ranks[1];
13719 let _r1 = rank1.gpu.enter_main()?;
13720 let Nvfp4DeviceRoutesWorkspace {
13721 input,
13722 sel,
13723 route_w,
13724 ..
13725 } = &mut *workspace;
13726 let (in1, rest_sel) = (&input[1], &mut sel[1]);
13727 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
13728 workspace.rank1_routed = true;
13729 }
13730 }
13731 workspace.prestaged = true;
13732 Ok(true)
13733 }
13734
13735 #[allow(clippy::too_many_arguments)]
13752 fn determ_stage_bytes(v: &[u8]) -> u64 {
13761 v.iter().fold(0u64, |a, b| {
13762 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
13763 })
13764 }
13765
13766 fn determ_stage_i32(v: &[i32]) -> u64 {
13771 v.iter().fold(0u64, |a, b| {
13772 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
13773 })
13774 }
13775
13776 fn determ_stage_sum(v: &[f32]) -> u64 {
13777 v.iter().fold(0u64, |a, x| {
13778 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
13779 })
13780 }
13781
13782 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
13784 &self,
13785 experts: &ResidentNvfp4TensorParallel,
13786 e: &Engine,
13787 z_t: &crate::CudaSlice<f32>,
13788 t: usize,
13789 sel: &[i32],
13790 w: &[f32],
13791 n_used: usize,
13792 activation_limit: Option<f32>,
13793 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13794 let world = self.ranks.len();
13795 if world != NVFP4_CANONICAL_ROW_SHARDS {
13796 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
13797 }
13798 let slot_major = experts.gate.iter().all(|b| b.slot_major)
13812 && experts.up.iter().all(|b| b.slot_major)
13813 && experts.down.iter().all(|b| b.slot_major);
13814 let any_slot_major = experts.gate.iter().any(|b| b.slot_major)
13815 || experts.up.iter().any(|b| b.slot_major)
13816 || experts.down.iter().any(|b| b.slot_major);
13817 if any_slot_major != slot_major {
13818 return Err(
13819 "NVFP4 grouped prime: gate/up/down banks disagree on the row layout — \
13820 one grouped GEMM cannot serve two byte maps"
13821 .into(),
13822 );
13823 }
13824 let bank_qt = if slot_major {
13825 crate::QT_NVFP4_V2
13826 } else {
13827 crate::QT_NVFP4
13828 };
13829 let width = experts.input_width;
13830 let n_expert = experts.expert_count;
13831 let n_pairs = t * n_used;
13832 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
13833 return Err("NVFP4 grouped prime geometry".into());
13834 }
13835 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
13844 let g_t0 = std::time::Instant::now();
13845 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
13848 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
13849 let s_id = s_id as usize;
13850 if s_id >= n_expert {
13851 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
13852 }
13853 buckets[s_id].push(p as i32);
13854 }
13855 let mut ex_ids: Vec<i32> = Vec::new();
13856 let mut ex_off: Vec<i32> = vec![0];
13857 let mut ex_pairs: Vec<i32> = Vec::new();
13858 for (e_id, b) in buckets.iter().enumerate() {
13859 if !b.is_empty() {
13860 ex_ids.push(e_id as i32);
13861 ex_pairs.extend_from_slice(b);
13862 ex_off.push(ex_pairs.len() as i32);
13863 }
13864 }
13865 let n_active = ex_ids.len();
13866 if n_active == 0 {
13867 return e.zeros(t * width);
13868 }
13869 if n_active > 512 {
13870 return Err("grouped prime n_active > 512 (direct lane cap)".into());
13871 }
13872 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
13873 let mut inv = vec![0i32; n_pairs];
13876 for (row, &pair) in ex_pairs.iter().enumerate() {
13877 inv[pair as usize] = row as i32;
13878 }
13879 let mg: Vec<f32> = ex_pairs
13881 .iter()
13882 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
13883 .collect();
13884 let mu: Vec<f32> = ex_pairs
13885 .iter()
13886 .map(|&p| experts.macros_up[sel[p as usize] as usize])
13887 .collect();
13888 let wd: Vec<f32> = (0..n_pairs)
13889 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
13890 .collect();
13891 {
13895 let mut tabs = experts
13896 .prime_tables
13897 .lock()
13898 .map_err(|_| "grouped prime table cache is poisoned")?;
13899 if tabs.len() != world {
13900 tabs.clear();
13901 for rank in 0..world {
13902 let engine = &self.ranks[rank];
13903 let _main = engine.gpu.enter_main()?;
13904 let (gb, ub, db) =
13905 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
13906 let mut tab = vec![0u64; 3 * n_expert];
13907 {
13908 use cudarc::driver::DevicePtr;
13909 let stream = engine.stream();
13910 let (pg, _g0) = gb.bank.device_ptr(&stream);
13911 let (pu, _g1) = ub.bank.device_ptr(&stream);
13912 let (pd, _g2) = db.bank.device_ptr(&stream);
13913 for ex in 0..n_expert {
13914 tab[ex] = pg + (ex * gb.expert_bytes) as u64;
13915 tab[n_expert + ex] = pu + (ex * ub.expert_bytes) as u64;
13916 tab[2 * n_expert + ex] = pd + (ex * db.expert_bytes) as u64;
13917 }
13918 }
13919 tabs.push(engine.htod_u64(&tab)?);
13920 }
13921 }
13922 }
13923 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
13924 let g_t1 = std::time::Instant::now();
13925 {
13932 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
13933 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13934 for rank in 0..world {
13935 let e_r = &self.ranks[rank];
13936 let _m = e_r.gpu.enter_main();
13937 eprintln!(
13938 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
13939 root_stream={:?}",
13940 e_r.ctx().ordinal(),
13941 std::sync::Arc::as_ptr(e_r.ctx()),
13942 e_r.stream().cu_stream(),
13943 e.ctx().ordinal(),
13944 e.stream().cu_stream(),
13945 );
13946 }
13947 }
13948 }
13949
13950 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
13951 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
13952 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
13953 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
13954 for rank in 0..world {
13955 let engine = &self.ranks[rank];
13956 let _main = engine.gpu.enter_main()?;
13957 if gprof {
13958 let h = engine
13963 .ctx()
13964 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13965 h.record(&engine.stream())?;
13966 ev_head.push(h);
13967 }
13968 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
13971 let gb = &experts.gate[rank];
13972 let ub = &experts.up[rank];
13973 let db = &experts.down[rank];
13974 if db.device_rank != rank {
13975 return Err("grouped prime: down shard placement drifted".into());
13976 }
13977 let local_ff = gb.local_out;
13978 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
13979 return Err("grouped prime: bank width mismatch".into());
13980 }
13981 let csr_tok_d = engine.htod_i32(&csr_tok)?;
13984 let exi_d = engine.htod_i32(&ex_ids)?;
13985 let exoff_d = engine.htod_i32(&ex_off)?;
13986 let mg_d = engine.htod(&mg)?;
13987 let mu_d = engine.htod(&mu)?;
13988 let tabs_guard = experts
13990 .prime_tables
13991 .lock()
13992 .map_err(|_| "grouped prime table cache is poisoned")?;
13993 let tab_d = &tabs_guard[rank];
13994 let mut z_r = engine.uninit(t * width)?;
13995 {
13996 let mut dst = z_r.slice_mut(0..t * width);
13997 engine
13998 .stream()
13999 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
14000 }
14001 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
14002 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
14003 if dstage {
14004 let zr = engine.dtoh(&z_r)?;
14008 let zsv = engine.dtoh(&zs)?;
14009 let z16v = engine.dtoh_u8(&z16)?;
14010 eprintln!(
14011 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
14012 Self::determ_stage_sum(&zr),
14013 Self::determ_stage_sum(&zsv),
14014 Self::determ_stage_bytes(&z16v)
14015 );
14016 }
14017 if dstage {
14018 engine.stream().synchronize()?;
14026 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
14027 let exi_v = engine.dtoh_i32(&exi_d)?;
14028 let exo_v = engine.dtoh_i32(&exoff_d)?;
14029 let mg_v = engine.dtoh(&mg_d)?;
14030 let mu_v = engine.dtoh(&mu_d)?;
14031 let tab_v = engine.dtoh_u64(tab_d)?;
14032 eprintln!(
14033 "[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={}",
14034 Self::determ_stage_i32(&csr_v),
14035 Self::determ_stage_i32(&exi_v),
14036 Self::determ_stage_i32(&exo_v),
14037 Self::determ_stage_i32(&ex_off),
14038 Self::determ_stage_sum(&mg_v),
14039 Self::determ_stage_sum(&mu_v),
14040 tab_v
14041 .iter()
14042 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
14043 gb.row_bytes
14044 );
14045 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
14048 let bank_v = engine.dtoh_u8(&gb.bank)?;
14049 eprintln!(
14050 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
14051 Self::determ_stage_bytes(&bank_v),
14052 bank_v.len()
14053 );
14054 }
14055 }
14056 let mut g = engine.moe_f16_grouped(
14057 tab_d,
14058 0,
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 gb.row_bytes,
14071 )?;
14072 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
14073 let mut u = engine.moe_f16_grouped(
14074 tab_d,
14075 1,
14076 n_expert,
14077 &exi_d,
14078 &ex_off,
14079 &exoff_d,
14080 &z16,
14081 &zs,
14082 width,
14083 local_ff,
14084 n_active,
14085 n_pairs,
14086 bank_qt,
14087 ub.row_bytes,
14088 )?;
14089 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
14090 let act = match activation_limit.filter(|l| *l > 1e-6) {
14094 Some(lim) => {
14095 let mut a = engine.uninit(n_pairs * local_ff)?;
14096 engine.swiglu_clamped_mul_scaled(
14097 &g,
14098 &u,
14099 1.0,
14100 1.0,
14101 lim,
14102 &mut a,
14103 n_pairs * local_ff,
14104 )?;
14105 a
14106 }
14107 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
14108 };
14109 if dstage {
14110 let gv = engine.dtoh(&g)?;
14111 let uv = engine.dtoh(&u)?;
14112 let av = engine.dtoh(&act)?;
14113 let key = (rank, t);
14118 let mut prev_map = DETERM_PREV
14119 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
14120 .lock()
14121 .map_err(|_| "determ prev map poisoned")?;
14122 let shape = match prev_map.get(&key) {
14123 Some(prev) if prev.len() == gv.len() => {
14124 let mut md = 0.0f32;
14125 let mut n_diff = 0usize;
14126 let mut n_big = 0usize;
14127 for (a, b) in prev.iter().zip(gv.iter()) {
14128 let d = (a - b).abs();
14129 if d > 0.0 {
14130 n_diff += 1;
14131 }
14132 if d > 1e-3 {
14133 n_big += 1;
14134 }
14135 if d > md {
14136 md = d;
14137 }
14138 }
14139 format!(
14140 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
14141 gv.len()
14142 )
14143 }
14144 _ => String::new(),
14145 };
14146 prev_map.insert(key, gv.clone());
14147 drop(prev_map);
14148 eprintln!(
14149 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
14150 Self::determ_stage_sum(&gv),
14151 Self::determ_stage_sum(&uv),
14152 Self::determ_stage_sum(&av)
14153 );
14154 }
14155 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
14156 let d_csr = engine.moe_f16_grouped(
14157 tab_d,
14158 2,
14159 n_expert,
14160 &exi_d,
14161 &ex_off,
14162 &exoff_d,
14163 &a16,
14164 &a_s,
14165 local_ff,
14166 width,
14167 n_active,
14168 n_pairs,
14169 bank_qt,
14170 db.row_bytes,
14171 )?;
14172
14173 if dstage {
14176 engine.stream().synchronize()?;
14177 let a16v = engine.dtoh_u8(&a16)?;
14178 let dv = engine.dtoh(&d_csr)?;
14179 eprintln!(
14180 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
14181 Self::determ_stage_bytes(&a16v),
14182 Self::determ_stage_sum(&dv)
14183 );
14184 }
14185 let ev = engine.ctx().new_event(None)?;
14186 ev.record(&engine.stream())?;
14187 if gprof {
14188 let tp = engine
14197 .ctx()
14198 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
14199 tp.record(&engine.stream())?;
14200 ev_tail_prof.push(tp);
14201 }
14202 ev_rank.push(ev);
14203 partials.push(d_csr);
14204 }
14205 let _main = e.gpu.enter_main()?;
14206 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
14207 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
14209 let g_t2 = std::time::Instant::now();
14210 for ev in &ev_rank {
14211 e.stream().wait(ev)?;
14212 }
14213 let mut y0 = e.uninit(n_pairs * width)?;
14216 {
14217 let mut dst = y0.slice_mut(0..n_pairs * width);
14218 e.stream()
14219 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
14220 }
14221 let mut y1 = e.uninit(n_pairs * width)?;
14222 {
14223 let mut dst = y1.slice_mut(0..n_pairs * width);
14224 e.stream()
14225 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
14226 }
14227 let inv_d = e.htod_i32(&inv)?;
14228 let wd_d = e.htod(&wd)?;
14229 let mut out = e.uninit(t * width)?;
14230 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
14231 if gprof {
14232 let _ = e.stream().synchronize();
14233 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
14234 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
14242 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
14243 let guard = self.ranks[rank].gpu.enter_main();
14244 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
14245 Ok(v) => span_ms.push(v),
14246 Err(err) => {
14247 static SAID: std::sync::atomic::AtomicBool =
14248 std::sync::atomic::AtomicBool::new(false);
14249 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
14250 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
14251 }
14252 span_ms.push(-1.0);
14253 }
14254 }
14255 }
14256 eprintln!(
14257 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
14258 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
14259 span_ms.iter().sum::<f32>(),
14260 span_ms.iter().cloned().fold(0.0f32, f32::max)
14261 );
14262 }
14263 Ok(out)
14264 }
14265
14266 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_routed(
14268 &self,
14269 experts: &ResidentNvfp4TensorParallel,
14270 e: &Engine,
14271 input_dev: &crate::CudaSlice<f32>,
14272 sel_d: &crate::CudaSlice<i32>,
14273 w_d: &crate::CudaSlice<f32>,
14274 experts_per_token: usize,
14275 activation_limit: Option<f32>,
14276 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
14277 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
14278 experts,
14279 e,
14280 input_dev,
14281 sel_d,
14282 w_d,
14283 experts_per_token,
14284 activation_limit,
14285 || Ok(()),
14286 )
14287 }
14288
14289 #[allow(clippy::too_many_arguments)]
14295 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
14296 &self,
14297 experts: &ResidentNvfp4TensorParallel,
14298 e: &Engine,
14299 input_dev: &crate::CudaSlice<f32>,
14300 sel_d: &crate::CudaSlice<i32>,
14301 w_d: &crate::CudaSlice<f32>,
14302 experts_per_token: usize,
14303 activation_limit: Option<f32>,
14304 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
14305 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
14306 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
14307 experts,
14308 e,
14309 input_dev,
14310 sel_d,
14311 w_d,
14312 experts_per_token,
14313 activation_limit,
14314 pre_join,
14315 None,
14316 )
14317 }
14318
14319 #[allow(clippy::too_many_arguments)]
14324 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
14325 &self,
14326 experts: &ResidentNvfp4TensorParallel,
14327 e: &Engine,
14328 input_dev: &crate::CudaSlice<f32>,
14329 sel_d: &crate::CudaSlice<i32>,
14330 w_d: &crate::CudaSlice<f32>,
14331 experts_per_token: usize,
14332 activation_limit: Option<f32>,
14333 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
14334 post_add: Option<(u64, u64)>,
14335 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
14336 if input_dev.len() != experts.input_width {
14337 return Err(format!(
14338 "NVFP4 device-routed input {} != width {}",
14339 input_dev.len(),
14340 experts.input_width
14341 )
14342 .into());
14343 }
14344 let n_sel = experts_per_token;
14345 if sel_d.len() < n_sel || w_d.len() < n_sel {
14346 return Err(format!(
14347 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
14348 sel_d.len(),
14349 w_d.len()
14350 )
14351 .into());
14352 }
14353 let world = self.ranks.len();
14354 if world != NVFP4_CANONICAL_ROW_SHARDS {
14355 return Err(format!(
14356 "NVFP4 device routes require world == canonical shard grid \
14357 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
14358 )
14359 .into());
14360 }
14361 let local_out = if experts.ep2 {
14362 experts.expert_width
14363 } else {
14364 experts.expert_width / world
14365 };
14366
14367 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
14368 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
14369 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
14370 let started = timing.then(std::time::Instant::now);
14371
14372 let mut workspace_guard = experts
14373 .device_workspace
14374 .lock()
14375 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
14376 if workspace_guard.is_none() {
14377 drop(workspace_guard);
14378 let zero = vec![0.0f32; experts.input_width];
14379 let zero_sel = vec![0usize; n_sel];
14380 let zero_w = vec![0.0f32; n_sel];
14381 let _ = self.run_tensor_parallel_routes_nvfp4_device(
14382 experts,
14383 &zero,
14384 &zero_sel,
14385 &zero_w,
14386 n_sel,
14387 activation_limit,
14388 )?;
14389 workspace_guard = experts
14390 .device_workspace
14391 .lock()
14392 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
14393 }
14394 let workspace = workspace_guard
14395 .as_mut()
14396 .expect("NVFP4 device routes workspace initialized above");
14397 if workspace.n_sel != n_sel {
14398 return Err(format!(
14399 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
14400 workspace.n_sel
14401 )
14402 .into());
14403 }
14404
14405 if step_tp_graph_enabled()? && step_tp_graph_headroom_ok(e) {
14416 if experts.ep2 {
14417 return Err(
14418 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
14419 co-gated; unset one"
14420 .into(),
14421 );
14422 }
14423 if workspace.dev_route_e.is_none() {
14424 let _main = e.gpu.enter_main()?;
14425 workspace.dev_route_e = Some((
14426 e.htod_i32(&vec![0i32; n_sel])?,
14427 e.htod(&vec![0.0f32; n_sel])?,
14428 ));
14429 }
14430 if workspace.in_stage_e.is_none() {
14431 let _main = e.gpu.enter_main()?;
14432 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14433 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14434 }
14435 if workspace.routes_graph.is_none() {
14436 let graph = self.nvfp4_routes_build_graph(
14437 experts,
14438 workspace,
14439 local_out,
14440 n_sel,
14441 activation_limit,
14442 )?;
14443 workspace.routes_graph = Some(graph);
14444 eprintln!(
14445 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
14446 children=3 updates=none performance_claim=false"
14447 );
14448 }
14449 let output = {
14450 let _main = e.gpu.enter_main()?;
14451 {
14452 let (sel_e, w_e) = workspace
14453 .dev_route_e
14454 .as_mut()
14455 .expect("device route staging set above");
14456 {
14457 let mut dst = sel_e.slice_mut(0..n_sel);
14458 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14459 }
14460 {
14461 let mut dst = w_e.slice_mut(0..n_sel);
14462 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14463 }
14464 }
14465 {
14466 let in_stage = workspace
14467 .in_stage_e
14468 .as_mut()
14469 .expect("graph staging set above");
14470 let mut dst = in_stage.slice_mut(0..experts.input_width);
14471 e.stream()
14472 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
14473 }
14474 unsafe {
14475 let r = cudarc::driver::sys::cuGraphLaunch(
14476 workspace
14477 .routes_graph
14478 .as_ref()
14479 .expect("routes graph built above")
14480 .exec,
14481 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
14482 );
14483 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
14484 return Err(format!("routes graph launch: {r:?}").into());
14485 }
14486 }
14487 let mut output = e.uninit(experts.input_width)?;
14488 {
14489 let out_stage = workspace
14490 .out_stage_e
14491 .as_ref()
14492 .expect("graph staging set above");
14493 e.stream().memcpy_dtod(
14494 &out_stage.slice(0..experts.input_width),
14495 &mut output.slice_mut(0..experts.input_width),
14496 )?;
14497 }
14498 output
14499 };
14500 if let Some(started) = started {
14501 use std::sync::atomic::Ordering;
14502 let ns = TIMING_NS
14503 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14504 + started.elapsed().as_nanos() as u64;
14505 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14506 if calls.is_multiple_of(430) {
14507 eprintln!(
14508 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14509 ns as f64 / 1.0e6,
14510 ns as f64 / calls as f64 / 1.0e3,
14511 );
14512 }
14513 }
14514 return Ok(output);
14515 }
14516
14517 if let Some((_, device)) = workspace.ev_entry.as_ref() {
14521 if *device != e.ctx().ordinal() {
14522 return Err("NVFP4 device-routed routes engine changed".into());
14523 }
14524 } else {
14525 let _main = e.gpu.enter_main()?;
14526 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
14527 }
14528 if workspace.dev_route_e.is_none() {
14529 let _main = e.gpu.enter_main()?;
14530 workspace.dev_route_e = Some((
14531 e.htod_i32(&vec![0i32; n_sel])?,
14532 e.htod(&vec![0.0f32; n_sel])?,
14533 ));
14534 }
14535 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
14541 let e_device = e.ctx().ordinal();
14542 let rank1_routed_peek = workspace.rank1_routed;
14544 let stage_needed = !mirror
14545 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
14546 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
14547 });
14548 {
14549 let _main = e.gpu.enter_main()?;
14550 if stage_needed {
14551 let (sel_e, w_e) = workspace
14552 .dev_route_e
14553 .as_mut()
14554 .expect("device route staging set above");
14555 {
14556 let mut dst = sel_e.slice_mut(0..n_sel);
14557 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14558 }
14559 {
14560 let mut dst = w_e.slice_mut(0..n_sel);
14561 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14562 }
14563 }
14564 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14565 ev_entry.record(&e.stream())?;
14566 }
14567 let prestaged = std::mem::take(&mut workspace.prestaged);
14570 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
14571 for (rank_index, engine) in self.ranks.iter().enumerate() {
14572 let _main = engine.gpu.enter_main()?;
14573 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14574 engine.stream().wait(ev_entry)?;
14575 if !prestaged {
14576 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
14577 engine
14578 .stream()
14579 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
14580 }
14581 if !(rank1_routed && rank_index == 1) {
14582 let same_dev = engine.ctx().ordinal() == e_device;
14586 if mirror {
14587 let Nvfp4DeviceRoutesWorkspace {
14590 sel,
14591 route_w,
14592 dev_route_e,
14593 ..
14594 } = &mut *workspace;
14595 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
14596 if same_dev {
14597 (sel_d, w_d)
14598 } else {
14599 let (sel_e, w_e) = dev_route_e
14600 .as_ref()
14601 .expect("device route staging set above");
14602 (sel_e, w_e)
14603 };
14604 engine.moe_sel_w_mirror(
14605 src_sel,
14606 src_w,
14607 &mut sel[rank_index],
14608 &mut route_w[rank_index],
14609 n_sel,
14610 )?;
14611 } else {
14612 let (sel_e, w_e) = workspace
14613 .dev_route_e
14614 .as_ref()
14615 .expect("device route staging set above");
14616 {
14617 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
14618 engine
14619 .stream()
14620 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
14621 }
14622 {
14623 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
14624 engine
14625 .stream()
14626 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
14627 }
14628 }
14629 }
14630 if !prestaged {
14631 let Nvfp4DeviceRoutesWorkspace {
14632 input, in_q, in_d, ..
14633 } = &mut *workspace;
14634 engine.quantize_q8_1_into(
14635 &input[rank_index],
14636 1,
14637 experts.input_width,
14638 &mut in_q[rank_index],
14639 &mut in_d[rank_index],
14640 )?;
14641 }
14642 }
14643 self.nvfp4_routes_batched_sweeps(
14644 experts,
14645 workspace,
14646 &[],
14647 &[],
14648 &[],
14649 local_out,
14650 n_sel,
14651 activation_limit,
14652 true,
14653 )?;
14654
14655 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
14658 let _main = engine.gpu.enter_main()?;
14659 workspace.ev_rank[rank_index].record(&engine.stream())?;
14660 }
14661 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
14664 let mut ticket = 0u32;
14665 if memops {
14666 use cudarc::driver::sys;
14667 if workspace.fence_flags_raw == 0 {
14668 let root = &self.ranks[0];
14669 let _main = root.gpu.enter_main()?;
14670 let mut ptr: sys::CUdeviceptr = 0;
14671 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
14672 if r != sys::CUresult::CUDA_SUCCESS {
14673 return Err(format!("fence flag alloc: {r:?}").into());
14674 }
14675 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
14676 if r != sys::CUresult::CUDA_SUCCESS {
14677 return Err(format!("fence flag memset: {r:?}").into());
14678 }
14679 workspace.fence_flags_raw = ptr as u64;
14680 }
14681 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
14682 ticket = workspace.fence_ticket;
14683 let base = workspace.fence_flags_raw;
14684 if fence_rank1_on() {
14690 let peer = &self.ranks[1];
14691 let _pmain = peer.gpu.enter_main()?;
14692 peer.ring_flag_raw(base, ticket)?;
14693 }
14694 {
14695 let root = &self.ranks[0];
14696 let _main = root.gpu.enter_main()?;
14697 let r = unsafe {
14698 sys::cuStreamWriteValue32_v2(
14699 root.stream().cu_stream() as sys::CUstream,
14700 (base + 4) as sys::CUdeviceptr,
14701 ticket,
14702 0,
14703 )
14704 };
14705 if r != sys::CUresult::CUDA_SUCCESS {
14706 return Err(format!("fence write root: {r:?}").into());
14707 }
14708 }
14709 }
14710 pre_join()?;
14713
14714 if moe_direct_on() && self.ranks.len() == 2 {
14715 let _main = e.gpu.enter_main()?;
14722 if memops {
14723 use cudarc::driver::sys;
14724 let base = workspace.fence_flags_raw;
14725 let r = unsafe {
14726 sys::cuStreamWaitValue32_v2(
14727 e.stream().cu_stream() as sys::CUstream,
14728 (base + 4) as sys::CUdeviceptr,
14729 ticket,
14730 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14731 )
14732 };
14733 if r != sys::CUresult::CUDA_SUCCESS {
14734 return Err(format!("fence wait: {r:?}").into());
14735 }
14736 if fence_rank1_on() {
14737 let r = unsafe {
14739 sys::cuStreamWaitValue32_v2(
14740 e.stream().cu_stream() as sys::CUstream,
14741 base as sys::CUdeviceptr,
14742 ticket,
14743 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14744 )
14745 };
14746 if r != sys::CUresult::CUDA_SUCCESS {
14747 return Err(format!("fence wait rank1: {r:?}").into());
14748 }
14749 } else {
14750 for ev in workspace.ev_rank.iter().skip(1) {
14751 e.stream().wait(ev)?;
14752 }
14753 }
14754 } else {
14755 {
14756 let root = &self.ranks[0];
14757 let _rmain = root.gpu.enter_main()?;
14758 workspace
14759 .ev_done
14760 .as_ref()
14761 .expect("device routes done event")
14762 .record(&root.stream())?;
14763 }
14764 e.stream().wait(
14765 workspace
14766 .ev_done
14767 .as_ref()
14768 .expect("device routes done event"),
14769 )?;
14770 for ev in workspace.ev_rank.iter().skip(1) {
14771 e.stream().wait(ev)?;
14772 }
14773 }
14774 let mut output = e.uninit(experts.input_width)?;
14775 if let Some((sh_raw, scale_raw)) = post_add {
14776 e.add3_raw(
14779 &workspace.accumulator[0],
14780 &workspace.accumulator[1],
14781 sh_raw,
14782 scale_raw,
14783 &mut output,
14784 experts.input_width,
14785 )?;
14786 } else {
14787 e.add(
14788 &workspace.accumulator[0],
14789 &workspace.accumulator[1],
14790 &mut output,
14791 experts.input_width,
14792 )?;
14793 }
14794 let output = output;
14795 if let Some(started) = started {
14796 use std::sync::atomic::Ordering;
14797 let ns = TIMING_NS
14798 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14799 + started.elapsed().as_nanos() as u64;
14800 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14801 if calls.is_multiple_of(430) {
14802 eprintln!(
14803 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14804 ns as f64 / 1.0e6,
14805 ns as f64 / calls as f64 / 1.0e3,
14806 );
14807 }
14808 }
14809 return Ok(output);
14810 }
14811 {
14812 let root = &self.ranks[0];
14813 let _main = root.gpu.enter_main()?;
14814 for ev in workspace.ev_rank.iter().skip(1) {
14815 root.stream().wait(ev)?;
14816 }
14817 root.stream()
14818 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
14819 {
14820 let Nvfp4DeviceRoutesWorkspace {
14821 accumulator,
14822 remote,
14823 combined,
14824 ..
14825 } = &mut *workspace;
14826 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14827 }
14828 workspace
14829 .ev_done
14830 .as_ref()
14831 .expect("device routes done event")
14832 .record(&root.stream())?;
14833 }
14834 let output = {
14835 let _main = e.gpu.enter_main()?;
14836 e.stream().wait(
14837 workspace
14838 .ev_done
14839 .as_ref()
14840 .expect("device routes done event"),
14841 )?;
14842 let mut output = e.uninit(experts.input_width)?;
14845 e.stream().memcpy_dtod(
14846 &workspace.combined.slice(0..experts.input_width),
14847 &mut output.slice_mut(0..experts.input_width),
14848 )?;
14849 output
14850 };
14851 if let Some(started) = started {
14852 use std::sync::atomic::Ordering;
14853 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14854 + started.elapsed().as_nanos() as u64;
14855 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14856 if calls.is_multiple_of(430) {
14857 eprintln!(
14858 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14859 ns as f64 / 1.0e6,
14860 ns as f64 / calls as f64 / 1.0e3,
14861 );
14862 }
14863 }
14864 Ok(output)
14865 }
14866
14867 pub(crate) fn decode_v2_finish_root_fused(
14871 &self,
14872 ws: &mut StepTpDecodeV2Ws,
14873 ) -> Result<(), Box<dyn std::error::Error>> {
14874 let root = &self.ranks[0];
14875 let _main = root.gpu.enter_main()?;
14876 if ws.raw_peer_partial != 0 {
14877 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
14879 } else {
14880 root.stream()
14881 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
14882 }
14883 {
14884 let StepTpDecodeV2Ws {
14885 o_partials,
14886 peer_partial,
14887 reduce_a,
14888 o_out,
14889 ..
14890 } = &mut *ws;
14891 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
14892 }
14893 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
14894 if shadows {
14895 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
14898 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
14899 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
14900 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
14901 }
14902 if shadows && ws.raw_peer_partial != 0 {
14903 raw_copy_bytes(
14904 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
14905 ws.raw_k1,
14906 ws.local_kv_dim * 4,
14907 root,
14908 )?;
14909 raw_copy_bytes(
14910 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
14911 ws.raw_v1,
14912 ws.local_kv_dim * 4,
14913 root,
14914 )?;
14915 } else if shadows {
14916 let start = ws.local_kv_dim;
14917 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
14918 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
14919 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
14920 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
14921 }
14922 if ws.raw_mixed_stage_e != 0 {
14923 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
14926 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
14927 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
14928 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
14929 }
14930 Ok(())
14931 }
14932
14933 pub(crate) fn decode_v2_arm_token_mirrors(
14936 &self,
14937 ws: &mut StepTpDecodeV2Ws,
14938 mixed_stage_e: u64,
14939 shadow_stage_e: (u64, u64),
14940 ) -> Result<(), Box<dyn std::error::Error>> {
14941 use cudarc::driver::DevicePtr;
14942 let root = &self.ranks[0];
14943 let _main = root.gpu.enter_main()?;
14944 let stream = root.stream();
14945 let (a, _g) = ws.reduce_a.device_ptr(&stream);
14946 ws.raw_reduce_a = a;
14947 ws.raw_mixed_stage_e = mixed_stage_e;
14948 ws.raw_shadow_stage_e = shadow_stage_e;
14949 Ok(())
14950 }
14951
14952 fn nvfp4_routes_build_graph(
14958 &self,
14959 experts: &ResidentNvfp4TensorParallel,
14960 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14961 local_out: usize,
14962 n_sel: usize,
14963 activation_limit: Option<f32>,
14964 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
14965 use cudarc::driver::DevicePtr;
14966 use cudarc::driver::sys;
14967 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14968 if r == sys::CUresult::CUDA_SUCCESS {
14969 Ok(())
14970 } else {
14971 Err(format!("{what}: {r:?}").into())
14972 }
14973 }
14974 let world = self.ranks.len();
14975 if world != 2 {
14976 return Err("routes graph door is built for the TP2 pair".into());
14977 }
14978 let width = experts.input_width;
14979
14980 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
14982 let stream = engine.stream();
14983 let (ptr, _g) = buf.device_ptr(&stream);
14984 ptr
14985 };
14986 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
14987 let stream = engine.stream();
14988 let (ptr, _g) = buf.device_ptr(&stream);
14989 ptr
14990 };
14991 let (sel_e, w_e) = workspace
14992 .dev_route_e
14993 .as_ref()
14994 .expect("device route staging set before graph build");
14995 let root_engine = &self.ranks[0];
14996 let p_in_stage = ptr_f32(
14997 workspace.in_stage_e.as_ref().expect("graph staging"),
14998 root_engine,
14999 );
15000 let p_out_stage = ptr_f32(
15001 workspace.out_stage_e.as_ref().expect("graph staging"),
15002 root_engine,
15003 );
15004 let p_sel_e = ptr_i32(sel_e, root_engine);
15005 let p_w_e = ptr_f32(w_e, root_engine);
15006 let p_input: Vec<u64> = (0..world)
15007 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
15008 .collect();
15009 let p_sel: Vec<u64> = (0..world)
15010 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
15011 .collect();
15012 let p_route_w: Vec<u64> = (0..world)
15013 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
15014 .collect();
15015 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
15016 let p_remote = ptr_f32(&workspace.remote, root_engine);
15017 let p_combined = ptr_f32(&workspace.combined, root_engine);
15018
15019 let raw_copy = |dst: u64,
15020 src: u64,
15021 bytes: usize,
15022 engine: &Engine|
15023 -> Result<(), Box<dyn std::error::Error>> {
15024 unsafe {
15025 cu_try(
15026 sys::cuMemcpyAsync(
15027 dst as sys::CUdeviceptr,
15028 src as sys::CUdeviceptr,
15029 bytes,
15030 engine.stream().cu_stream() as sys::CUstream,
15031 ),
15032 "routes graph cuMemcpyAsync",
15033 )
15034 }
15035 };
15036
15037 let mut children = Vec::with_capacity(3);
15038 for rank in 0..world {
15039 let engine = &self.ranks[rank];
15040 let _main = engine.gpu.enter_main()?;
15041 let (child, _retained) = engine.capture_graph_retained(|_| {
15042 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
15043 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
15044 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
15045 {
15046 let Nvfp4DeviceRoutesWorkspace {
15047 input, in_q, in_d, ..
15048 } = &mut *workspace;
15049 engine.quantize_q8_1_into(
15050 &input[rank],
15051 1,
15052 width,
15053 &mut in_q[rank],
15054 &mut in_d[rank],
15055 )?;
15056 }
15057 self.nvfp4_routes_batched_sweeps_rank(
15058 experts,
15059 workspace,
15060 &[],
15061 &[],
15062 &[],
15063 local_out,
15064 n_sel,
15065 activation_limit,
15066 true,
15067 rank,
15068 )?;
15069 Ok(())
15070 })?;
15071 children.push(child);
15072 }
15073 {
15074 let root = &self.ranks[0];
15075 let _main = root.gpu.enter_main()?;
15076 let (child, _retained) = root.capture_graph_retained(|_| {
15077 raw_copy(p_remote, p_acc1, width * 4, root)?;
15078 {
15079 let Nvfp4DeviceRoutesWorkspace {
15080 accumulator,
15081 remote,
15082 combined,
15083 ..
15084 } = &mut *workspace;
15085 root.add(&accumulator[0], remote, combined, width)?;
15086 }
15087 raw_copy(p_out_stage, p_combined, width * 4, root)?;
15088 Ok(())
15089 })?;
15090 children.push(child);
15091 }
15092
15093 let mut parent: sys::CUgraph = std::ptr::null_mut();
15094 unsafe {
15095 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
15096 }
15097 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
15098 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
15099 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
15100 unsafe {
15101 cu_try(
15102 sys::cuGraphAddChildGraphNode(
15103 &mut n0,
15104 parent,
15105 std::ptr::null(),
15106 0,
15107 children[0].cu_graph(),
15108 ),
15109 "routes child r0",
15110 )?;
15111 cu_try(
15112 sys::cuGraphAddChildGraphNode(
15113 &mut n1,
15114 parent,
15115 std::ptr::null(),
15116 0,
15117 children[1].cu_graph(),
15118 ),
15119 "routes child r1",
15120 )?;
15121 let deps = [n0, n1];
15122 cu_try(
15123 sys::cuGraphAddChildGraphNode(
15124 &mut n2,
15125 parent,
15126 deps.as_ptr(),
15127 2,
15128 children[2].cu_graph(),
15129 ),
15130 "routes child root",
15131 )?;
15132 }
15133 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
15134 unsafe {
15135 cu_try(
15136 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
15137 "routes instantiate",
15138 )?;
15139 }
15140 Ok(RoutesGraph {
15141 exec,
15142 parent,
15143 _children: children,
15144 })
15145 }
15146
15147 #[allow(clippy::too_many_arguments)]
15151 pub(crate) fn routes_rank_section(
15152 &self,
15153 experts: &ResidentNvfp4TensorParallel,
15154 workspace: &mut Nvfp4DeviceRoutesWorkspace,
15155 raw_input_src: u64,
15156 local_out: usize,
15157 n_sel: usize,
15158 activation_limit: Option<f32>,
15159 rank_index: usize,
15160 ) -> Result<(), Box<dyn std::error::Error>> {
15161 let engine = &self.ranks[rank_index];
15162 {
15163 let _main = engine.gpu.enter_main()?;
15164 let (sel_e_ptr, w_e_ptr) = workspace
15166 .raw_dev_route_e
15167 .ok_or("routes rank section requires armed staging pointers")?;
15168 raw_copy_bytes(
15169 workspace.raw_input[rank_index],
15170 raw_input_src,
15171 experts.input_width * 4,
15172 engine,
15173 )?;
15174 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
15175 raw_copy_bytes(
15176 workspace.raw_route_w[rank_index],
15177 w_e_ptr,
15178 n_sel * 4,
15179 engine,
15180 )?;
15181 {
15182 let Nvfp4DeviceRoutesWorkspace {
15183 input, in_q, in_d, ..
15184 } = &mut *workspace;
15185 engine.quantize_q8_1_into(
15186 &input[rank_index],
15187 1,
15188 experts.input_width,
15189 &mut in_q[rank_index],
15190 &mut in_d[rank_index],
15191 )?;
15192 }
15193 }
15194 self.nvfp4_routes_batched_sweeps_rank(
15195 experts,
15196 workspace,
15197 &[],
15198 &[],
15199 &[],
15200 local_out,
15201 n_sel,
15202 activation_limit,
15203 true,
15204 rank_index,
15205 )
15206 }
15207
15208 pub(crate) fn routes_root_section(
15211 &self,
15212 experts: &ResidentNvfp4TensorParallel,
15213 workspace: &mut Nvfp4DeviceRoutesWorkspace,
15214 ) -> Result<(), Box<dyn std::error::Error>> {
15215 let root = &self.ranks[0];
15216 let _main = root.gpu.enter_main()?;
15217 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
15218 .raw_combine
15219 .ok_or("routes root section requires armed combine pointers")?;
15220 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
15221 {
15222 let Nvfp4DeviceRoutesWorkspace {
15223 accumulator,
15224 remote,
15225 combined,
15226 ..
15227 } = &mut *workspace;
15228 root.add(&accumulator[0], remote, combined, experts.input_width)?;
15229 }
15230 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
15231 Ok(())
15232 }
15233
15234 pub(crate) fn routes_arm_raw(
15237 &self,
15238 experts: &ResidentNvfp4TensorParallel,
15239 workspace: &mut Nvfp4DeviceRoutesWorkspace,
15240 ) -> Result<(), Box<dyn std::error::Error>> {
15241 use cudarc::driver::DevicePtr;
15242 if workspace.raw_dev_route_e.is_some() {
15243 return Ok(());
15244 }
15245 let _ = experts;
15246 let (sel_e, w_e) = workspace
15247 .dev_route_e
15248 .as_ref()
15249 .ok_or("routes staging not armed")?;
15250 let root = &self.ranks[0];
15251 {
15252 let _main = root.gpu.enter_main()?;
15253 let stream = root.stream();
15254 let (a, _g) = sel_e.device_ptr(&stream);
15255 let (b, _g) = w_e.device_ptr(&stream);
15256 workspace.raw_dev_route_e = Some((a, b));
15257 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
15258 let (d, _g) = workspace.remote.device_ptr(&stream);
15259 let (f, _g) = workspace.combined.device_ptr(&stream);
15260 let out_stage = workspace
15261 .out_stage_e
15262 .as_ref()
15263 .ok_or("routes out stage not armed")?;
15264 let (g_, _g) = out_stage.device_ptr(&stream);
15265 workspace.raw_combine = Some((c, d, f, g_));
15266 }
15267 for rank in 0..self.ranks.len() {
15268 let engine = &self.ranks[rank];
15269 let _main = engine.gpu.enter_main()?;
15270 let stream = engine.stream();
15271 let (a, _g) = workspace.input[rank].device_ptr(&stream);
15272 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
15273 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
15274 workspace.raw_input.push(a);
15275 workspace.raw_sel.push(b);
15276 workspace.raw_route_w.push(c);
15277 }
15278 Ok(())
15279 }
15280
15281 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4(
15286 &self,
15287 experts: &ResidentNvfp4TensorParallel,
15288 input: &[f32],
15289 tokens: usize,
15290 selected: &[usize],
15291 route_weights: &[f32],
15292 experts_per_token: usize,
15293 activation_limit: Option<f32>,
15294 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
15295 validate_activations(input, tokens, experts.input_width)?;
15296 let pairs = tokens
15297 .checked_mul(experts_per_token)
15298 .ok_or("NVFP4 TP route count overflow")?;
15299 if selected.len() != pairs || route_weights.len() != pairs {
15300 return Err(format!(
15301 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
15302 {experts_per_token} ({pairs})",
15303 selected.len(),
15304 route_weights.len(),
15305 )
15306 .into());
15307 }
15308 if !route_weights.iter().all(|weight| weight.is_finite()) {
15309 return Err("NVFP4 TP route weights contain a non-finite value".into());
15310 }
15311
15312 let mut output = vec![0.0f32; tokens * experts.input_width];
15313 for token in 0..tokens {
15314 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
15315 for slot in 0..experts_per_token {
15316 let pair = token * experts_per_token + slot;
15317 let expert = selected[pair];
15318 if expert >= experts.expert_count {
15319 return Err(format!(
15320 "NVFP4 TP selected expert {expert} outside 0..{}",
15321 experts.expert_count
15322 )
15323 .into());
15324 }
15325 let gate = if experts.ep2 {
15331 self.run_full_bank_expert_nvfp4(
15332 &experts.gate,
15333 &experts.macros_gate,
15334 expert,
15335 input_row,
15336 )?
15337 } else {
15338 self.run_column_bank_expert_nvfp4(
15339 &experts.gate,
15340 &experts.macros_gate,
15341 expert,
15342 input_row,
15343 )?
15344 };
15345 let up = if experts.ep2 {
15346 self.run_full_bank_expert_nvfp4(
15347 &experts.up,
15348 &experts.macros_up,
15349 expert,
15350 input_row,
15351 )?
15352 } else {
15353 self.run_column_bank_expert_nvfp4(
15354 &experts.up,
15355 &experts.macros_up,
15356 expert,
15357 input_row,
15358 )?
15359 };
15360 let activated: Vec<f32> = gate
15361 .iter()
15362 .zip(&up)
15363 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
15364 .collect();
15365 debug_assert_eq!(activated.len(), experts.expert_width);
15366 let down = if experts.ep2 {
15367 self.run_full_down_expert_nvfp4(
15368 &experts.down,
15369 &experts.macros_down,
15370 expert,
15371 &activated,
15372 )?
15373 } else {
15374 self.run_row_bank_expert_nvfp4(
15375 &experts.down,
15376 &experts.macros_down,
15377 expert,
15378 &activated,
15379 )?
15380 };
15381 let weight = route_weights[pair];
15382 for (sum, value) in output
15383 [token * experts.input_width..(token + 1) * experts.input_width]
15384 .iter_mut()
15385 .zip(down)
15386 {
15387 *sum += weight * value;
15388 }
15389 }
15390 }
15391 Ok(output)
15392 }
15393}
15394
15395#[cfg(test)]
15396mod default_on_door_tests {
15397 use super::door_default_on_value;
15398
15399 #[test]
15410 fn the_default_on_door_parses_every_state_and_names_its_source() {
15411 assert_eq!(
15414 door_default_on_value("MEMRA_TEST_DOOR", None),
15415 (true, "default-on")
15416 );
15417 assert_eq!(
15419 door_default_on_value("MEMRA_TEST_DOOR", Some("1")),
15420 (true, "env=1")
15421 );
15422 assert_eq!(
15424 door_default_on_value("MEMRA_TEST_DOOR", Some("0")),
15425 (false, "env=0 (rollback seam)")
15426 );
15427 for bad in [
15432 "false", "off", "no", "", " 0", "0 ", "00", "true", "2", "-1",
15433 ] {
15434 let (on, source) = door_default_on_value("MEMRA_TEST_DOOR", Some(bad));
15435 assert!(on, "value {bad:?} must NOT disarm a default-ON door");
15436 assert!(
15437 source.contains("default-on") && source.contains("unrecognized"),
15438 "value {bad:?} gave source {source:?}, which does not announce itself as an \
15439 ignored value — a receipt reader would take it for a clean default"
15440 );
15441 }
15442 }
15443}
15444
15445#[cfg(test)]
15446mod bank_v2_layout_tests {
15447 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
15448
15449 #[test]
15459 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
15460 let (out_f, in_f) = (2usize, 128usize);
15462 let row_bytes = nvfp4_row_bytes(in_f);
15463 assert_eq!(row_bytes, 72);
15464 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
15465 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
15466 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
15467 let n_slots = in_f / 32;
15468 for row in 0..out_f {
15469 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
15470 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
15471 for g in 0..n_slots {
15472 let (sblk, h) = (g / 2, g % 2);
15473 let sb = &src[sblk * 36..sblk * 36 + 36];
15474 assert_eq!(
15475 &dst[g * 16..g * 16 + 16],
15476 &sb[4 + 16 * h..4 + 16 * h + 16],
15477 "row {row} slot {g} codes"
15478 );
15479 assert_eq!(
15480 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
15481 &sb[2 * h..2 * h + 2],
15482 "row {row} slot {g} scales"
15483 );
15484 }
15485 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
15487 a.sort_unstable();
15488 b.sort_unstable();
15489 assert_eq!(a, b, "row {row} is not a byte permutation");
15490 }
15491 }
15492}
15493
15494#[cfg(test)]
15495mod tests {
15496
15497 #[test]
15498 fn replicated_row_join_is_strictly_tp2_native_and_nonempty() {
15499 assert!(super::validate_tp2_replicated_row_join(2, true, 4096).is_ok());
15500 assert!(
15501 super::validate_tp2_replicated_row_join(1, true, 4096)
15502 .unwrap_err()
15503 .contains("exactly two ranks")
15504 );
15505 assert!(
15506 super::validate_tp2_replicated_row_join(4, true, 4096)
15507 .unwrap_err()
15508 .contains("exactly two ranks")
15509 );
15510 assert!(
15511 super::validate_tp2_replicated_row_join(2, false, 4096)
15512 .unwrap_err()
15513 .contains("native P2P")
15514 );
15515 assert!(super::validate_tp2_replicated_row_join(2, true, 0).is_err());
15516 }
15517
15518 #[test]
15519 fn door_composition_refuses_first_armed_flag_by_name() {
15520 let table: [(&str, &str); 2] = [
15521 ("MEMRA_DOOR_A", "gated on the unsharded walk only"),
15522 ("MEMRA_DOOR_B", "no sharded branches"),
15523 ];
15524 super::refuse_door_composition("MEMRA_X_TP", &table, |_| false).expect("cold doors pass");
15526 let err = super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_B")
15528 .expect_err("armed door must refuse");
15529 assert_eq!(
15530 err,
15531 "MEMRA_X_TP + MEMRA_DOOR_B: unproven composition, refused (no sharded branches)"
15532 );
15533 super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_C")
15535 .expect("foreign flags are not the matrix");
15536 }
15537
15538 #[test]
15545 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
15546 let (kp, bp) = (0xdead_0000u64, 0u64);
15547 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
15548 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
15549 assert_eq!(
15550 super::retired_rows_tab_key(kp, bp, 20, 2),
15551 super::retired_rows_tab_key(kp, bp, 20, 2),
15552 "same layer and t must hash the same, or the test proves nothing"
15553 );
15554 let a = super::rows_tab_host(&live, 0x9000, true, 1);
15555 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
15556 assert_ne!(a, b, "the two generations write DIFFERENT tables");
15557 assert_eq!(
15559 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
15560 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
15561 "the retired key collides across allocation generations"
15562 );
15563 }
15564
15565 #[test]
15568 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
15569 let parts = [
15570 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
15571 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
15572 ];
15573 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
15574 assert_eq!(
15575 same,
15576 vec![
15577 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
15578 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
15581 "same-session rows share one counter cell and step back t-1-r"
15582 );
15583 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
15584 assert_eq!(
15585 cross,
15586 vec![
15587 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
15588 0x00c1u64, 0x00d1u64, 0x7004, 0,
15589 ],
15590 "cross-session rows get their own counter cell and no step back"
15591 );
15592 }
15593 use super::*;
15594
15595 #[test]
15596 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
15597 let limit = Some(7.0);
15598 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
15599 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
15600 assert!(
15601 step_expert_activation_host(-20.0, 9.0, limit).abs()
15602 < step_expert_activation_host(-20.0, 9.0, None).abs()
15603 );
15604 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
15605 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
15606 assert!(validate_step_expert_activation_limit(limit).is_ok());
15607 }
15608
15609 #[test]
15610 fn moe_residual_host_preserves_official_add_order() {
15611 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
15612 assert_eq!(output, [0.0]);
15613 assert_eq!(
15614 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
15615 "MoE residual lengths residual=1 routed=2 shared=1"
15616 );
15617 }
15618
15619 #[test]
15620 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
15621 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
15622 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
15623 assert_eq!(owners.len(), 4);
15624 for (rank, owner) in owners.iter().enumerate() {
15625 assert_eq!(owner.rank, rank);
15626 assert_eq!(owner.selected, vec![0, 36]);
15627 assert_eq!(owner.token_rows, vec![0, 0]);
15628 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
15629 }
15630 }
15631
15632 #[test]
15633 fn expert_owner_routes_validate_geometry_and_selected_experts() {
15634 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
15635 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
15636 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
15637 assert!(error.contains("outside 0..288"));
15638 }
15639
15640 #[test]
15641 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
15642 let selected = [
15643 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
15644 ];
15645 assert_eq!(
15646 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
15647 16
15648 );
15649 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
15650 assert_eq!(
15651 owners
15652 .iter()
15653 .map(|owner| owner.selected.len())
15654 .collect::<Vec<_>>(),
15655 vec![2, 4, 6, 4]
15656 );
15657 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
15658 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
15659 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
15660 }
15661
15662 #[test]
15663 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
15664 let owner0 = [0usize, 3];
15665 let owner1 = [1usize, 2];
15666 let owners = [owner0.as_slice(), owner1.as_slice()];
15667 assert_eq!(
15668 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
15669 .unwrap(),
15670 WeightedRouteCombineShape {
15671 pairs: 4,
15672 max_pairs: 12,
15673 }
15674 );
15675 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
15676 assert!(
15677 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
15678 .is_err()
15679 );
15680 assert!(
15681 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
15682 .is_err()
15683 );
15684 assert!(
15685 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
15686 .is_err()
15687 );
15688 }
15689
15690 #[test]
15691 fn native_p2p_door_is_strict_and_default_off() {
15692 assert!(!parse_step_tp_native_p2p(None).unwrap());
15693 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
15694 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
15695 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
15696 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
15697 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
15698 }
15699
15700 #[test]
15701 fn bulk_p2p_door_is_strict_and_default_off() {
15702 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
15703 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
15704 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
15705 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
15706 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
15707 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
15708 }
15709
15710 #[test]
15711 fn ep_device_arithmetic_door_is_strict_and_default_off() {
15712 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
15713 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
15714 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
15715 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
15716 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
15717 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
15718 }
15719
15720 #[test]
15721 fn f32_mirror_door_is_strict_and_default_off() {
15722 assert!(!parse_step_tp_f32_mirror(None).unwrap());
15723 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
15724 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
15725 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
15726 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
15727 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
15728 }
15729
15730 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
15731 let codes = (0..out_features * in_features)
15732 .map(|index| (index % 251) as u8)
15733 .collect();
15734 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
15735 .map(|index| index as f32 + 1.0)
15736 .collect();
15737 (codes, scales)
15738 }
15739
15740 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
15741 (0..out_features * in_features)
15742 .flat_map(|value| (value as u16).to_le_bytes())
15743 .collect()
15744 }
15745
15746 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
15747 bytes
15748 .chunks_exact(2)
15749 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
15750 .collect()
15751 }
15752
15753 #[test]
15754 fn bf16_matrix_rejects_wrong_byte_count() {
15755 let bytes = vec![0u8; 4 * 4 * 2 - 1];
15756 let matrix = Bf16Matrix {
15757 bytes: &bytes,
15758 out_features: 4,
15759 in_features: 4,
15760 };
15761 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
15762 }
15763
15764 #[test]
15765 fn replicated_device_rows_require_exact_rank_local_shapes() {
15766 assert_eq!(
15767 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
15768 12_288
15769 );
15770 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
15771 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
15772 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
15773 assert!(
15774 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
15775 );
15776 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
15777 }
15778
15779 #[test]
15780 fn replicated_device_row_refresh_requires_exact_root_source() {
15781 assert_eq!(
15782 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
15783 12_288
15784 );
15785 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
15786 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
15787 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
15788 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
15789 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
15790 }
15791
15792 #[test]
15793 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
15794 for tp in [1, 2, 4, 8] {
15795 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
15796 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
15797 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
15798 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
15799 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
15800 }
15801 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
15802 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
15803 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
15804 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
15805 }
15806
15807 #[test]
15808 fn cache_rows_split_by_token_then_rank() {
15809 let rows = (0u8..24).collect::<Vec<_>>();
15810 assert_eq!(
15811 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
15812 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
15813 );
15814 assert_eq!(
15815 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
15816 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
15817 );
15818 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
15819 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
15820 }
15821
15822 #[test]
15823 fn bf16_column_shard_preserves_contiguous_output_rows() {
15824 let bytes = bf16_matrix_bytes(4, 4);
15825 let matrix = Bf16Matrix {
15826 bytes: &bytes,
15827 out_features: 4,
15828 in_features: 4,
15829 };
15830 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
15831 assert_eq!(shard.out_features, 2);
15832 assert_eq!(shard.in_features, 4);
15833 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
15834 }
15835
15836 #[test]
15837 fn bf16_row_shard_preserves_each_input_column_window() {
15838 let bytes = bf16_matrix_bytes(3, 4);
15839 let matrix = Bf16Matrix {
15840 bytes: &bytes,
15841 out_features: 3,
15842 in_features: 4,
15843 };
15844 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
15845 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
15846 }
15847
15848 #[test]
15849 fn bf16_row_block_preserves_global_column_order() {
15850 let bytes = bf16_matrix_bytes(3, 8);
15851 let matrix = Bf16Matrix {
15852 bytes: &bytes,
15853 out_features: 3,
15854 in_features: 8,
15855 };
15856 let block = bf16_row_block(matrix, 2, 3).unwrap();
15857 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
15858 }
15859
15860 #[test]
15861 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
15862 let (codes, scales) = matrix(1280, 4096);
15863 let matrix = E4m3BlockMatrix {
15864 codes: &codes,
15865 scales: &scales,
15866 out_features: 1280,
15867 in_features: 4096,
15868 };
15869 let shard = column_shard(matrix, 2, 1).unwrap();
15870 assert_eq!(shard.out_features, 640);
15871 assert_eq!(shard.codes, &codes[640 * 4096..]);
15872 assert_eq!(shard.scales, &scales[5 * 32..]);
15873 }
15874
15875 #[test]
15876 fn row_shard_preserves_each_weight_and_scale_column_window() {
15877 let (codes, scales) = matrix(4096, 1280);
15878 let matrix = E4m3BlockMatrix {
15879 codes: &codes,
15880 scales: &scales,
15881 out_features: 4096,
15882 in_features: 1280,
15883 };
15884 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
15885 assert_eq!(shard_codes.len(), 4096 * 640);
15886 assert_eq!(&shard_codes[..640], &codes[640..1280]);
15887 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
15888 assert_eq!(shard_scales.len(), 32 * 5);
15889 assert_eq!(&shard_scales[..5], &scales[5..10]);
15890 assert_eq!(&shard_scales[5..10], &scales[15..20]);
15891 }
15892
15893 #[test]
15894 fn activation_shards_keep_token_rows_separate() {
15895 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
15896 assert_eq!(
15897 activation_shard(&activations, 2, 8, 2, 1),
15898 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
15899 );
15900 }
15901
15902 #[test]
15903 fn expert_bank_selects_expert_major_code_and_scale_planes() {
15904 let expert_count = 2;
15905 let out_features = 128;
15906 let in_features = 128;
15907 let code_stride = out_features * in_features;
15908 let codes: Vec<u8> = (0..expert_count * code_stride)
15909 .map(|index| (index % 251) as u8)
15910 .collect();
15911 let scales = vec![1.0f32, 2.0];
15912 let bank = E4m3ExpertBank {
15913 codes: &codes,
15914 scales: &scales,
15915 expert_count,
15916 out_features,
15917 in_features,
15918 };
15919 bank.validate().unwrap();
15920 let expert = bank.expert(1).unwrap();
15921 assert_eq!(expert.codes, &codes[code_stride..]);
15922 assert_eq!(expert.scales, &[2.0]);
15923 }
15924
15925 #[test]
15926 fn expert_bank_rejects_non_positive_scale() {
15927 let codes = vec![0u8; 128 * 128];
15928 let scales = vec![0.0f32];
15929 let bank = E4m3ExpertBank {
15930 codes: &codes,
15931 scales: &scales,
15932 expert_count: 1,
15933 out_features: 128,
15934 in_features: 128,
15935 };
15936 assert!(bank.validate().unwrap_err().contains("non-positive"));
15937 }
15938
15939 #[test]
15940 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
15941 let expert_count = 2;
15942 let out_features = 256;
15943 let in_features = 128;
15944 let code_stride = out_features * in_features;
15945 let scale_stride = 2;
15946 let codes = (0..expert_count * code_stride)
15947 .map(|index| (index % 251) as u8)
15948 .collect::<Vec<_>>();
15949 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15950 let bank = E4m3ExpertBank {
15951 codes: &codes,
15952 scales: &scales,
15953 expert_count,
15954 out_features,
15955 in_features,
15956 };
15957
15958 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
15959 assert_eq!(rank.out_features, 128);
15960 assert_eq!(rank.in_features, 128);
15961 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15962 assert_eq!(rank.scales, vec![11.0, 21.0]);
15963 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
15964 assert_eq!(
15965 &rank.codes[128 * 128..],
15966 &codes[code_stride + 128 * 128..2 * code_stride]
15967 );
15968 assert_eq!(scale_stride, scales.len() / expert_count);
15969 }
15970
15971 #[test]
15972 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
15973 let expert_count = 2;
15974 let out_features = 128;
15975 let in_features = 256;
15976 let code_stride = out_features * in_features;
15977 let codes = (0..expert_count * code_stride)
15978 .map(|index| (index % 251) as u8)
15979 .collect::<Vec<_>>();
15980 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15981 let bank = E4m3ExpertBank {
15982 codes: &codes,
15983 scales: &scales,
15984 expert_count,
15985 out_features,
15986 in_features,
15987 };
15988
15989 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15990 assert_eq!(rank.out_features, 128);
15991 assert_eq!(rank.in_features, 128);
15992 assert_eq!(rank.k_blocks, Some(1));
15993 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15994 assert_eq!(rank.scales, vec![11.0, 21.0]);
15995 assert_eq!(&rank.codes[..128], &codes[128..256]);
15996 assert_eq!(
15997 &rank.codes[128 * 128..128 * 128 + 128],
15998 &codes[code_stride + 128..code_stride + 256]
15999 );
16000 }
16001
16002 #[test]
16003 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
16004 let expert_count = 2;
16005 let out_features = 256;
16006 let in_features = 512;
16007 let code_stride = out_features * in_features;
16008 let mut codes = vec![0u8; expert_count * code_stride];
16009 for expert in 0..expert_count {
16010 for row in 0..out_features {
16011 for block in 0..4 {
16012 let value = (expert * 80 + block * 16 + row % 16) as u8;
16013 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
16014 codes[start..start + FP8_BLOCK].fill(value);
16015 }
16016 }
16017 }
16018 let scales = vec![
16019 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,
16020 112.0, 113.0, 114.0,
16021 ];
16022 let bank = E4m3ExpertBank {
16023 codes: &codes,
16024 scales: &scales,
16025 expert_count,
16026 out_features,
16027 in_features,
16028 };
16029
16030 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
16031 assert_eq!(rank.out_features, out_features);
16032 assert_eq!(rank.in_features, 256);
16033 assert_eq!(rank.k_blocks, Some(2));
16034 assert_eq!(rank.code_stride, out_features * 256);
16035 assert_eq!(rank.scale_stride, 4);
16036 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
16037 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
16038
16039 let block_stride = out_features * FP8_BLOCK;
16040 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
16041 assert!(
16042 rank.codes[block_stride..block_stride + FP8_BLOCK]
16043 .iter()
16044 .all(|&code| code == 48)
16045 );
16046 assert!(
16047 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
16048 .iter()
16049 .all(|&code| code == 112)
16050 );
16051 assert!(
16052 rank.codes
16053 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
16054 .iter()
16055 .all(|&code| code == 128)
16056 );
16057 }
16058
16059 #[test]
16060 fn automatic_parallel_policy_needs_only_one_device_set_not_layer_recipes() {
16061 assert_eq!(parse_auto_parallel_devices(None, None).unwrap(), None);
16062 assert_eq!(
16063 parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3")).unwrap(),
16064 Some(vec![0, 1, 2, 3])
16065 );
16066 assert!(parse_auto_parallel_devices(Some("auto"), None).is_err());
16067 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,1")).is_err());
16068 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3,4")).is_err());
16069 assert!(parse_auto_parallel_devices(Some("ep"), Some("0,1")).is_err());
16070 }
16071
16072 #[test]
16073 fn automatic_ep_device_router_flag_is_strict() {
16074 assert!(!parse_parallel_ep_device_router(None).unwrap());
16075 assert!(!parse_parallel_ep_device_router(Some("0")).unwrap());
16076 assert!(parse_parallel_ep_device_router(Some("1")).unwrap());
16077 assert!(parse_parallel_ep_device_router(Some("true")).is_err());
16078 }
16079
16080 #[test]
16081 fn automatic_ep_graph_flag_is_strict_and_defaults_off() {
16082 assert!(!parse_parallel_ep_graph(None).unwrap());
16083 assert!(!parse_parallel_ep_graph(Some("0")).unwrap());
16084 assert!(parse_parallel_ep_graph(Some("1")).unwrap());
16085 assert!(parse_parallel_ep_graph(Some("true")).is_err());
16086 }
16087
16088 #[test]
16089 fn automatic_ep_pair_down_flag_is_strict_and_defaults_off() {
16090 assert!(!parse_parallel_ep_pair_down(None).unwrap());
16091 assert!(!parse_parallel_ep_pair_down(Some("0")).unwrap());
16092 assert!(parse_parallel_ep_pair_down(Some("1")).unwrap());
16093 assert!(parse_parallel_ep_pair_down(Some("true")).is_err());
16094 }
16095
16096 #[test]
16097 fn automatic_ep_q8_activation_flag_is_strict() {
16098 assert!(!parse_parallel_ep_q8_act(None).unwrap());
16099 assert!(!parse_parallel_ep_q8_act(Some("0")).unwrap());
16100 assert!(parse_parallel_ep_q8_act(Some("1")).unwrap());
16101 assert!(parse_parallel_ep_q8_act(Some("true")).is_err());
16102 }
16103
16104 #[test]
16105 fn automatic_ep_q8_scope_is_explicit_and_strict() {
16106 assert_eq!(parse_parallel_ep_q8_scope(None).unwrap(), None);
16107 assert_eq!(
16108 parse_parallel_ep_q8_scope(Some("all")).unwrap(),
16109 Some(ParallelEpQ8Scope::All)
16110 );
16111 assert_eq!(
16112 parse_parallel_ep_q8_scope(Some("gate-up")).unwrap(),
16113 Some(ParallelEpQ8Scope::GateUp)
16114 );
16115 assert_eq!(
16116 parse_parallel_ep_q8_scope(Some("down")).unwrap(),
16117 Some(ParallelEpQ8Scope::Down)
16118 );
16119 assert!(parse_parallel_ep_q8_scope(Some("input")).is_err());
16120 }
16121
16122 #[test]
16123 fn automatic_ep_q8_gate_up_paired_is_parent_scoped_and_strict() {
16124 assert_eq!(parse_parallel_ep_q8_gu_paired(None).unwrap(), None);
16125 assert_eq!(parse_parallel_ep_q8_gu_paired(Some("")).unwrap(), None);
16126 assert_eq!(
16127 parse_parallel_ep_q8_gu_paired(Some("0")).unwrap(),
16128 Some(false)
16129 );
16130 assert_eq!(
16131 parse_parallel_ep_q8_gu_paired(Some("1")).unwrap(),
16132 Some(true)
16133 );
16134 assert!(parse_parallel_ep_q8_gu_paired(Some("paired")).is_err());
16135 assert!(parse_parallel_ep_q8_gu_paired(Some("true")).is_err());
16136
16137 assert!(!resolve_parallel_ep_q8_gu_paired(None, false, None).unwrap());
16138 assert!(resolve_parallel_ep_q8_gu_paired(None, true, None).unwrap());
16139 assert!(
16140 resolve_parallel_ep_q8_gu_paired(None, true, Some(ParallelEpQ8Scope::GateUp)).unwrap()
16141 );
16142 assert!(
16143 !resolve_parallel_ep_q8_gu_paired(None, true, Some(ParallelEpQ8Scope::Down)).unwrap()
16144 );
16145 assert!(!resolve_parallel_ep_q8_gu_paired(Some("0"), false, None).unwrap());
16146 assert!(!resolve_parallel_ep_q8_gu_paired(Some("0"), true, None).unwrap());
16147 assert!(resolve_parallel_ep_q8_gu_paired(Some("1"), false, None).is_err());
16148 assert!(
16149 resolve_parallel_ep_q8_gu_paired(Some("1"), true, Some(ParallelEpQ8Scope::Down))
16150 .is_err()
16151 );
16152 }
16153
16154 #[test]
16155 fn w4a16_device_ep_accepts_a_capacity_backed_active_prefix() {
16156 let width = 4096;
16157 assert_eq!(
16158 nvfp4_ep_active_input_values(160 * width, 44, width).unwrap(),
16159 44 * width
16160 );
16161 assert_eq!(
16162 nvfp4_ep_active_input_values(44 * width, 44, width).unwrap(),
16163 44 * width
16164 );
16165 assert!(nvfp4_ep_active_input_values(43 * width, 44, width).is_err());
16166 assert!(
16167 nvfp4_ep_active_input_values(160 * width, NVFP4_EP_DEVICE_BATCH_CAP + 1, width)
16168 .is_err()
16169 );
16170 }
16171
16172 #[test]
16173 fn step_ep_layer_specs_are_literal_and_fail_closed() {
16174 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
16175 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
16176 assert_eq!(
16177 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
16178 vec![StepEpLayerSpec {
16179 layer: 24,
16180 devices: vec![1, 2],
16181 }]
16182 );
16183 assert_eq!(
16184 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
16185 vec![
16186 StepEpLayerSpec {
16187 layer: 24,
16188 devices: vec![1, 2],
16189 },
16190 StepEpLayerSpec {
16191 layer: 25,
16192 devices: vec![1, 2],
16193 },
16194 StepEpLayerSpec {
16195 layer: 31,
16196 devices: vec![0, 2],
16197 },
16198 ]
16199 );
16200 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
16201 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
16202 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
16203 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
16204 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
16205 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
16206 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
16207 }
16208
16209 #[test]
16210 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
16211 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
16212 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
16213 assert_eq!(
16214 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
16215 vec![
16216 StepTpLayerSpec {
16217 layer: 24,
16218 devices: vec![1, 2],
16219 },
16220 StepTpLayerSpec {
16221 layer: 25,
16222 devices: vec![1, 2],
16223 },
16224 ]
16225 );
16226 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
16227 assert!(error.contains("MEMRA_STEP_TP"));
16228 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
16229 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
16230
16231 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
16232 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
16233 assert_eq!(all.first().unwrap().layer, 0);
16234 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
16235 let devices = (0..8).collect::<Vec<_>>();
16236 assert!(all.iter().all(|spec| spec.devices == devices));
16237 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
16238 }
16239}
16240
16241struct TokenGraphChild {
16253 #[allow(dead_code)]
16254 graph: cudarc::driver::CudaGraph,
16256 node: cudarc::driver::sys::CUgraphNode,
16257 ctx: cudarc::driver::sys::CUcontext,
16258}
16259
16260struct TokenGraphFaSite {
16264 ctx: cudarc::driver::sys::CUcontext,
16265 memset_o: cudarc::driver::sys::CUgraphNode,
16266 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
16267 fa: cudarc::driver::sys::CUgraphNode,
16268 combine: cudarc::driver::sys::CUgraphNode,
16269 window: usize,
16270 n_head: usize,
16271 n_head_kv: usize,
16272 head_dim: usize,
16273}
16274
16275pub struct TokenGraphBuilder {
16276 parent: cudarc::driver::sys::CUgraph,
16277 children: Vec<TokenGraphChild>,
16278 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
16281 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
16284 group: Option<(
16287 u32,
16288 Vec<cudarc::driver::sys::CUgraphNode>,
16289 Vec<cudarc::driver::sys::CUgraphNode>,
16290 )>,
16291}
16292
16293unsafe impl Send for TokenGraphBuilder {}
16295
16296impl TokenGraphBuilder {
16297 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
16298 use cudarc::driver::sys;
16299 let mut parent: sys::CUgraph = std::ptr::null_mut();
16300 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
16301 if r != sys::CUresult::CUDA_SUCCESS {
16302 return Err(format!("token graph create: {r:?}").into());
16303 }
16304 Ok(Self {
16305 parent,
16306 children: Vec::new(),
16307 frontier: Vec::new(),
16308 pending_detached: Vec::new(),
16309 group: None,
16310 })
16311 }
16312
16313 fn push_child(
16314 &mut self,
16315 graph: cudarc::driver::CudaGraph,
16316 parallel_group: Option<u32>,
16317 detached: bool,
16318 absorb: bool,
16319 ctx: cudarc::driver::sys::CUcontext,
16320 ) -> Result<(), Box<dyn std::error::Error>> {
16321 use cudarc::driver::sys;
16322 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
16326 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
16327 (state, Some(group)) => {
16328 if let Some((_, _, members)) = state.take() {
16330 self.frontier = members;
16331 }
16332 let base = self.frontier.clone();
16333 *state = Some((group, base.clone(), Vec::new()));
16334 base
16335 }
16336 (state, None) if detached => match state.as_ref() {
16337 Some((_, base, _)) => base.clone(),
16338 None => self.frontier.clone(),
16339 },
16340 (state, None) => {
16341 if let Some((_, _, members)) = state.take() {
16342 self.frontier = members;
16343 }
16344 let mut deps = self.frontier.clone();
16345 if absorb {
16346 deps.append(&mut self.pending_detached);
16347 }
16348 deps
16349 }
16350 };
16351 let mut node: sys::CUgraphNode = std::ptr::null_mut();
16352 let r = unsafe {
16353 sys::cuGraphAddChildGraphNode(
16354 &mut node,
16355 self.parent,
16356 if deps.is_empty() {
16357 std::ptr::null()
16358 } else {
16359 deps.as_ptr()
16360 },
16361 deps.len(),
16362 graph.cu_graph(),
16363 )
16364 };
16365 if r != sys::CUresult::CUDA_SUCCESS {
16366 return Err(format!("token graph child: {r:?}").into());
16367 }
16368 match (&mut self.group, parallel_group, detached) {
16369 (_, None, true) => self.pending_detached.push(node),
16370 (Some((_, _, members)), Some(_), _) => members.push(node),
16371 _ => self.frontier = vec![node],
16372 }
16373 self.children.push(TokenGraphChild { graph, node, ctx });
16374 Ok(())
16375 }
16376
16377 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
16378 use cudarc::driver::sys;
16379 if let Some((_, _, members)) = self.group.take() {
16380 self.frontier = members;
16381 }
16382 let mut fa_sites = Vec::new();
16385 for child in &self.children {
16386 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
16387 fa_sites.push(site);
16388 }
16389 }
16390 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
16391 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
16392 if r != sys::CUresult::CUDA_SUCCESS {
16393 return Err(format!("token graph instantiate: {r:?}").into());
16394 }
16395 Ok(TokenGraph {
16396 exec,
16397 parent: self.parent,
16398 _children: self.children,
16399 fa_sites,
16400 })
16401 }
16402}
16403
16404fn discover_fa_site(
16407 child_node: cudarc::driver::sys::CUgraphNode,
16408 ctx: cudarc::driver::sys::CUcontext,
16409) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
16410 use cudarc::driver::sys;
16411 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16412 if r == sys::CUresult::CUDA_SUCCESS {
16413 Ok(())
16414 } else {
16415 Err(format!("{what}: {r:?}").into())
16416 }
16417 }
16418 let mut graph: sys::CUgraph = std::ptr::null_mut();
16419 unsafe {
16420 cu_try(
16421 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
16422 "fa-site child GetGraph",
16423 )?;
16424 }
16425 let mut count: usize = 0;
16426 unsafe {
16427 cu_try(
16428 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
16429 "fa-site GetNodes(count)",
16430 )?;
16431 }
16432 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
16433 unsafe {
16434 cu_try(
16435 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
16436 "fa-site GetNodes",
16437 )?;
16438 }
16439 nodes.truncate(count);
16440 let node_type =
16441 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
16442 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
16443 unsafe {
16444 cu_try(
16445 sys::cuGraphNodeGetType(node, &mut ty),
16446 "fa-site NodeGetType",
16447 )?;
16448 }
16449 Ok(ty)
16450 };
16451 let memsets: Vec<sys::CUgraphNode> = {
16452 let mut v = Vec::new();
16453 for &node in &nodes {
16454 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
16455 v.push(node);
16456 }
16457 }
16458 v
16459 };
16460 if memsets.len() != 3 {
16461 return Ok(None);
16462 }
16463 let dependents =
16465 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
16466 let mut n: usize = 0;
16467 unsafe {
16468 cu_try(
16469 sys::cuGraphNodeGetDependentNodes_v2(
16470 node,
16471 std::ptr::null_mut(),
16472 std::ptr::null_mut(),
16473 &mut n,
16474 ),
16475 "fa-site GetDependentNodes(count)",
16476 )?;
16477 }
16478 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
16479 unsafe {
16480 cu_try(
16481 sys::cuGraphNodeGetDependentNodes_v2(
16482 node,
16483 v.as_mut_ptr(),
16484 std::ptr::null_mut(),
16485 &mut n,
16486 ),
16487 "fa-site GetDependentNodes",
16488 )?;
16489 }
16490 v.truncate(n);
16491 Ok(v)
16492 };
16493 let mut fa: Option<sys::CUgraphNode> = None;
16496 let mut last_memset: Option<sys::CUgraphNode> = None;
16497 for &ms in &memsets {
16498 for dep in dependents(ms)? {
16499 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16500 fa = Some(dep);
16501 last_memset = Some(ms);
16502 }
16503 }
16504 }
16505 let (Some(fa), Some(_last)) = (fa, last_memset) else {
16506 return Ok(None);
16507 };
16508 let mut combine: Option<sys::CUgraphNode> = None;
16509 for dep in dependents(fa)? {
16510 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16511 combine = Some(dep);
16512 }
16513 }
16514 let Some(combine) = combine else {
16515 return Ok(None);
16516 };
16517 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16520 unsafe {
16521 cu_try(
16522 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
16523 "fa-site KernelNodeGetParams",
16524 )?;
16525 }
16526 let arg_i32 =
16527 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
16528 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
16529 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
16531 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16532 unsafe {
16533 cu_try(
16534 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16535 "fa-site MemsetNodeGetParams",
16536 )?;
16537 }
16538 Ok(mp.width)
16539 };
16540 let mut widest = memsets[0];
16541 for &ms in &memsets[1..] {
16542 if width_of(ms)? > width_of(widest)? {
16543 widest = ms;
16544 }
16545 }
16546 let memset_m: Vec<sys::CUgraphNode> =
16547 memsets.iter().copied().filter(|&m| m != widest).collect();
16548 Ok(Some(TokenGraphFaSite {
16549 ctx,
16550 memset_o: widest,
16551 memset_m: [memset_m[0], memset_m[1]],
16552 fa,
16553 combine,
16554 window: win as usize,
16555 n_head: nh as usize,
16556 n_head_kv: nhkv as usize,
16557 head_dim: hd as usize,
16558 }))
16559}
16560
16561pub struct TokenGraph {
16562 exec: cudarc::driver::sys::CUgraphExec,
16563 parent: cudarc::driver::sys::CUgraph,
16564 _children: Vec<TokenGraphChild>,
16565 fa_sites: Vec<TokenGraphFaSite>,
16566}
16567
16568unsafe impl Send for TokenGraph {}
16569
16570impl TokenGraph {
16571 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
16576 use cudarc::driver::sys;
16577 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16578 if r == sys::CUresult::CUDA_SUCCESS {
16579 Ok(())
16580 } else {
16581 Err(format!("{what}: {r:?}").into())
16582 }
16583 }
16584 for site in &self.fa_sites {
16585 let layer_bucket = if site.window > 0 {
16586 bucket.min(site.window)
16587 } else {
16588 bucket
16589 };
16590 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
16591 let nsp = layer_bucket.div_ceil(sp).max(1);
16592 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16594 unsafe {
16595 cu_try(
16596 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
16597 "retarget fa GetParams",
16598 )?;
16599 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
16600 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
16601 params.gridDimY = nsp as u32;
16602 cu_try(
16603 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
16604 "retarget fa SetParams",
16605 )?;
16606 }
16607 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16609 unsafe {
16610 cu_try(
16611 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
16612 "retarget combine GetParams",
16613 )?;
16614 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
16615 cu_try(
16616 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
16617 "retarget combine SetParams",
16618 )?;
16619 }
16620 let set_width =
16622 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
16623 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16624 unsafe {
16625 cu_try(
16626 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16627 "retarget memset GetParams",
16628 )?;
16629 }
16630 mp.width = width;
16631 unsafe {
16632 cu_try(
16633 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
16634 "retarget memset SetParams",
16635 )?;
16636 }
16637 Ok(())
16638 };
16639 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
16640 set_width(site.memset_m[0], site.n_head * nsp)?;
16641 set_width(site.memset_m[1], site.n_head * nsp)?;
16642 }
16643 Ok(())
16644 }
16645
16646 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
16647 use cudarc::driver::sys;
16648 let _main = e.gpu.enter_main()?;
16649 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
16650 if r != sys::CUresult::CUDA_SUCCESS {
16651 return Err(format!("token graph launch: {r:?}").into());
16652 }
16653 Ok(())
16654 }
16655}
16656
16657impl Drop for TokenGraph {
16658 fn drop(&mut self) {
16659 unsafe {
16660 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
16661 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
16662 }
16663 }
16664}
16665
16666std::thread_local! {
16667 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
16668 const { std::cell::RefCell::new(None) };
16669}
16670
16671pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
16673 let builder = TokenGraphBuilder::new()?;
16674 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
16675 Ok(())
16676}
16677
16678pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
16680 let builder = TOKEN_GRAPH_BUILDER
16681 .with(|cell| cell.borrow_mut().take())
16682 .ok_or("token graph build was not begun")?;
16683 builder.finish()
16684}
16685
16686pub fn token_graph_building() -> bool {
16688 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
16689}
16690
16691pub fn graph_section<F>(
16696 engine: &Engine,
16697 parallel_group: Option<u32>,
16698 f: F,
16699) -> Result<(), Box<dyn std::error::Error>>
16700where
16701 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16702{
16703 graph_section_opts(engine, parallel_group, false, false, f)
16704}
16705
16706pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16708where
16709 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16710{
16711 graph_section_opts(engine, None, false, true, f)
16712}
16713
16714pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16717where
16718 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16719{
16720 graph_section_opts(engine, None, true, false, f)
16721}
16722
16723pub fn graph_section_opts<F>(
16724 engine: &Engine,
16725 parallel_group: Option<u32>,
16726 detached: bool,
16727 absorb: bool,
16728 f: F,
16729) -> Result<(), Box<dyn std::error::Error>>
16730where
16731 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16732{
16733 let building = token_graph_building();
16734 if !building {
16735 let mut f = f;
16736 return f();
16737 }
16738 let (child, ctx) = {
16739 let _main = engine.gpu.enter_main()?;
16740 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
16741 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
16742 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
16743 return Err(format!("graph section ctx query: {r:?}").into());
16744 }
16745 let mut f = f;
16746 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
16749 (child, ctx)
16750 };
16751 TOKEN_GRAPH_BUILDER.with(|cell| {
16752 cell.borrow_mut()
16753 .as_mut()
16754 .expect("builder checked above")
16755 .push_child(child, parallel_group, detached, absorb, ctx)
16756 })
16757}