1use crate::Engine;
9use crate::mmq_ffi::{DeviceExpertCsr, ExpertCsr, Fp8GroupedWorkspace};
10use crate::parallel::{PRODUCT_MAX_CARDS, STEP37_TRUNK_LAYERS};
11use cudarc::driver::{CudaEvent, CudaSlice, DeviceSlice};
12use std::ops::Range;
13
14#[allow(clippy::type_complexity)] static DETERM_PREV: std::sync::OnceLock<
18 std::sync::Mutex<std::collections::HashMap<(usize, usize), Vec<f32>>>,
19> = std::sync::OnceLock::new();
20
21const FP8_BLOCK: usize = 128;
22const NATIVE_P2P_PROBE_WORDS: &[usize] = &[4096, 16_384, 262_144, 16_777_216];
23const STEP_GROUPED_FP8_EXPERTS: usize = 288;
24const STEP_GROUPED_FP8_TOP_K: usize = 8;
25const STEP_GROUPED_FP8_WIDTH: usize = 1280;
26
27fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
28 if let Some(limit) = limit
29 && (!limit.is_finite() || limit <= 0.0)
30 {
31 return Err(format!(
32 "Step routed-expert activation limit must be positive and finite, got {limit}"
33 ));
34 }
35 Ok(())
36}
37
38pub(crate) fn routes_prestage_on() -> bool {
61 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
62 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
63}
64
65pub(crate) fn oproj_tail_on() -> bool {
83 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
84 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
85}
86thread_local! {
87 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
88 const { std::cell::Cell::new(None) };
89}
90thread_local! {
91 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
96}
97pub(crate) struct OprojTailScope(());
99pub(crate) fn oproj_tail_scope() -> OprojTailScope {
100 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
101 OprojTailScope(())
102}
103impl Drop for OprojTailScope {
104 fn drop(&mut self) {
105 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
106 OPROJ_TAIL_PENDING.with(|c| c.set(None));
108 }
109}
110thread_local! {
111 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
114}
115pub(crate) fn set_verify_tcol(c: Option<usize>) {
116 VERIFY_TCOL.with(|x| x.set(c));
117}
118pub(crate) fn take_verify_tcol() -> Option<usize> {
119 VERIFY_TCOL.with(|x| x.take())
120}
121
122pub(crate) fn tcol_oproj_on() -> bool {
129 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
130 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
131}
132thread_local! {
133 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
137 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
138}
139pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
140 TCOL_OPROJ_DEFER.with(|x| x.set(c));
141}
142pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
143 TCOL_OPROJ_DEFER.with(|x| x.take())
144}
145pub(crate) fn set_tcol_oproj_stashed() {
146 TCOL_OPROJ_STASHED.with(|x| x.set(true));
147}
148pub(crate) fn take_tcol_oproj_stashed() -> bool {
149 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
150}
151
152pub(crate) fn oproj_tail_eligible() -> bool {
153 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
154}
155pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
156 OPROJ_TAIL_PENDING.with(|c| c.take())
157}
158pub(crate) fn set_oproj_tail(v: (u64, u64)) {
159 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
160}
161
162pub(crate) fn rank0_merge_on() -> bool {
163 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
164 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
165}
166
167pub(crate) fn len_mirror_lazy_on() -> bool {
168 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
169 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
170}
171
172pub(crate) fn fence_memops_on() -> bool {
173 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
174 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
175}
176
177pub(crate) fn moe_direct_on() -> bool {
178 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
179 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
180}
181
182pub(crate) fn fence_rank1_on() -> bool {
194 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
195 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
196}
197
198pub(crate) fn rows_tab_host(
222 parts_rank: &[[u64; 4]],
223 ctr_base: u64,
224 same_session: bool,
225 t: usize,
226) -> Vec<u64> {
227 let mut host = Vec::with_capacity(t * 6);
228 for (r, parts) in parts_rank.iter().enumerate().take(t) {
229 host.extend_from_slice(&[
230 parts[0],
231 parts[1],
232 parts[2],
233 parts[3],
234 if same_session {
235 ctr_base
236 } else {
237 ctr_base + (r as u64) * 4
238 },
239 if same_session {
240 (t - 1 - r) as u64
241 } else {
242 0u64
243 },
244 ]);
245 }
246 host
247}
248
249#[cfg(test)]
253pub(crate) fn retired_rows_tab_key(kp: u64, bp: u64, il: usize, t: usize) -> u64 {
254 kp.rotate_left(17)
255 .wrapping_add(bp)
256 .wrapping_add((il as u64) << 32)
257 .wrapping_add(t as u64)
258 .wrapping_add(1 << 63)
259}
260
261pub(crate) fn rows_tab_restage_on() -> bool {
262 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
263 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_RESTAGE").as_deref() != Ok("0"))
264}
265
266pub(crate) fn rows_tab_stale_scan() -> bool {
275 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
276 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_STALE_SCAN").as_deref() == Ok("1"))
277}
278
279pub(crate) static ROWS_TAB_ENGAGED: std::sync::atomic::AtomicU64 =
280 std::sync::atomic::AtomicU64::new(0);
281pub(crate) static ROWS_TAB_STALE: std::sync::atomic::AtomicU64 =
282 std::sync::atomic::AtomicU64::new(0);
283
284pub(crate) fn spec_fa2_on() -> bool {
285 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
286 crate::step37_door(&ENV, "MEMRA_SPEC_FA2")
287}
288thread_local! {
289 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
292 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
293}
294pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
295 SPEC_FA2_DEFER.with(|x| x.set(c));
296}
297pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
298 SPEC_FA2_DEFER.with(|x| x.take())
299}
300pub(crate) fn set_spec_fa2_stashed() {
301 SPEC_FA2_STASHED.with(|x| x.set(true));
302}
303pub(crate) fn take_spec_fa2_stashed() -> bool {
304 SPEC_FA2_STASHED.with(|x| x.replace(false))
305}
306
307pub(crate) fn sel_mirror_on() -> bool {
308 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
309 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
310}
311
312pub(crate) fn step_nvfp4_ep2_on() -> bool {
319 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
320 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
321}
322
323pub(crate) fn oproj_direct_on() -> bool {
324 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
325 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
326}
327
328fn door_default_on(name: &'static str) -> (bool, &'static str) {
367 let raw = std::env::var(name).ok();
368 door_default_on_value(name, raw.as_deref())
369}
370
371fn door_default_on_value(name: &str, value: Option<&str>) -> (bool, &'static str) {
376 match value {
377 Some("0") => (false, "env=0 (rollback seam)"),
378 Some("1") => (true, "env=1"),
379 None => (true, "default-on"),
380 Some(_) => {
381 eprintln!(
382 "[nvfp4-door] WARN {name} has an unrecognized value; only `0` and `1` are \
383 accepted and the DEFAULT-ON answer is kept. To roll back, set {name}=0."
384 );
385 (true, "default-on (unrecognized value ignored)")
386 }
387 }
388}
389
390pub(crate) fn bank_slot_major_on() -> bool {
410 bank_slot_major_source().0
411}
412
413pub(crate) fn bank_slot_major_source() -> (bool, &'static str) {
415 static ON: std::sync::OnceLock<(bool, &'static str)> = std::sync::OnceLock::new();
416 *ON.get_or_init(|| door_default_on("MEMRA_NVFP4_BANK_SM"))
417}
418
419pub(crate) fn sel_gu_fused_on() -> bool {
428 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
429 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_SEL_GU").as_deref() == Ok("1"))
430}
431
432pub(crate) fn sel_down8_on() -> bool {
456 sel_down8_source().0
457}
458
459pub(crate) fn sel_down8_source() -> (bool, &'static str) {
461 static ON: std::sync::OnceLock<(bool, &'static str)> = std::sync::OnceLock::new();
462 *ON.get_or_init(|| door_default_on("MEMRA_NVFP4_SEL_DOWN8"))
463}
464
465pub(crate) fn raw_copy_bytes(
466 dst: u64,
467 src: u64,
468 bytes: usize,
469 engine: &Engine,
470) -> Result<(), Box<dyn std::error::Error>> {
471 use cudarc::driver::sys;
472 let r = unsafe {
473 sys::cuMemcpyAsync(
474 dst as sys::CUdeviceptr,
475 src as sys::CUdeviceptr,
476 bytes,
477 engine.stream().cu_stream() as sys::CUstream,
478 )
479 };
480 if r == sys::CUresult::CUDA_SUCCESS {
481 Ok(())
482 } else {
483 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
486 eprintln!(
487 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
488 std::backtrace::Backtrace::force_capture()
489 );
490 }
491 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
492 }
493}
494
495pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
496 let silu = gate / (1.0 + (-gate).exp());
497 match limit {
498 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
499 None => silu * up,
500 }
501}
502
503#[derive(Debug, Clone, PartialEq, Eq)]
504struct ExpertOwnerRoutes {
505 rank: usize,
506 selected: Vec<usize>,
507 token_rows: Vec<usize>,
508 global_pairs: Vec<usize>,
509}
510
511#[allow(clippy::manual_is_multiple_of)] fn partition_expert_owner_routes(
513 expert_count: usize,
514 ranks: usize,
515 tokens: usize,
516 experts_per_token: usize,
517 selected: &[usize],
518) -> Result<Vec<ExpertOwnerRoutes>, String> {
519 if expert_count == 0
520 || ranks == 0
521 || tokens == 0
522 || experts_per_token == 0
523 || expert_count % ranks != 0
524 {
525 return Err(format!(
526 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
527 tokens={tokens} experts_per_token={experts_per_token}"
528 ));
529 }
530 let pairs = tokens
531 .checked_mul(experts_per_token)
532 .ok_or("expert-owner route count overflow")?;
533 if selected.len() != pairs {
534 return Err(format!(
535 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
536 selected.len()
537 ));
538 }
539 let per_rank = expert_count / ranks;
540 let mut owners = (0..ranks)
541 .map(|rank| ExpertOwnerRoutes {
542 rank,
543 selected: Vec::new(),
544 token_rows: Vec::new(),
545 global_pairs: Vec::new(),
546 })
547 .collect::<Vec<_>>();
548 for (pair, &expert) in selected.iter().enumerate() {
549 if expert >= expert_count {
550 return Err(format!(
551 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
552 ));
553 }
554 let rank = expert / per_rank;
555 owners[rank].selected.push(expert - rank * per_rank);
556 owners[rank].token_rows.push(pair / experts_per_token);
557 owners[rank].global_pairs.push(pair);
558 }
559 Ok(owners)
560}
561
562fn validate_step_grouped_owner_routes(
563 expert_count: usize,
564 tokens: usize,
565 selected: &[usize],
566) -> Result<usize, String> {
567 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
568 return Err(format!(
569 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
570 experts={expert_count} tokens={tokens}",
571 STEP_GROUPED_FP8_EXPERTS
572 ));
573 }
574 let pairs = tokens
575 .checked_mul(STEP_GROUPED_FP8_TOP_K)
576 .ok_or("official Step owner-grouped FP8 route count overflow")?;
577 if selected.len() != pairs {
578 return Err(format!(
579 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
580 selected.len(),
581 STEP_GROUPED_FP8_TOP_K,
582 ));
583 }
584 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
585 let mut unique = routes.to_vec();
586 unique.sort_unstable();
587 unique.dedup();
588 if unique.len() != STEP_GROUPED_FP8_TOP_K {
589 return Err(format!(
590 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
591 {routes:?}"
592 ));
593 }
594 }
595 Ok(pairs)
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599struct WeightedRouteCombineShape {
600 pairs: usize,
601 max_pairs: usize,
602}
603
604fn validate_weighted_route_combine(
605 width: usize,
606 experts_per_token: usize,
607 max_tokens: usize,
608 tokens: usize,
609 owner_global_pairs: &[&[usize]],
610 route_weights: &[f32],
611) -> Result<WeightedRouteCombineShape, String> {
612 if width == 0
613 || experts_per_token == 0
614 || max_tokens == 0
615 || tokens == 0
616 || tokens > max_tokens
617 || width > i32::MAX as usize
618 || experts_per_token > i32::MAX as usize
619 || tokens > i32::MAX as usize
620 {
621 return Err(format!(
622 "invalid weighted route combine geometry width={width} experts_per_token=\
623 {experts_per_token} tokens={tokens}/{max_tokens}"
624 ));
625 }
626 let pairs = tokens
627 .checked_mul(experts_per_token)
628 .ok_or("weighted route combine pair count overflow")?;
629 let max_pairs = max_tokens
630 .checked_mul(experts_per_token)
631 .ok_or("weighted route combine capacity overflow")?;
632 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
633 return Err(format!(
634 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
635 route_weights.len()
636 ));
637 }
638 let mut seen = vec![false; pairs];
639 let mut observed = 0usize;
640 for pairs_for_owner in owner_global_pairs {
641 observed = observed
642 .checked_add(pairs_for_owner.len())
643 .ok_or("weighted route combine observed pair count overflow")?;
644 for &pair in *pairs_for_owner {
645 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
646 return Err(format!(
647 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
648 ));
649 }
650 }
651 }
652 if observed != pairs || seen.iter().any(|present| !present) {
653 return Err(format!(
654 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
655 ));
656 }
657 Ok(WeightedRouteCombineShape { pairs, max_pairs })
658}
659
660fn cache_rank_rows(
661 rows: &[u8],
662 tokens: usize,
663 local_token_bytes: usize,
664 ranks: usize,
665 rank: usize,
666) -> Result<Vec<u8>, String> {
667 if ranks == 0 || rank >= ranks {
668 return Err(format!(
669 "TP cache rank {rank} is outside a {ranks}-rank layout"
670 ));
671 }
672 let global_token_bytes = local_token_bytes
673 .checked_mul(ranks)
674 .ok_or("TP cache global token-byte overflow")?;
675 let expected = tokens
676 .checked_mul(global_token_bytes)
677 .ok_or("TP cache row-byte overflow")?;
678 if rows.len() != expected {
679 return Err(format!(
680 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
681 rows.len()
682 ));
683 }
684 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
685 for token in 0..tokens {
686 let start = token * global_token_bytes + rank * local_token_bytes;
687 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
688 }
689 Ok(shard)
690}
691
692fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
693 match value {
694 None | Some("") | Some("0") => Ok(false),
695 Some("1") => Ok(true),
696 Some(value) => Err(format!(
697 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
698 )),
699 }
700}
701
702pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
703 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
704}
705
706fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
707 match value {
708 None | Some("") | Some("0") => Ok(false),
709 Some("1") => Ok(true),
710 Some(value) => Err(format!(
711 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
712 )),
713 }
714}
715
716pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
717 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
718}
719
720fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
721 match value {
722 None | Some("") | Some("0") => Ok(false),
723 Some("1") => Ok(true),
724 Some(value) => Err(format!(
725 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
726 )),
727 }
728}
729
730fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
731 match value {
732 None | Some("") | Some("0") => Ok(false),
733 Some("1") => Ok(true),
734 Some(value) => Err(format!(
735 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
736 )),
737 }
738}
739
740pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
743 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
744}
745
746pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
747 parse_step_ep_device_arithmetic(
748 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
749 .ok()
750 .as_deref(),
751 )
752}
753
754fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
755 match value {
756 None | Some("") | Some("0") => Ok(false),
757 Some("1") => Ok(true),
758 Some(value) => Err(format!(
759 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
760 )),
761 }
762}
763
764pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
765 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
766}
767
768fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
769 match value {
770 None | Some("") | Some("0") => Ok(false),
771 Some("1") => Ok(true),
772 Some(value) => Err(format!(
773 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
774 )),
775 }
776}
777
778pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
783 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
784}
785
786fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
787 match value {
788 None | Some("") | Some("0") => Ok(false),
789 Some("1") => Ok(true),
790 Some(value) => Err(format!(
791 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
792 )),
793 }
794}
795
796fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
797 match value {
798 None | Some("") | Some("0") => Ok(false),
799 Some("1") => Ok(true),
800 Some(value) => Err(format!(
801 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
802 )),
803 }
804}
805
806pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
810 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
811}
812
813fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
814 match value {
815 None | Some("") | Some("0") => Ok(false),
816 Some("1") => Ok(true),
817 Some(value) => Err(format!(
818 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
819 )),
820 }
821}
822
823fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
824 match value {
825 None | Some("") | Some("0") => Ok(false),
826 Some("1") => Ok(true),
827 Some(value) => Err(format!(
828 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
829 )),
830 }
831}
832
833pub fn step_tp_dcw_enabled() -> Result<bool, String> {
838 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
839}
840
841pub fn step_tp_graph_enabled() -> Result<bool, String> {
846 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
847}
848
849fn step_tp_graph_headroom_ok(e: &Engine) -> bool {
854 let ok = crate::spec::graph_launch_headroom_ok(e);
855 if !ok {
856 static NOTED: std::sync::Once = std::sync::Once::new();
857 NOTED.call_once(|| crate::spec::graph_replay_suspended_note("step-tp-routes"));
858 }
859 ok
860}
861
862pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
866 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
867}
868
869#[derive(Debug, Clone, PartialEq, Eq)]
870pub struct StepEpLayerSpec {
871 pub layer: usize,
872 pub devices: Vec<usize>,
873}
874
875pub type StepTpLayerSpec = StepEpLayerSpec;
876
877fn parse_auto_parallel_devices(
881 mode: Option<&str>,
882 raw_devices: Option<&str>,
883) -> Result<Option<Vec<usize>>, String> {
884 let mode = match mode {
885 None | Some("") | Some("0") | Some("off") => return Ok(None),
886 Some("auto") => "auto",
887 Some(value) => {
888 return Err(format!(
889 "MEMRA_PARALLEL={value:?} is invalid; expected off or auto"
890 ));
891 }
892 };
893 let raw = raw_devices.ok_or_else(|| {
894 format!("{mode} parallel placement requires MEMRA_PARALLEL_DEVICES=DEVICE,DEVICE[...]")
895 })?;
896 let devices =
897 raw.split(',')
898 .map(|device| {
899 device.trim().parse::<usize>().map_err(|_| {
900 format!("MEMRA_PARALLEL_DEVICES entry {device:?} is not an integer")
901 })
902 })
903 .collect::<Result<Vec<_>, _>>()?;
904 if !(2..=crate::parallel::AUTO_PARALLEL_MAX_CARDS).contains(&devices.len()) {
905 return Err(format!(
906 "MEMRA_PARALLEL=auto requires 2..={} devices, got {}",
907 crate::parallel::AUTO_PARALLEL_MAX_CARDS,
908 devices.len()
909 ));
910 }
911 let mut unique = devices.clone();
912 unique.sort_unstable();
913 unique.dedup();
914 if unique.len() != devices.len() {
915 return Err(format!(
916 "MEMRA_PARALLEL_DEVICES must be distinct, got {devices:?}"
917 ));
918 }
919 Ok(Some(devices))
920}
921
922pub fn auto_parallel_devices() -> Result<Option<Vec<usize>>, String> {
923 parse_auto_parallel_devices(
924 std::env::var("MEMRA_PARALLEL").ok().as_deref(),
925 std::env::var("MEMRA_PARALLEL_DEVICES").ok().as_deref(),
926 )
927}
928
929fn parse_parallel_ep_device_router(value: Option<&str>) -> Result<bool, String> {
930 match value {
931 None | Some("") | Some("0") => Ok(false),
932 Some("1") => Ok(true),
933 Some(value) => Err(format!(
934 "MEMRA_PARALLEL_EP_DEVICE_ROUTER={value:?} is invalid; expected 0 or 1"
935 )),
936 }
937}
938
939pub fn parallel_ep_device_router_enabled() -> Result<bool, String> {
940 parse_parallel_ep_device_router(
941 std::env::var("MEMRA_PARALLEL_EP_DEVICE_ROUTER")
942 .ok()
943 .as_deref(),
944 )
945}
946
947fn parse_parallel_ep_graph(value: Option<&str>) -> Result<bool, String> {
948 match value {
949 None | Some("") | Some("0") => Ok(false),
950 Some("1") => Ok(true),
951 Some(value) => Err(format!(
952 "MEMRA_PARALLEL_EP_GRAPH={value:?} is invalid; expected 0 or 1"
953 )),
954 }
955}
956
957pub fn parallel_ep_graph_enabled() -> Result<bool, String> {
958 parse_parallel_ep_graph(std::env::var("MEMRA_PARALLEL_EP_GRAPH").ok().as_deref())
959}
960
961fn parse_parallel_ep_pair_down(value: Option<&str>) -> Result<bool, String> {
962 match value {
963 None | Some("") | Some("0") => Ok(false),
964 Some("1") => Ok(true),
965 Some(value) => Err(format!(
966 "MEMRA_PARALLEL_EP_PAIR_DOWN={value:?} is invalid; expected 0 or 1"
967 )),
968 }
969}
970
971pub fn parallel_ep_pair_down_enabled() -> Result<bool, String> {
972 parse_parallel_ep_pair_down(std::env::var("MEMRA_PARALLEL_EP_PAIR_DOWN").ok().as_deref())
973}
974
975fn parse_parallel_ep_q8_act(value: Option<&str>) -> Result<bool, String> {
976 match value {
977 None | Some("") | Some("0") => Ok(false),
978 Some("1") => Ok(true),
979 Some(value) => Err(format!(
980 "MEMRA_PARALLEL_EP_Q8_ACT={value:?} is invalid; expected 0 or 1"
981 )),
982 }
983}
984
985pub fn parallel_ep_q8_act_enabled() -> Result<bool, String> {
986 parse_parallel_ep_q8_act(std::env::var("MEMRA_PARALLEL_EP_Q8_ACT").ok().as_deref())
987}
988
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990pub(crate) enum ParallelEpQ8Scope {
991 All,
992 GateUp,
993 Down,
994}
995
996impl ParallelEpQ8Scope {
997 fn label(self) -> &'static str {
998 match self {
999 Self::All => "all",
1000 Self::GateUp => "gate-up",
1001 Self::Down => "down",
1002 }
1003 }
1004}
1005
1006fn parse_parallel_ep_q8_scope(value: Option<&str>) -> Result<Option<ParallelEpQ8Scope>, String> {
1007 match value {
1008 None | Some("") => Ok(None),
1009 Some("all") => Ok(Some(ParallelEpQ8Scope::All)),
1010 Some("gate-up") => Ok(Some(ParallelEpQ8Scope::GateUp)),
1011 Some("down") => Ok(Some(ParallelEpQ8Scope::Down)),
1012 Some(value) => Err(format!(
1013 "MEMRA_PARALLEL_EP_Q8_SCOPE={value:?} is invalid; expected all, gate-up, or down"
1014 )),
1015 }
1016}
1017
1018pub(crate) fn parallel_ep_q8_scope() -> Result<Option<ParallelEpQ8Scope>, String> {
1019 parse_parallel_ep_q8_scope(std::env::var("MEMRA_PARALLEL_EP_Q8_SCOPE").ok().as_deref())
1020}
1021
1022fn parse_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 enum StepTpGateShards<'a> {
1783 F32(&'a [crate::CudaSlice<f32>]),
1784 Bf16(&'a [crate::CudaSlice<u8>]),
1785}
1786
1787pub struct StepTpDecodeV2Ws {
1788 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1792 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1793 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1794 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1795 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1796 pub(crate) tcol_cap: usize,
1797 w8_aq: Vec<CudaSlice<i8>>,
1801 w8_ad: Vec<CudaSlice<f32>>,
1802 w8_in: usize,
1803 w8o_aq: Vec<CudaSlice<i8>>,
1806 w8o_ad: Vec<CudaSlice<f32>>,
1807 w8o_in: usize,
1808 w8t_aq: Vec<CudaSlice<i8>>,
1812 w8t_ad: Vec<CudaSlice<f32>>,
1813 w8t_in: usize,
1814 w8t_oaq: Vec<CudaSlice<i8>>,
1815 w8t_oad: Vec<CudaSlice<f32>>,
1816 w8t_oin: usize,
1817 w8t_cap: usize,
1818 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1825 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1826 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1827 pub(crate) fa2_cap: usize,
1828 rope_k_t: Vec<CudaSlice<f32>>,
1832 rope_ctr_t: Vec<CudaSlice<u32>>,
1833 rope_pos_t: Vec<CudaSlice<i32>>,
1834 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1838 rows_tab_t: Vec<CudaSlice<u64>>,
1848 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1852 tcol_gated: Vec<CudaSlice<f32>>,
1853 tcol_opart: Vec<CudaSlice<f32>>,
1854 tcol_opeer: Option<CudaSlice<f32>>,
1855 tcol_omix: Option<CudaSlice<f32>>,
1856 tcol_ocap: usize,
1857 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1860 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1861 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1862 pub(crate) q: Vec<CudaSlice<f32>>,
1863 pub(crate) k: Vec<CudaSlice<f32>>,
1864 pub(crate) pos: Vec<CudaSlice<i32>>,
1865 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1867 pub(crate) gate: Vec<CudaSlice<f32>>,
1868 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1869 pub(crate) gated: Vec<CudaSlice<f32>>,
1870 o_partials: Vec<Vec<CudaSlice<f32>>>,
1872 raw_o_partials: Vec<Vec<u64>>,
1876 raw_k: Vec<u64>,
1877 raw_v_raw: Vec<u64>,
1878 ev_rank: Vec<CudaEvent>,
1880 peer_partial: CudaSlice<f32>,
1882 reduce_a: CudaSlice<f32>,
1883 reduce_b: CudaSlice<f32>,
1884 zeros: CudaSlice<f32>,
1886 pub(crate) k_shadow: CudaSlice<f32>,
1887 pub(crate) v_shadow: CudaSlice<f32>,
1888 ev_refresh: CudaEvent,
1889 ev_oproj: CudaEvent,
1890 gate_e: CudaSlice<f32>,
1892 pub(crate) h_stage: Option<CudaSlice<f32>>,
1895 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1896 attn_in: Vec<CudaSlice<f32>>,
1900 raw_h_stage: u64,
1902 raw_pos_stage: u64,
1903 raw_attn_in: Vec<u64>,
1904 raw_pos: Vec<u64>,
1905 raw_o_partial1: u64,
1906 raw_peer_partial: u64,
1907 raw_k1: u64,
1908 raw_v1: u64,
1909 raw_k_shadow: u64,
1910 raw_v_shadow: u64,
1911 raw_mixed_stage_e: u64,
1915 raw_reduce_a: u64,
1916 raw_shadow_stage_e: (u64, u64),
1917 ev_entry: CudaEvent,
1918 e_device: usize,
1919 local_q_dim: usize,
1921 local_kv_dim: usize,
1922 heads: usize,
1923 pub(crate) o_out: usize,
1924 o_block_cols: usize,
1925 blocks_per_rank: usize,
1926}
1927
1928impl TpE4m3HostBounce {
1929 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1930 Self::new_inner(devices, false, false, false, false)
1931 }
1932
1933 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1934 Self::new_inner(devices, false, true, false, false)
1935 }
1936
1937 pub fn new_native_p2p_device_arithmetic(
1938 devices: &[usize],
1939 ) -> Result<Self, Box<dyn std::error::Error>> {
1940 Self::new_inner(devices, false, true, true, false)
1941 }
1942
1943 pub(crate) fn new_configured(
1944 devices: &[usize],
1945 native_p2p: bool,
1946 ep_device_arithmetic: bool,
1947 bulk_p2p: bool,
1948 ) -> Result<Self, Box<dyn std::error::Error>> {
1949 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1950 }
1951
1952 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1957 Self::new_inner(&[device], true, false, false, false)
1958 }
1959
1960 fn new_inner(
1961 devices: &[usize],
1962 allow_single_rank: bool,
1963 native_p2p: bool,
1964 ep_device_arithmetic: bool,
1965 bulk_p2p: bool,
1966 ) -> Result<Self, Box<dyn std::error::Error>> {
1967 if ep_device_arithmetic && !native_p2p {
1968 return Err("device-resident EP arithmetic requires native P2P".into());
1969 }
1970 if bulk_p2p && !native_p2p {
1971 return Err("bulk TP transport requires native P2P".into());
1972 }
1973 let minimum = if allow_single_rank { 1 } else { 2 };
1974 if !(minimum..=8).contains(&devices.len()) {
1975 return Err(format!(
1976 "TP reference requires {minimum}..=8 devices, got {}",
1977 devices.len()
1978 )
1979 .into());
1980 }
1981 let mut unique = devices.to_vec();
1982 unique.sort_unstable();
1983 unique.dedup();
1984 if unique.len() != devices.len() {
1985 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1986 }
1987 let ranks = devices
1988 .iter()
1989 .map(|&device| Engine::new(device))
1990 .collect::<Result<Vec<_>, _>>()?;
1991 if native_p2p {
1992 configure_native_p2p(&ranks, devices)?;
1993 }
1994 if allow_single_rank {
1995 eprintln!(
1996 "[tp] canonical oracle transport=local device={} performance_claim=false",
1997 devices[0]
1998 );
1999 } else if native_p2p {
2000 if ep_device_arithmetic {
2001 eprintln!(
2002 "[tp] correctness transport=native-p2p devices={devices:?} \
2003 native_p2p=true activation=device-host-exact \
2004 accumulation=device-host-exact output=root-readback \
2005 bulk_p2p={bulk_p2p} performance_claim=false"
2006 );
2007 } else {
2008 eprintln!(
2009 "[tp] correctness transport=native-p2p devices={devices:?} \
2010 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
2011 performance_claim=false"
2012 );
2013 }
2014 } else {
2015 eprintln!(
2016 "[tp] correctness transport=host-bounce devices={devices:?} \
2017 native_p2p=false performance_claim=false"
2018 );
2019 }
2020 Ok(Self {
2021 devices: devices.to_vec(),
2022 ranks,
2023 native_p2p,
2024 ep_device_arithmetic,
2025 bulk_p2p,
2026 decode_v2: std::sync::Mutex::new(Vec::new()),
2027 })
2028 }
2029
2030 pub fn devices(&self) -> &[usize] {
2031 &self.devices
2032 }
2033
2034 pub fn native_p2p(&self) -> bool {
2035 self.native_p2p
2036 }
2037
2038 pub fn bulk_p2p(&self) -> bool {
2039 self.bulk_p2p
2040 }
2041
2042 pub fn expert_activation_label(&self) -> &'static str {
2043 if self.ep_device_arithmetic {
2044 "device-host-exact"
2045 } else {
2046 "host-canonical"
2047 }
2048 }
2049
2050 pub fn expert_accumulation_label(&self) -> &'static str {
2051 self.expert_activation_label()
2052 }
2053
2054 pub fn expert_output_label(&self) -> &'static str {
2055 if self.ep_device_arithmetic {
2056 "root-readback"
2057 } else {
2058 "host-accumulated"
2059 }
2060 }
2061
2062 pub fn transport_label(&self) -> &'static str {
2063 if self.devices.len() == 1 {
2064 "local"
2065 } else if self.native_p2p {
2066 "native-p2p"
2067 } else {
2068 "host-bounce"
2069 }
2070 }
2071
2072 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
2073 self.ranks
2074 .iter()
2075 .map(|rank| rank.ctx().name().map_err(Into::into))
2076 .collect()
2077 }
2078
2079 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
2085 self.ranks.get(rank)
2086 }
2087
2088 pub fn allocate_tp_kv_cache(
2089 &self,
2090 kv_dim_k: usize,
2091 kv_dim_v: usize,
2092 capacity: usize,
2093 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2094 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
2095 }
2096
2097 pub fn allocate_tp_swa_kv_cache(
2098 &self,
2099 kv_dim_k: usize,
2100 kv_dim_v: usize,
2101 capacity: usize,
2102 window: usize,
2103 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2104 if window == 0 {
2105 return Err("TP SWA KV window must be nonzero".into());
2106 }
2107 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
2108 }
2109
2110 fn allocate_tp_kv_cache_inner(
2111 &self,
2112 kv_dim_k: usize,
2113 kv_dim_v: usize,
2114 capacity: usize,
2115 window: Option<usize>,
2116 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2117 if capacity == 0 || capacity > i32::MAX as usize {
2118 return Err(
2119 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
2120 );
2121 }
2122 let tp = self.ranks.len();
2123 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
2124 let physical_rows = window
2125 .map(|window| crate::cache::swa_ring_rows(window, capacity))
2126 .unwrap_or(capacity);
2127 let k_plane_bytes = physical_rows
2128 .checked_mul(shape.k_token_bytes)
2129 .and_then(|bytes| bytes.checked_add(8))
2130 .ok_or("TP KV K plane-byte overflow")?;
2131 let v_plane_bytes = physical_rows
2132 .checked_mul(shape.v_token_bytes)
2133 .and_then(|bytes| bytes.checked_add(8))
2134 .ok_or("TP KV V plane-byte overflow")?;
2135 let mut ranks = Vec::with_capacity(tp);
2136 for engine in &self.ranks {
2137 let _main = engine.gpu.enter_main()?;
2138 ranks.push(ResidentTpKvCacheRank::new(
2139 engine.alloc_u8(k_plane_bytes)?,
2140 engine.alloc_u8(v_plane_bytes)?,
2141 engine.htod_i32(&[0])?,
2142 ));
2143 }
2144 Ok(match window {
2145 Some(window) => ResidentTpKvCache::new_swa(
2146 ranks,
2147 shape.kv_dim_k,
2148 shape.kv_dim_v,
2149 shape.k_token_bytes,
2150 shape.v_token_bytes,
2151 capacity,
2152 window,
2153 ),
2154 None => ResidentTpKvCache::new(
2155 ranks,
2156 shape.kv_dim_k,
2157 shape.kv_dim_v,
2158 shape.k_token_bytes,
2159 shape.v_token_bytes,
2160 capacity,
2161 ),
2162 })
2163 }
2164
2165 pub fn grow_tp_kv_cache(
2166 &self,
2167 source: &ResidentTpKvCache,
2168 target_capacity: usize,
2169 rows: usize,
2170 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2171 self.validate_tp_kv_cache(source)?;
2172 let plan = source.prepare_grow(target_capacity, rows)?;
2173 let ranks = self.ranks.len();
2174 let global_k = source
2175 .kv_dim_k()
2176 .checked_mul(ranks)
2177 .ok_or("TP KV grow global K dimension overflow")?;
2178 let global_v = source
2179 .kv_dim_v()
2180 .checked_mul(ranks)
2181 .ok_or("TP KV grow global V dimension overflow")?;
2182 let mut target = match source.ring_window() {
2183 Some(window) => {
2184 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
2185 }
2186 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
2187 };
2188 self.validate_tp_kv_cache(&target)?;
2189
2190 for (rank, engine) in self.ranks.iter().enumerate() {
2191 let _main = engine.gpu.enter_main()?;
2192 let src = source
2193 .rank(rank)
2194 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
2195 let dst = target
2196 .rank_mut(rank)
2197 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
2198 if plan.k_bytes() > 0 {
2199 engine.copy_u8_range_into(
2200 dst.k_mut(),
2201 0,
2202 src.k(),
2203 plan.source_row() * source.k_tok_bytes(),
2204 plan.k_bytes(),
2205 )?;
2206 }
2207 if plan.v_bytes() > 0 {
2208 engine.copy_u8_range_into(
2209 dst.v_mut(),
2210 0,
2211 src.v(),
2212 plan.source_row() * source.v_tok_bytes(),
2213 plan.v_bytes(),
2214 )?;
2215 }
2216 }
2217 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
2218
2219 for engine in &self.ranks {
2222 let _main = engine.gpu.enter_main()?;
2223 engine.stream().synchronize()?;
2224 }
2225 let physical_copy_rows = plan.copy_rows();
2226 target.publish_grow(plan)?;
2227 eprintln!(
2228 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
2229 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
2230 rank_streams_synchronized=true generation_preserved=true",
2231 rows,
2232 source.capacity(),
2233 target_capacity,
2234 ranks,
2235 physical_copy_rows,
2236 source.ring_window(),
2237 );
2238 Ok(target)
2239 }
2240
2241 pub fn hydrate_tp_kv_cache(
2242 &self,
2243 cache: &mut ResidentTpKvCache,
2244 rows: usize,
2245 k_rows: &[u8],
2246 v_rows: &[u8],
2247 ) -> Result<(), Box<dyn std::error::Error>> {
2248 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
2249 }
2250
2251 pub fn hydrate_tp_kv_cache_from(
2252 &self,
2253 cache: &mut ResidentTpKvCache,
2254 logical_len: usize,
2255 resident_start: usize,
2256 k_rows: &[u8],
2257 v_rows: &[u8],
2258 ) -> Result<(), Box<dyn std::error::Error>> {
2259 self.validate_tp_kv_cache(cache)?;
2260 if cache.committed_len() != 0 || cache.staged_len() != 0 {
2261 return Err(format!(
2262 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
2263 cache.committed_len(),
2264 cache.staged_len()
2265 )
2266 .into());
2267 }
2268 if resident_start > logical_len || logical_len > cache.capacity() {
2269 return Err(format!(
2270 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
2271 cache.capacity(),
2272 )
2273 .into());
2274 }
2275 let rows = logical_len - resident_start;
2276 if rows > cache.physical_capacity() {
2277 return Err(format!(
2278 "TP KV hydration rows {rows} exceed physical capacity {}",
2279 cache.physical_capacity()
2280 )
2281 .into());
2282 }
2283 for rank in 0..self.ranks.len() {
2284 let k_rank =
2285 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
2286 let v_rank =
2287 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
2288 let engine = &self.ranks[rank];
2289 let _main = engine.gpu.enter_main()?;
2290 let rank_cache = cache
2291 .rank_mut(rank)
2292 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2293 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
2294 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
2295 }
2296 cache.publish_hydration(logical_len, resident_start)?;
2297 Ok(())
2298 }
2299
2300 pub fn append_tp_kv_transaction(
2301 &self,
2302 cache: &mut ResidentTpKvCache,
2303 transaction: TpKvTransaction,
2304 k_shards: &[CudaSlice<f32>],
2305 v_shards: &[CudaSlice<f32>],
2306 rows: usize,
2307 ) -> Result<(), Box<dyn std::error::Error>> {
2308 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
2309 }
2310
2311 #[allow(clippy::too_many_arguments)]
2316 pub fn append_tp_kv_transaction_inner(
2317 &self,
2318 cache: &mut ResidentTpKvCache,
2319 transaction: TpKvTransaction,
2320 k_shards: &[CudaSlice<f32>],
2321 v_shards: &[CudaSlice<f32>],
2322 rows: usize,
2323 external_rank_appends: bool,
2324 ) -> Result<(), Box<dyn std::error::Error>> {
2325 self.validate_tp_kv_cache(cache)?;
2326 let plan = cache.prepare_append(transaction, rows)?;
2327 let target = plan.target();
2328 let expected_k = rows
2329 .checked_mul(cache.kv_dim_k())
2330 .ok_or("TP KV K append size overflow")?;
2331 let expected_v = rows
2332 .checked_mul(cache.kv_dim_v())
2333 .ok_or("TP KV V append size overflow")?;
2334 if !external_rank_appends
2337 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
2338 {
2339 return Err(format!(
2340 "TP KV append shard counts k={} v={} != ranks {}",
2341 k_shards.len(),
2342 v_shards.len(),
2343 self.ranks.len()
2344 )
2345 .into());
2346 }
2347 let kv_dim_k = cache.kv_dim_k();
2348 let kv_dim_v = cache.kv_dim_v();
2349 let k_tok_bytes = cache.k_tok_bytes();
2350 let v_tok_bytes = cache.v_tok_bytes();
2351 if let Some(KvRingAppend::Rebase {
2352 src_row,
2353 keep_rows,
2354 new_base,
2355 ..
2356 }) = plan.ring_append()
2357 {
2358 for rank in 0..self.ranks.len() {
2359 let engine = &self.ranks[rank];
2360 let _main = engine.gpu.enter_main()?;
2361 let rank_cache = cache
2362 .rank_mut(rank)
2363 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2364 if keep_rows > 0 {
2365 let k_len = keep_rows
2366 .checked_mul(k_tok_bytes)
2367 .ok_or("TP KV K rebase-byte overflow")?;
2368 let v_len = keep_rows
2369 .checked_mul(v_tok_bytes)
2370 .ok_or("TP KV V rebase-byte overflow")?;
2371 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
2372 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2373 engine.copy_u8_range_into(
2374 &mut k_tmp,
2375 0,
2376 rank_cache.k(),
2377 src_row * k_tok_bytes,
2378 k_len,
2379 )?;
2380 engine.copy_u8_range_into(
2381 &mut v_tmp,
2382 0,
2383 rank_cache.v(),
2384 src_row * v_tok_bytes,
2385 v_len,
2386 )?;
2387 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2388 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2389 }
2390 if rank_cache.base_d().is_some() {
2394 let value = new_base as i32;
2395 let rank_cache = cache
2396 .rank_mut(rank)
2397 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2398 if let Some(base_d) = rank_cache.base_d_mut() {
2399 engine.set_i32_one(base_d, value)?;
2400 }
2401 }
2402 }
2403 }
2404 cache.publish_append_rebase(plan)?;
2405 let write_row = plan.write_row();
2406 for rank in 0..self.ranks.len() {
2407 if external_rank_appends {
2408 break;
2409 }
2410 let engine = &self.ranks[rank];
2411 let _main = engine.gpu.enter_main()?;
2412 if k_shards[rank].len() != expected_k
2413 || v_shards[rank].len() != expected_v
2414 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2415 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2416 {
2417 return Err(format!(
2418 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2419 != expected {expected_k}/{expected_v} on device {}",
2420 k_shards[rank].len(),
2421 k_shards[rank].ordinal(),
2422 v_shards[rank].len(),
2423 v_shards[rank].ordinal(),
2424 engine.ctx().ordinal(),
2425 )
2426 .into());
2427 }
2428 let rank_cache = cache
2429 .rank_mut(rank)
2430 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2431 let (rank_k, rank_v) = rank_cache.planes_mut();
2432 engine.append_kv_quantized_rows(
2433 &k_shards[rank],
2434 &v_shards[rank],
2435 rank_k,
2436 rank_v,
2437 write_row,
2438 rows,
2439 kv_dim_k,
2440 kv_dim_v,
2441 k_tok_bytes,
2442 v_tok_bytes,
2443 Engine::kv_fp8_on(),
2444 )?;
2445 }
2446 if !external_rank_appends {
2447 self.set_tp_kv_len_mirrors(cache, target)?;
2450 }
2451 cache.publish_append_plan(plan)?;
2452 Ok(())
2453 }
2454
2455 pub fn commit_tp_kv_transaction(
2456 &self,
2457 cache: &mut ResidentTpKvCache,
2458 transaction: TpKvTransaction,
2459 accepted_rows: usize,
2460 ) -> Result<(), Box<dyn std::error::Error>> {
2461 self.validate_tp_kv_cache(cache)?;
2462 let target = cache.commit_target(transaction, accepted_rows)?;
2463 self.set_tp_kv_len_mirrors(cache, target)?;
2464 cache.publish_finalize(transaction, target)?;
2465 Ok(())
2466 }
2467
2468 pub fn commit_tp_kv_transaction_external(
2474 &self,
2475 cache: &mut ResidentTpKvCache,
2476 transaction: TpKvTransaction,
2477 accepted_rows: usize,
2478 ) -> Result<(), Box<dyn std::error::Error>> {
2479 self.validate_tp_kv_cache(cache)?;
2480 let target = cache.commit_target(transaction, accepted_rows)?;
2481 cache.publish_finalize(transaction, target)?;
2482 Ok(())
2483 }
2484
2485 pub fn rollback_tp_kv_transaction(
2486 &self,
2487 cache: &mut ResidentTpKvCache,
2488 transaction: TpKvTransaction,
2489 ) -> Result<(), Box<dyn std::error::Error>> {
2490 self.validate_tp_kv_cache(cache)?;
2491 cache.validate_transaction(transaction)?;
2492 let target = transaction.base_len();
2493 self.set_tp_kv_len_mirrors(cache, target)?;
2494 cache.publish_finalize(transaction, target)?;
2495 Ok(())
2496 }
2497
2498 pub fn tp_kv_device_lengths(
2499 &self,
2500 cache: &ResidentTpKvCache,
2501 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2502 self.validate_tp_kv_cache(cache)?;
2503 let mut lengths = Vec::with_capacity(self.ranks.len());
2504 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2505 let _main = engine.gpu.enter_main()?;
2506 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2507 }
2508 Ok(lengths)
2509 }
2510
2511 fn set_tp_kv_len_mirrors(
2512 &self,
2513 cache: &mut ResidentTpKvCache,
2514 len: usize,
2515 ) -> Result<(), Box<dyn std::error::Error>> {
2516 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2517 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2518 let _main = engine.gpu.enter_main()?;
2519 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2520 }
2521 Ok(())
2522 }
2523
2524 fn validate_tp_kv_cache(
2525 &self,
2526 cache: &ResidentTpKvCache,
2527 ) -> Result<(), Box<dyn std::error::Error>> {
2528 if cache.ranks_len() != self.ranks.len() {
2529 return Err(format!(
2530 "TP KV cache ranks {} != runtime ranks {}",
2531 cache.ranks_len(),
2532 self.ranks.len()
2533 )
2534 .into());
2535 }
2536 let expected_k = cache
2537 .physical_capacity()
2538 .checked_mul(cache.k_tok_bytes())
2539 .and_then(|bytes| bytes.checked_add(8))
2540 .ok_or("TP KV K plane validation overflow")?;
2541 let expected_v = cache
2542 .physical_capacity()
2543 .checked_mul(cache.v_tok_bytes())
2544 .and_then(|bytes| bytes.checked_add(8))
2545 .ok_or("TP KV V plane validation overflow")?;
2546 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2547 let device = engine.ctx().ordinal();
2548 if rank_cache.k().len() != expected_k
2549 || rank_cache.v().len() != expected_v
2550 || rank_cache.len_d().len() != 1
2551 || rank_cache.k().ordinal() != device
2552 || rank_cache.v().ordinal() != device
2553 || rank_cache.len_d().ordinal() != device
2554 {
2555 return Err(format!(
2556 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2557 )
2558 .into());
2559 }
2560 }
2561 Ok(())
2562 }
2563
2564 pub fn full(
2565 &self,
2566 matrix: E4m3BlockMatrix<'_>,
2567 activations: &[f32],
2568 tokens: usize,
2569 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2570 matrix.validate()?;
2571 validate_activations(activations, tokens, matrix.in_features)?;
2572 run_rank(&self.ranks[0], matrix, activations, tokens)
2573 }
2574
2575 #[allow(clippy::manual_is_multiple_of)] pub fn column_parallel(
2580 &self,
2581 matrix: E4m3BlockMatrix<'_>,
2582 activations: &[f32],
2583 tokens: usize,
2584 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2585 matrix.validate()?;
2586 validate_activations(activations, tokens, matrix.in_features)?;
2587 let tp = self.ranks.len();
2588 if matrix.out_features % tp != 0 {
2589 return Err(format!(
2590 "column-parallel out_features {} is not divisible by TP={tp}",
2591 matrix.out_features
2592 )
2593 .into());
2594 }
2595 let local_out = matrix.out_features / tp;
2596 if !local_out.is_multiple_of(FP8_BLOCK) {
2597 return Err(format!(
2598 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2599 E4M3 scale block"
2600 )
2601 .into());
2602 }
2603
2604 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2605 let mut rank_outputs = Vec::with_capacity(tp);
2606 for (rank_index, rank) in self.ranks.iter().enumerate() {
2607 let shard = column_shard(matrix, tp, rank_index)?;
2608 let output = run_rank(rank, shard, activations, tokens)?;
2609 let row_start = rank_index * local_out;
2610 for token in 0..tokens {
2611 gathered[token * matrix.out_features + row_start
2612 ..token * matrix.out_features + row_start + local_out]
2613 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2614 }
2615 rank_outputs.push(output);
2616 }
2617 Ok(ColumnParallelResult {
2618 gathered,
2619 rank_outputs,
2620 })
2621 }
2622
2623 pub fn upload_column_parallel(
2624 &self,
2625 matrix: E4m3BlockMatrix<'_>,
2626 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2627 matrix.validate()?;
2628 let tp = self.ranks.len();
2629 validate_column_shape(matrix, tp)?;
2630 let mut ranks = Vec::with_capacity(tp);
2631 for (rank_index, engine) in self.ranks.iter().enumerate() {
2632 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2633 }
2634 Ok(ResidentColumnParallel {
2635 ranks,
2636 out_features: matrix.out_features,
2637 in_features: matrix.in_features,
2638 })
2639 }
2640
2641 pub fn column_parallel_resident(
2642 &self,
2643 matrix: &ResidentColumnParallel,
2644 activations: &[f32],
2645 tokens: usize,
2646 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2647 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2648 validate_activations(activations, tokens, matrix.in_features)?;
2649 let local_out = matrix.out_features / self.ranks.len();
2650 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2651 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2652 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2653 let output = run_resident_rank(engine, shard, activations, tokens)?;
2654 let row_start = rank_index * local_out;
2655 for token in 0..tokens {
2656 gathered[token * matrix.out_features + row_start
2657 ..token * matrix.out_features + row_start + local_out]
2658 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2659 }
2660 rank_outputs.push(output);
2661 }
2662 Ok(ColumnParallelResult {
2663 gathered,
2664 rank_outputs,
2665 })
2666 }
2667
2668 #[allow(clippy::manual_is_multiple_of)] pub fn row_parallel(
2673 &self,
2674 matrix: E4m3BlockMatrix<'_>,
2675 activations: &[f32],
2676 tokens: usize,
2677 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2678 matrix.validate()?;
2679 validate_activations(activations, tokens, matrix.in_features)?;
2680 let tp = self.ranks.len();
2681 if matrix.in_features % tp != 0 {
2682 return Err(format!(
2683 "row-parallel in_features {} is not divisible by TP={tp}",
2684 matrix.in_features
2685 )
2686 .into());
2687 }
2688 let local_in = matrix.in_features / tp;
2689 if !local_in.is_multiple_of(FP8_BLOCK) {
2690 return Err(format!(
2691 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2692 E4M3 scale block"
2693 )
2694 .into());
2695 }
2696
2697 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2698 let mut rank_partials = Vec::with_capacity(tp);
2699 for (rank_index, rank) in self.ranks.iter().enumerate() {
2700 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2701 let local_activations =
2702 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2703 let shard = E4m3BlockMatrix {
2704 codes: &codes,
2705 scales: &scales,
2706 out_features: matrix.out_features,
2707 in_features: local_in,
2708 };
2709 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2710 for (sum, value) in reduced.iter_mut().zip(&partial) {
2711 *sum += *value;
2712 }
2713 rank_partials.push(partial);
2714 }
2715 Ok(RowParallelResult {
2716 reduced,
2717 rank_partials,
2718 })
2719 }
2720
2721 pub fn upload_row_parallel(
2722 &self,
2723 matrix: E4m3BlockMatrix<'_>,
2724 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2725 matrix.validate()?;
2726 let tp = self.ranks.len();
2727 validate_row_shape(matrix, tp)?;
2728 let local_in = matrix.in_features / tp;
2729 let mut ranks = Vec::with_capacity(tp);
2730 for (rank_index, engine) in self.ranks.iter().enumerate() {
2731 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2732 ranks.push(upload_rank(
2733 engine,
2734 E4m3BlockMatrix {
2735 codes: &codes,
2736 scales: &scales,
2737 out_features: matrix.out_features,
2738 in_features: local_in,
2739 },
2740 )?);
2741 }
2742 Ok(ResidentRowParallel {
2743 ranks,
2744 out_features: matrix.out_features,
2745 in_features: matrix.in_features,
2746 })
2747 }
2748
2749 pub fn row_parallel_resident(
2750 &self,
2751 matrix: &ResidentRowParallel,
2752 activations: &[f32],
2753 tokens: usize,
2754 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2755 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2756 validate_activations(activations, tokens, matrix.in_features)?;
2757 let tp = self.ranks.len();
2758 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2759 let mut rank_partials = Vec::with_capacity(tp);
2760 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2761 let local_activations =
2762 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2763 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2764 for (sum, value) in reduced.iter_mut().zip(&partial) {
2765 *sum += *value;
2766 }
2767 rank_partials.push(partial);
2768 }
2769 Ok(RowParallelResult {
2770 reduced,
2771 rank_partials,
2772 })
2773 }
2774
2775 pub fn upload_bf16_column_parallel(
2776 &self,
2777 matrix: Bf16Matrix<'_>,
2778 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2779 self.upload_bf16_column_parallel_inner(matrix, None, false)
2780 }
2781
2782 pub fn upload_step_bf16_column_parallel(
2784 &self,
2785 matrix: Bf16Matrix<'_>,
2786 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2787 self.upload_step_bf16_column_parallel_inner(matrix, false)
2788 }
2789
2790 pub fn upload_step_bf16_column_parallel_f32_mirror(
2795 &self,
2796 matrix: Bf16Matrix<'_>,
2797 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2798 self.upload_step_bf16_column_parallel_inner(matrix, true)
2799 }
2800
2801 fn upload_step_bf16_column_parallel_inner(
2802 &self,
2803 matrix: Bf16Matrix<'_>,
2804 f32_mirror: bool,
2805 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2806 let canonical_chunk_rows =
2807 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2808 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2809 }
2810
2811 #[allow(clippy::manual_is_multiple_of)] fn upload_bf16_column_parallel_inner(
2813 &self,
2814 matrix: Bf16Matrix<'_>,
2815 canonical_chunk_rows: Option<usize>,
2816 f32_mirror: bool,
2817 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2818 matrix.validate()?;
2819 let tp = self.ranks.len();
2820 if matrix.out_features % tp != 0 {
2821 return Err(format!(
2822 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2823 matrix.out_features
2824 )
2825 .into());
2826 }
2827 let mut ranks = Vec::with_capacity(tp);
2828 for (rank, engine) in self.ranks.iter().enumerate() {
2829 ranks.push(upload_bf16_rank(
2830 engine,
2831 bf16_column_shard(matrix, tp, rank)?,
2832 f32_mirror,
2833 )?);
2834 }
2835 Ok(ResidentBf16ColumnParallel {
2836 ranks,
2837 out_features: matrix.out_features,
2838 in_features: matrix.in_features,
2839 canonical_chunk_rows,
2840 })
2841 }
2842
2843 pub fn bf16_column_parallel_resident(
2844 &self,
2845 matrix: &ResidentBf16ColumnParallel,
2846 activations: &[f32],
2847 tokens: usize,
2848 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2849 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2850 validate_activations(activations, tokens, matrix.in_features)?;
2851 let local_out = matrix.out_features / self.ranks.len();
2852 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2853 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2854 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2855 let output = run_resident_bf16_rank(
2856 engine,
2857 shard,
2858 activations,
2859 tokens,
2860 matrix.canonical_chunk_rows,
2861 )?;
2862 for token in 0..tokens {
2863 let src = &output[token * local_out..(token + 1) * local_out];
2864 let dst_start = token * matrix.out_features + rank * local_out;
2865 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2866 }
2867 rank_outputs.push(output);
2868 }
2869 Ok(ColumnParallelResult {
2870 gathered,
2871 rank_outputs,
2872 })
2873 }
2874
2875 pub fn bf16_column_parallel_resident_native(
2882 &self,
2883 matrix: &ResidentBf16ColumnParallel,
2884 activations: &[f32],
2885 tokens: usize,
2886 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2887 let rank_outputs =
2888 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2889 let local_out = matrix.out_features / self.ranks.len();
2890 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2891 }
2892
2893 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2898 self.ranks
2899 .first()
2900 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2901 }
2902
2903 pub fn bf16_column_parallel_resident_native_device(
2916 &self,
2917 matrix: &ResidentBf16ColumnParallel,
2918 root_activation: &CudaSlice<f32>,
2919 tokens: usize,
2920 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2921 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2922 matrix,
2923 root_activation,
2924 tokens,
2925 )?;
2926 let local_out = matrix.out_features / self.ranks.len();
2927 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2928 let root = &self.ranks[0];
2929 let _main = root.gpu.enter_main()?;
2930 root.stream().synchronize()?;
2931 Ok(gathered)
2932 }
2933
2934 pub fn bf16_column_parallel_resident_device_shards_from_root(
2939 &self,
2940 matrix: &ResidentBf16ColumnParallel,
2941 root_activation: &CudaSlice<f32>,
2942 tokens: usize,
2943 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2944 if self.ranks.len() > 1 && !self.native_p2p {
2945 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2946 }
2947 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2948 let values = tokens
2949 .checked_mul(matrix.in_features)
2950 .ok_or("device BF16 column activation size overflow")?;
2951 let root = &self.ranks[0];
2952 if tokens == 0
2953 || root_activation.len() < values
2954 || root_activation.ordinal() != root.ctx().ordinal()
2955 {
2956 return Err("device BF16 column root activation geometry mismatch".into());
2957 }
2958
2959 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2960 let root_input = {
2961 let _main = root.gpu.enter_main()?;
2962 let mut root_input = root.uninit(values)?;
2963 root.stream()
2964 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2965 root_input
2966 };
2967 {
2971 let _main = root.gpu.enter_main()?;
2972 root.stream().synchronize()?;
2973 }
2974 rank_inputs.push(root_input);
2975 for engine in &self.ranks[1..] {
2976 let peer_input = {
2977 let _main = engine.gpu.enter_main()?;
2978 let mut peer_input = engine.uninit(values)?;
2979 engine
2980 .stream()
2981 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2982 peer_input
2983 };
2984 rank_inputs.push(peer_input);
2985 }
2986
2987 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2988 #[allow(clippy::needless_range_loop)]
2989 for rank in 0..self.ranks.len() {
2991 rank_outputs.push(run_resident_bf16_rank_device(
2992 &self.ranks[rank],
2993 &matrix.ranks[rank],
2994 &rank_inputs[rank],
2995 tokens,
2996 matrix.canonical_chunk_rows,
2997 self.bulk_p2p,
2998 )?);
2999 }
3000 Ok(rank_outputs)
3001 }
3002
3003 pub fn bf16_column_parallel_resident_device_shards(
3010 &self,
3011 matrix: &ResidentBf16ColumnParallel,
3012 activations: &[f32],
3013 tokens: usize,
3014 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3015 if self.ranks.len() > 1 && !self.native_p2p {
3016 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3017 }
3018 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3019 validate_activations(activations, tokens, matrix.in_features)?;
3020
3021 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3022 let root_input = {
3023 let root = &self.ranks[0];
3024 let _main = root.gpu.enter_main()?;
3025 root.htod(activations)?
3026 };
3027 {
3033 let root = &self.ranks[0];
3034 let _main = root.gpu.enter_main()?;
3035 root.stream().synchronize()?;
3036 }
3037 rank_inputs.push(root_input);
3038 for engine in &self.ranks[1..] {
3039 let peer_input = {
3040 let _main = engine.gpu.enter_main()?;
3041 let mut peer_input = engine.uninit(activations.len())?;
3042 engine
3043 .stream()
3044 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3045 peer_input
3046 };
3047 rank_inputs.push(peer_input);
3048 }
3049
3050 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3051 #[allow(clippy::needless_range_loop)]
3052 for rank in 0..self.ranks.len() {
3054 rank_outputs.push(run_resident_bf16_rank_device(
3055 &self.ranks[rank],
3056 &matrix.ranks[rank],
3057 &rank_inputs[rank],
3058 tokens,
3059 matrix.canonical_chunk_rows,
3060 self.bulk_p2p,
3061 )?);
3062 }
3063 Ok(rank_outputs)
3064 }
3065
3066 pub fn allocate_replicated_device_rows(
3070 &self,
3071 tokens: usize,
3072 width: usize,
3073 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3074 if self.ranks.len() > 1 && !self.native_p2p {
3075 return Err("replicated device rows require native P2P ranks".into());
3076 }
3077 let values = tokens
3078 .checked_mul(width)
3079 .ok_or("replicated device row size overflow")?;
3080 let rank_lengths = vec![values; self.ranks.len()];
3081 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
3082 let mut ranks = Vec::with_capacity(self.ranks.len());
3083 for engine in &self.ranks {
3084 let _main = engine.gpu.enter_main()?;
3085 ranks.push(engine.uninit(values)?);
3086 }
3087 Ok(ResidentReplicatedDeviceRows {
3088 ranks,
3089 tokens,
3090 width,
3091 })
3092 }
3093
3094 pub fn refresh_replicated_device_rows_from_root(
3096 &self,
3097 rows: &mut ResidentReplicatedDeviceRows,
3098 source: &CudaSlice<f32>,
3099 ) -> Result<(), Box<dyn std::error::Error>> {
3100 if self.ranks.len() > 1 && !self.native_p2p {
3101 return Err("replicated device rows require native P2P ranks".into());
3102 }
3103 validate_replicated_device_rows(&self.ranks, rows)?;
3104 let root = self
3105 .ranks
3106 .first()
3107 .ok_or("replicated rows have no root rank")?;
3108 let values = replicated_device_row_source_values(
3109 rows.tokens,
3110 rows.width,
3111 source.len(),
3112 source.ordinal(),
3113 root.ctx().ordinal(),
3114 )?;
3115 let (root_rows, peer_rows) = rows
3116 .ranks
3117 .split_first_mut()
3118 .ok_or("replicated rows have no root allocation")?;
3119 {
3120 let _main = root.gpu.enter_main()?;
3121 let mut destination = root_rows.slice_mut(0..values);
3122 root.stream()
3123 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
3124 root.stream().synchronize()?;
3125 }
3126 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
3127 let _main = engine.gpu.enter_main()?;
3128 let mut destination = peer_rows.slice_mut(0..values);
3129 engine
3130 .stream()
3131 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
3132 }
3133 Ok(())
3134 }
3135
3136 pub fn upload_replicated_device_rows(
3138 &self,
3139 rows: &[f32],
3140 tokens: usize,
3141 width: usize,
3142 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3143 if self.ranks.len() > 1 && !self.native_p2p {
3144 return Err("replicated device rows require native P2P ranks".into());
3145 }
3146 validate_activations(rows, tokens, width)?;
3147 let root = self
3148 .ranks
3149 .first()
3150 .ok_or("replicated rows have no root rank")?;
3151 let root_rows = {
3152 let _main = root.gpu.enter_main()?;
3153 root.htod(rows)?
3154 };
3155 {
3156 let _main = root.gpu.enter_main()?;
3157 root.stream().synchronize()?;
3158 }
3159 let mut ranks = Vec::with_capacity(self.ranks.len());
3160 ranks.push(root_rows);
3161 for engine in self.ranks.iter().skip(1) {
3162 let _main = engine.gpu.enter_main()?;
3163 let mut peer_rows = engine.uninit(rows.len())?;
3164 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
3165 ranks.push(peer_rows);
3166 }
3167 Ok(ResidentReplicatedDeviceRows {
3168 ranks,
3169 tokens,
3170 width,
3171 })
3172 }
3173
3174 pub fn bf16_column_parallel_resident_replicated_device_shards(
3176 &self,
3177 matrix: &ResidentBf16ColumnParallel,
3178 activations: &ResidentReplicatedDeviceRows,
3179 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3180 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3181 validate_replicated_device_rows(&self.ranks, activations)?;
3182 if activations.width != matrix.in_features {
3183 return Err(format!(
3184 "replicated BF16 column input width {} != matrix width {}",
3185 activations.width, matrix.in_features
3186 )
3187 .into());
3188 }
3189 let mut outputs = Vec::with_capacity(self.ranks.len());
3190 for rank in 0..self.ranks.len() {
3191 outputs.push(run_resident_bf16_rank_device(
3192 &self.ranks[rank],
3193 &matrix.ranks[rank],
3194 &activations.ranks[rank],
3195 activations.tokens,
3196 matrix.canonical_chunk_rows,
3197 self.bulk_p2p,
3198 )?);
3199 }
3200 Ok(outputs)
3201 }
3202
3203 #[allow(clippy::too_many_arguments)]
3205 pub fn upload_sigmoid_topk_router(
3206 &self,
3207 weight: Bf16Matrix<'_>,
3208 correction_bias: &[f32],
3209 active: Option<&[bool]>,
3210 experts_per_token: usize,
3211 scaling_factor: f32,
3212 route_norm: bool,
3213 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
3214 weight.validate()?;
3215 if correction_bias.len() != weight.out_features
3216 || experts_per_token == 0
3217 || experts_per_token > weight.out_features
3218 || !correction_bias.iter().all(|value| value.is_finite())
3219 || !scaling_factor.is_finite()
3220 || scaling_factor <= 0.0
3221 {
3222 return Err(format!(
3223 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
3224 weight.out_features,
3225 weight.in_features,
3226 correction_bias.len(),
3227 experts_per_token,
3228 )
3229 .into());
3230 }
3231 let active_row = active
3232 .map(|mask| {
3233 if mask.len() != weight.out_features {
3234 return Err(format!(
3235 "sigmoid router active mask {} != experts {}",
3236 mask.len(),
3237 weight.out_features
3238 ));
3239 }
3240 Ok(mask
3241 .iter()
3242 .map(|&enabled| u8::from(enabled))
3243 .collect::<Vec<_>>())
3244 })
3245 .transpose()?
3246 .unwrap_or_else(|| vec![1; weight.out_features]);
3247 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
3248 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
3249
3250 let root = self
3251 .ranks
3252 .first()
3253 .ok_or("sigmoid router runtime has no root rank")?;
3254 let _main = root.gpu.enter_main()?;
3255 let bf16 = root.htod_bytes(weight.bytes)?;
3256 let weight_f32 = root.bf16_to_f32(
3257 &bf16.slice(0..bf16.len()),
3258 weight.out_features * weight.in_features,
3259 )?;
3260 Ok(ResidentSigmoidTopKRouter {
3261 weight: weight_f32,
3262 correction_bias: root.htod(correction_bias)?,
3263 active: root.htod_bytes(&active_row)?,
3264 root_device: root.ctx().ordinal(),
3265 input_width: weight.in_features,
3266 expert_count: weight.out_features,
3267 experts_per_token,
3268 active_count,
3269 scaling_factor,
3270 route_norm,
3271 })
3272 }
3273
3274 pub fn sigmoid_topk_replicated_device_rows_host(
3279 &self,
3280 router: &ResidentSigmoidTopKRouter,
3281 input: &ResidentReplicatedDeviceRows,
3282 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
3283 validate_replicated_device_rows(&self.ranks, input)?;
3284 if input.width != router.input_width {
3285 return Err(format!(
3286 "sigmoid router input width {} != resident width {}",
3287 input.width, router.input_width
3288 )
3289 .into());
3290 }
3291 let root = self
3292 .ranks
3293 .first()
3294 .ok_or("sigmoid router runtime has no root rank")?;
3295 let _main = root.gpu.enter_main()?;
3296 if root.ctx().ordinal() != router.root_device
3297 || router.weight.ordinal() != router.root_device
3298 || router.correction_bias.ordinal() != router.root_device
3299 || router.active.ordinal() != router.root_device
3300 {
3301 return Err("sigmoid router root residency changed".into());
3302 }
3303 let logits = root.router_gemv(
3304 &router.weight,
3305 &input.ranks[0],
3306 router.input_width,
3307 router.expert_count,
3308 input.tokens,
3309 )?;
3310 let (selected, weights) = root.moe_router_sigmoid_topk_host(
3311 &logits,
3312 input.tokens,
3313 router.expert_count,
3314 router.experts_per_token,
3315 router.active_count,
3316 &router.correction_bias,
3317 &router.active,
3318 router.scaling_factor,
3319 router.route_norm,
3320 )?;
3321 Ok(SigmoidTopKHostOutput {
3322 logits: root.dtoh(&logits)?,
3323 selected,
3324 weights,
3325 })
3326 }
3327
3328 pub fn upload_replicated_bf16_swiglu(
3330 &self,
3331 gate: Bf16Matrix<'_>,
3332 up: Bf16Matrix<'_>,
3333 down: Bf16Matrix<'_>,
3334 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
3335 gate.validate()?;
3336 up.validate()?;
3337 down.validate()?;
3338 if gate.in_features != up.in_features
3339 || gate.out_features != up.out_features
3340 || down.in_features != gate.out_features
3341 || down.out_features != gate.in_features
3342 {
3343 return Err(format!(
3344 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
3345 gate.out_features,
3346 gate.in_features,
3347 up.out_features,
3348 up.in_features,
3349 down.out_features,
3350 down.in_features,
3351 )
3352 .into());
3353 }
3354 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
3355 let mut up_ranks = Vec::with_capacity(self.ranks.len());
3356 let mut down_ranks = Vec::with_capacity(self.ranks.len());
3357 for engine in &self.ranks {
3358 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
3359 up_ranks.push(upload_bf16_rank(engine, up, false)?);
3360 down_ranks.push(upload_bf16_rank(engine, down, false)?);
3361 }
3362 Ok(ResidentReplicatedBf16SwiGlu {
3363 gate: gate_ranks,
3364 up: up_ranks,
3365 down: down_ranks,
3366 input_width: gate.in_features,
3367 intermediate_width: gate.out_features,
3368 })
3369 }
3370
3371 pub fn replicated_bf16_swiglu_resident_device(
3373 &self,
3374 mlp: &ResidentReplicatedBf16SwiGlu,
3375 input: &ResidentReplicatedDeviceRows,
3376 activation_limit: Option<f32>,
3377 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3378 validate_step_expert_activation_limit(activation_limit)?;
3379 validate_replicated_device_rows(&self.ranks, input)?;
3380 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3381 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3382 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3383 if input.width != mlp.input_width
3384 || mlp.gate.len() != self.ranks.len()
3385 || mlp.up.len() != self.ranks.len()
3386 || mlp.down.len() != self.ranks.len()
3387 {
3388 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3389 }
3390
3391 let mut outputs = Vec::with_capacity(self.ranks.len());
3392 for rank in 0..self.ranks.len() {
3393 let engine = &self.ranks[rank];
3394 let gate = run_resident_bf16_rank_device(
3395 engine,
3396 &mlp.gate[rank],
3397 &input.ranks[rank],
3398 input.tokens,
3399 None,
3400 self.bulk_p2p,
3401 )?;
3402 let up = run_resident_bf16_rank_device(
3403 engine,
3404 &mlp.up[rank],
3405 &input.ranks[rank],
3406 input.tokens,
3407 None,
3408 self.bulk_p2p,
3409 )?;
3410 let _main = engine.gpu.enter_main()?;
3411 let values = input
3412 .tokens
3413 .checked_mul(mlp.intermediate_width)
3414 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3415 let mut activation = engine.uninit(values)?;
3416 if let Some(limit) = activation_limit {
3417 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3418 } else {
3419 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3420 }
3421 outputs.push(run_resident_bf16_rank_device(
3422 engine,
3423 &mlp.down[rank],
3424 &activation,
3425 input.tokens,
3426 None,
3427 self.bulk_p2p,
3428 )?);
3429 }
3430 Ok(ResidentReplicatedDeviceRows {
3431 ranks: outputs,
3432 tokens: input.tokens,
3433 width: mlp.input_width,
3434 })
3435 }
3436
3437 pub fn rms_norm_replicated_device_rows(
3439 &self,
3440 input: &ResidentReplicatedDeviceRows,
3441 weight: &[f32],
3442 eps: f32,
3443 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3444 validate_replicated_device_rows(&self.ranks, input)?;
3445 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3446 return Err(format!(
3447 "replicated RMS norm weight/eps {}/{} != width {}",
3448 weight.len(),
3449 eps,
3450 input.width
3451 )
3452 .into());
3453 }
3454 let mut ranks = Vec::with_capacity(self.ranks.len());
3455 for (rank, engine) in self.ranks.iter().enumerate() {
3456 let _main = engine.gpu.enter_main()?;
3457 let weight = engine.htod(weight)?;
3458 let mut output = engine.uninit(input.tokens * input.width)?;
3459 engine.rms_norm(
3460 &input.ranks[rank],
3461 &weight,
3462 &mut output,
3463 input.width,
3464 input.tokens,
3465 eps,
3466 )?;
3467 ranks.push(output);
3468 }
3469 Ok(ResidentReplicatedDeviceRows {
3470 ranks,
3471 tokens: input.tokens,
3472 width: input.width,
3473 })
3474 }
3475
3476 pub fn add_rms_norm_replicated_device_rows(
3478 &self,
3479 input: &ResidentReplicatedDeviceRows,
3480 update: &ResidentReplicatedDeviceRows,
3481 weight: &[f32],
3482 eps: f32,
3483 ) -> Result<
3484 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3485 Box<dyn std::error::Error>,
3486 > {
3487 validate_replicated_device_rows(&self.ranks, input)?;
3488 validate_replicated_device_rows(&self.ranks, update)?;
3489 if input.tokens != update.tokens
3490 || input.width != update.width
3491 || weight.len() != input.width
3492 || !eps.is_finite()
3493 || eps <= 0.0
3494 {
3495 return Err(format!(
3496 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3497 input.tokens,
3498 input.width,
3499 update.tokens,
3500 update.width,
3501 weight.len(),
3502 )
3503 .into());
3504 }
3505 let values = input.tokens * input.width;
3506 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3507 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3508 for (rank, engine) in self.ranks.iter().enumerate() {
3509 let _main = engine.gpu.enter_main()?;
3510 let weight = engine.htod(weight)?;
3511 let mut residual = engine.uninit(values)?;
3512 let mut normalized = engine.uninit(values)?;
3513 engine.add_rms_norm(
3514 &input.ranks[rank],
3515 &update.ranks[rank],
3516 &weight,
3517 &mut residual,
3518 &mut normalized,
3519 input.width,
3520 input.tokens,
3521 eps,
3522 )?;
3523 residual_ranks.push(residual);
3524 normalized_ranks.push(normalized);
3525 }
3526 Ok((
3527 ResidentReplicatedDeviceRows {
3528 ranks: residual_ranks,
3529 tokens: input.tokens,
3530 width: input.width,
3531 },
3532 ResidentReplicatedDeviceRows {
3533 ranks: normalized_ranks,
3534 tokens: input.tokens,
3535 width: input.width,
3536 },
3537 ))
3538 }
3539
3540 pub fn collect_replicated_device_rows(
3541 &self,
3542 rows: &ResidentReplicatedDeviceRows,
3543 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3544 validate_replicated_device_rows(&self.ranks, rows)?;
3545 let mut outputs = Vec::with_capacity(self.ranks.len());
3546 for (rank, engine) in self.ranks.iter().enumerate() {
3547 let _main = engine.gpu.enter_main()?;
3548 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3549 }
3550 Ok(outputs)
3551 }
3552
3553 #[allow(clippy::manual_is_multiple_of)] pub fn upload_bf16_row_parallel(
3555 &self,
3556 matrix: Bf16Matrix<'_>,
3557 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3558 matrix.validate()?;
3559 let tp = self.ranks.len();
3560 if matrix.in_features % tp != 0 {
3561 return Err(format!(
3562 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3563 matrix.in_features
3564 )
3565 .into());
3566 }
3567 let mut ranks = Vec::with_capacity(tp);
3568 for (rank, engine) in self.ranks.iter().enumerate() {
3569 let shard = bf16_row_shard(matrix, tp, rank)?;
3570 ranks.push(upload_bf16_rank(
3571 engine,
3572 Bf16Matrix {
3573 bytes: &shard,
3574 out_features: matrix.out_features,
3575 in_features: matrix.in_features / tp,
3576 },
3577 false,
3578 )?);
3579 }
3580 Ok(ResidentBf16RowParallel {
3581 ranks,
3582 out_features: matrix.out_features,
3583 in_features: matrix.in_features,
3584 })
3585 }
3586
3587 pub fn bf16_row_parallel_resident(
3588 &self,
3589 matrix: &ResidentBf16RowParallel,
3590 activations: &[f32],
3591 tokens: usize,
3592 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3593 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3594 validate_activations(activations, tokens, matrix.in_features)?;
3595 let tp = self.ranks.len();
3596 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3597 let mut rank_partials = Vec::with_capacity(tp);
3598 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3599 let local_activations =
3600 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3601 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3602 for (sum, value) in reduced.iter_mut().zip(&partial) {
3603 *sum += value;
3604 }
3605 rank_partials.push(partial);
3606 }
3607 Ok(RowParallelResult {
3608 reduced,
3609 rank_partials,
3610 })
3611 }
3612
3613 pub fn upload_step_bf16_row_parallel(
3615 &self,
3616 matrix: Bf16Matrix<'_>,
3617 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3618 self.upload_step_bf16_row_parallel_inner(matrix, false)
3619 }
3620
3621 pub fn upload_step_bf16_row_parallel_f32_mirror(
3622 &self,
3623 matrix: Bf16Matrix<'_>,
3624 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3625 self.upload_step_bf16_row_parallel_inner(matrix, true)
3626 }
3627
3628 fn upload_step_bf16_row_parallel_inner(
3629 &self,
3630 matrix: Bf16Matrix<'_>,
3631 f32_mirror: bool,
3632 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3633 matrix.validate()?;
3634 let tp = self.ranks.len();
3635 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3636 let local_in = matrix.in_features / tp;
3637 let blocks_per_rank = local_in / canonical_chunk_cols;
3638 let mut ranks = Vec::with_capacity(tp);
3639 for (rank, engine) in self.ranks.iter().enumerate() {
3640 let mut blocks = Vec::with_capacity(blocks_per_rank);
3641 for block in 0..blocks_per_rank {
3642 let global_block = rank * blocks_per_rank + block;
3643 let col_start = global_block * canonical_chunk_cols;
3644 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3645 blocks.push(upload_bf16_rank(
3646 engine,
3647 Bf16Matrix {
3648 bytes: &bytes,
3649 out_features: matrix.out_features,
3650 in_features: canonical_chunk_cols,
3651 },
3652 f32_mirror,
3653 )?);
3654 }
3655 ranks.push(blocks);
3656 }
3657 Ok(ResidentStepBf16RowParallel {
3658 ranks,
3659 out_features: matrix.out_features,
3660 in_features: matrix.in_features,
3661 canonical_chunk_cols,
3662 })
3663 }
3664
3665 pub fn step_bf16_row_parallel_resident(
3670 &self,
3671 matrix: &ResidentStepBf16RowParallel,
3672 activations: &[f32],
3673 tokens: usize,
3674 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3675 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3676 validate_activations(activations, tokens, matrix.in_features)?;
3677 let root = &self.ranks[0];
3678 let output_len = tokens
3679 .checked_mul(matrix.out_features)
3680 .ok_or("Step BF16 row output size overflow")?;
3681 let mut reduced = {
3682 let _main = root.gpu.enter_main()?;
3683 root.htod(&vec![0.0f32; output_len])?
3684 };
3685 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3686 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3687 for (block, resident) in blocks.iter().enumerate() {
3688 let global_block = rank * blocks_per_rank + block;
3689 let input = activation_shard(
3690 activations,
3691 tokens,
3692 matrix.in_features,
3693 PRODUCT_MAX_CARDS,
3694 global_block,
3695 );
3696 let partial =
3697 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3698 let next = {
3699 let _main = root.gpu.enter_main()?;
3700 let partial = root.htod(&partial)?;
3701 let mut next = root.uninit(output_len)?;
3702 root.add(&reduced, &partial, &mut next, output_len)?;
3703 next
3704 };
3705 reduced = next;
3706 }
3707 }
3708 let _main = root.gpu.enter_main()?;
3709 root.dtoh(&reduced)
3710 }
3711
3712 pub fn step_bf16_row_parallel_resident_native(
3718 &self,
3719 matrix: &ResidentStepBf16RowParallel,
3720 activations: &[f32],
3721 tokens: usize,
3722 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3723 if self.ranks.len() > 1 && !self.native_p2p {
3724 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3725 }
3726 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3727 validate_activations(activations, tokens, matrix.in_features)?;
3728 let root = &self.ranks[0];
3729 let root_input = {
3730 let _main = root.gpu.enter_main()?;
3731 root.htod(activations)?
3732 };
3733 {
3736 let _main = root.gpu.enter_main()?;
3737 root.stream().synchronize()?;
3738 }
3739 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3740 let _main = root.gpu.enter_main()?;
3741 root.dtoh(&reduced)
3742 }
3743
3744 pub fn step_bf16_row_parallel_resident_native_device(
3752 &self,
3753 matrix: &ResidentStepBf16RowParallel,
3754 root_activation: &CudaSlice<f32>,
3755 tokens: usize,
3756 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3757 if self.ranks.len() > 1 && !self.native_p2p {
3758 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3759 }
3760 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3761 let values = tokens
3762 .checked_mul(matrix.in_features)
3763 .ok_or("device Step BF16 row activation size overflow")?;
3764 let root = &self.ranks[0];
3765 if tokens == 0
3766 || root_activation.len() < values
3767 || root_activation.ordinal() != root.ctx().ordinal()
3768 {
3769 return Err("device Step BF16 row root activation geometry mismatch".into());
3770 }
3771 let root_input = {
3772 let _main = root.gpu.enter_main()?;
3773 let mut root_input = root.uninit(values)?;
3774 root.stream()
3775 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3776 root.stream().synchronize()?; root_input
3778 };
3779 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3780 let _main = root.gpu.enter_main()?;
3781 root.stream().synchronize()?;
3782 Ok(reduced)
3783 }
3784
3785 fn step_bf16_row_native_reduce_from_root(
3790 &self,
3791 matrix: &ResidentStepBf16RowParallel,
3792 root_input: &CudaSlice<f32>,
3793 tokens: usize,
3794 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3795 let root = &self.ranks[0];
3796 let output_len = tokens
3797 .checked_mul(matrix.out_features)
3798 .ok_or("native Step BF16 row output size overflow")?;
3799 let mut reduced = {
3800 let _main = root.gpu.enter_main()?;
3801 root.htod(&vec![0.0f32; output_len])?
3802 };
3803 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3804 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3805 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3806 let mut remote_partial_keepalive = Vec::new();
3807 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3808 for (block, resident) in blocks.iter().enumerate() {
3809 let global_block = rank * blocks_per_rank + block;
3810 let col_start = global_block * matrix.canonical_chunk_cols;
3811 let block_len = tokens
3812 .checked_mul(matrix.canonical_chunk_cols)
3813 .ok_or("native Step BF16 row block size overflow")?;
3814 let block_input = if self.bulk_p2p {
3815 let root_packed = {
3816 let _main = root.gpu.enter_main()?;
3817 let mut root_packed = root.uninit(block_len)?;
3818 root.copy_rows_strided(
3819 root_input,
3820 &mut root_packed,
3821 matrix.canonical_chunk_cols,
3822 tokens,
3823 matrix.in_features,
3824 col_start,
3825 )?;
3826 root_packed
3827 };
3828 if rank == 0 {
3829 root_packed
3830 } else {
3831 {
3834 let _main = root.gpu.enter_main()?;
3835 root.stream().synchronize()?;
3836 }
3837 let engine = &self.ranks[rank];
3838 let _main = engine.gpu.enter_main()?;
3839 let mut block_input = engine.uninit(block_len)?;
3840 engine
3841 .stream()
3842 .memcpy_dtod(&root_packed, &mut block_input)?;
3843 root_packed_keepalive.push(root_packed);
3844 block_input
3845 }
3846 } else {
3847 let engine = &self.ranks[rank];
3848 let _main = engine.gpu.enter_main()?;
3849 let mut block_input = engine.uninit(block_len)?;
3850 for token in 0..tokens {
3851 let source_start = token * matrix.in_features + col_start;
3852 let source = root_input
3853 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3854 let destination_start = token * matrix.canonical_chunk_cols;
3855 let mut destination = block_input.slice_mut(
3856 destination_start..destination_start + matrix.canonical_chunk_cols,
3857 );
3858 engine.stream().memcpy_dtod(&source, &mut destination)?;
3859 }
3860 block_input
3861 };
3862 let partial = run_resident_bf16_rank_device(
3863 &self.ranks[rank],
3864 resident,
3865 &block_input,
3866 tokens,
3867 None,
3868 self.bulk_p2p,
3869 )?;
3870 block_input_keepalive.push(block_input);
3871 let root_partial = if rank == 0 {
3872 partial
3873 } else {
3874 {
3877 let engine = &self.ranks[rank];
3878 let _main = engine.gpu.enter_main()?;
3879 engine.stream().synchronize()?;
3880 }
3881 let _main = root.gpu.enter_main()?;
3882 let mut peer_partial = root.uninit(output_len)?;
3883 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3884 remote_partial_keepalive.push(partial);
3885 peer_partial
3886 };
3887 let next = {
3888 let _main = root.gpu.enter_main()?;
3889 let mut next = root.uninit(output_len)?;
3890 root.add(&reduced, &root_partial, &mut next, output_len)?;
3891 next
3892 };
3893 reduced = next;
3894 }
3895 }
3896 {
3897 let _main = root.gpu.enter_main()?;
3898 root.stream().synchronize()?;
3899 }
3900 drop(remote_partial_keepalive);
3901 drop(root_packed_keepalive);
3902 drop(block_input_keepalive);
3903 Ok(reduced)
3904 }
3905
3906 pub fn step_bf16_row_parallel_resident_root_device(
3909 &self,
3910 matrix: &ResidentStepBf16RowParallel,
3911 rank_activations: &[CudaSlice<f32>],
3912 tokens: usize,
3913 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3914 if self.ranks.len() > 1 && !self.native_p2p {
3915 return Err(
3916 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3917 );
3918 }
3919 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3920 let local_width = matrix.in_features / self.ranks.len();
3921 let shard_len = tokens
3922 .checked_mul(local_width)
3923 .ok_or("device Step BF16 row shard size overflow")?;
3924 if tokens == 0
3925 || rank_activations.len() != self.ranks.len()
3926 || rank_activations
3927 .iter()
3928 .zip(&self.ranks)
3929 .any(|(rows, engine)| {
3930 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3931 })
3932 {
3933 return Err("device Step BF16 row activation shard geometry changed".into());
3934 }
3935
3936 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3937 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3938 let mut partials = Vec::with_capacity(self.ranks.len());
3939 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3940 if blocks.len() != blocks_per_rank {
3941 return Err(format!(
3942 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3943 blocks.len()
3944 )
3945 .into());
3946 }
3947 let engine = &self.ranks[rank];
3948 let _main = engine.gpu.enter_main()?;
3949 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3950 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3951 for (block, resident) in blocks.iter().enumerate() {
3952 let block_len = tokens
3953 .checked_mul(matrix.canonical_chunk_cols)
3954 .ok_or("device Step BF16 row block size overflow")?;
3955 let mut block_input = engine.uninit(block_len)?;
3956 let local_col_start = block * matrix.canonical_chunk_cols;
3957 if self.bulk_p2p {
3958 engine.copy_rows_strided(
3959 &rank_activations[rank],
3960 &mut block_input,
3961 matrix.canonical_chunk_cols,
3962 tokens,
3963 local_width,
3964 local_col_start,
3965 )?;
3966 } else {
3967 for token in 0..tokens {
3968 let source_start = token * local_width + local_col_start;
3969 let source = rank_activations[rank]
3970 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3971 let destination_start = token * matrix.canonical_chunk_cols;
3972 let mut destination = block_input.slice_mut(
3973 destination_start..destination_start + matrix.canonical_chunk_cols,
3974 );
3975 engine.stream().memcpy_dtod(&source, &mut destination)?;
3976 }
3977 }
3978 let partial = run_resident_bf16_rank_device(
3979 engine,
3980 resident,
3981 &block_input,
3982 tokens,
3983 None,
3984 self.bulk_p2p,
3985 )?;
3986 rank_inputs.push(block_input);
3987 rank_partials.push(partial);
3988 }
3989 block_inputs.push(rank_inputs);
3990 partials.push(rank_partials);
3991 }
3992 for engine in self.ranks.iter().skip(1) {
3993 let _main = engine.gpu.enter_main()?;
3994 engine.stream().synchronize()?;
3995 }
3996
3997 let output_len = tokens
3998 .checked_mul(matrix.out_features)
3999 .ok_or("device Step BF16 row output size overflow")?;
4000 let root = &self.ranks[0];
4001 let _main = root.gpu.enter_main()?;
4002 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
4003 let mut remote_partials = Vec::new();
4004 for (rank, rank_partials) in partials.into_iter().enumerate() {
4005 for partial in rank_partials {
4006 let root_partial = if rank == 0 {
4007 partial
4008 } else {
4009 let mut peer_partial = root.uninit(output_len)?;
4010 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4011 remote_partials.push(partial);
4012 peer_partial
4013 };
4014 let mut next = root.uninit(output_len)?;
4015 root.add(&reduced, &root_partial, &mut next, output_len)?;
4016 reduced = next;
4017 }
4018 }
4019 root.stream().synchronize()?;
4020 drop(remote_partials);
4021 drop(block_inputs);
4022 Ok(reduced)
4023 }
4024
4025 pub fn step_bf16_row_parallel_resident_replicated_device(
4027 &self,
4028 matrix: &ResidentStepBf16RowParallel,
4029 rank_activations: &[CudaSlice<f32>],
4030 tokens: usize,
4031 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4032 let reduced =
4033 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
4034 let output_len = tokens
4035 .checked_mul(matrix.out_features)
4036 .ok_or("device Step BF16 row output size overflow")?;
4037 let mut ranks = Vec::with_capacity(self.ranks.len());
4038 ranks.push(reduced);
4039 for engine in self.ranks.iter().skip(1) {
4040 let _main = engine.gpu.enter_main()?;
4041 let mut peer_output = engine.uninit(output_len)?;
4042 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
4043 ranks.push(peer_output);
4044 }
4045 Ok(ResidentReplicatedDeviceRows {
4046 ranks,
4047 tokens,
4048 width: matrix.out_features,
4049 })
4050 }
4051
4052 pub fn upload_expert(
4053 &self,
4054 gate: E4m3BlockMatrix<'_>,
4055 up: E4m3BlockMatrix<'_>,
4056 down: E4m3BlockMatrix<'_>,
4057 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
4058 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4059 return Err("TP expert gate/up dimensions differ".into());
4060 }
4061 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4062 return Err(format!(
4063 "TP expert down {}x{} does not invert gate/up {}x{}",
4064 down.out_features, down.in_features, gate.out_features, gate.in_features
4065 )
4066 .into());
4067 }
4068 Ok(ResidentTpExpert {
4069 gate: self.upload_column_parallel(gate)?,
4070 up: self.upload_column_parallel(up)?,
4071 down: self.upload_row_parallel(down)?,
4072 input_width: gate.in_features,
4073 expert_width: gate.out_features,
4074 })
4075 }
4076
4077 pub fn run_expert(
4078 &self,
4079 expert: &ResidentTpExpert,
4080 input: &[f32],
4081 tokens: usize,
4082 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4083 validate_activations(input, tokens, expert.input_width)?;
4084 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
4085 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
4086 let activated: Vec<f32> = gate
4087 .gathered
4088 .iter()
4089 .zip(&up.gathered)
4090 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4091 .collect();
4092 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
4093 Ok(self
4094 .row_parallel_resident(&expert.down, &activated, tokens)?
4095 .reduced)
4096 }
4097
4098 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel(
4100 &self,
4101 gate: E4m3ExpertBank<'_>,
4102 up: E4m3ExpertBank<'_>,
4103 down: E4m3ExpertBank<'_>,
4104 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
4105 gate.validate()?;
4106 up.validate()?;
4107 down.validate()?;
4108 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4109 return Err("EP gate/up/down expert counts differ".into());
4110 }
4111 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4112 return Err("EP gate/up dimensions differ".into());
4113 }
4114 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4115 return Err(format!(
4116 "EP down {}x{} does not invert gate/up {}x{}",
4117 down.out_features, down.in_features, gate.out_features, gate.in_features
4118 )
4119 .into());
4120 }
4121 if gate.expert_count % self.ranks.len() != 0 {
4122 return Err(format!(
4123 "EP expert count {} is not divisible by {} ranks",
4124 gate.expert_count,
4125 self.ranks.len()
4126 )
4127 .into());
4128 }
4129
4130 let per_rank = gate.expert_count / self.ranks.len();
4131 let mut ranks = Vec::with_capacity(self.ranks.len());
4132 for (rank, engine) in self.ranks.iter().enumerate() {
4133 let expert_range = rank * per_rank..(rank + 1) * per_rank;
4134 ranks.push(ResidentEpRank {
4135 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
4136 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
4137 down: upload_expert_bank_rank(engine, down, expert_range)?,
4138 });
4139 }
4140 Ok(ResidentExpertParallel {
4141 ranks,
4142 expert_count: gate.expert_count,
4143 input_width: gate.in_features,
4144 expert_width: gate.out_features,
4145 })
4146 }
4147
4148 #[allow(clippy::too_many_arguments)]
4154 pub fn prepare_step_grouped_fp8_gate(
4155 &self,
4156 gate: E4m3ExpertBank<'_>,
4157 up: E4m3ExpertBank<'_>,
4158 down: E4m3ExpertBank<'_>,
4159 input: &[f32],
4160 tokens: usize,
4161 selected: &[usize],
4162 activation_limit: Option<f32>,
4163 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
4164 gate.validate()?;
4165 up.validate()?;
4166 down.validate()?;
4167 validate_step_expert_activation_limit(activation_limit)?;
4168 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
4169 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
4170 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
4171 {
4172 return Err(format!(
4173 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
4174 got gate/up/down={}/{}/{}",
4175 gate.expert_count, up.expert_count, down.expert_count,
4176 )
4177 .into());
4178 }
4179 if gate.in_features != up.in_features
4180 || gate.out_features != STEP_GROUPED_FP8_WIDTH
4181 || up.out_features != STEP_GROUPED_FP8_WIDTH
4182 || down.in_features != STEP_GROUPED_FP8_WIDTH
4183 || down.out_features != gate.in_features
4184 {
4185 return Err(format!(
4186 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
4187 gate.out_features,
4188 gate.in_features,
4189 up.out_features,
4190 up.in_features,
4191 down.out_features,
4192 down.in_features,
4193 )
4194 .into());
4195 }
4196 validate_activations(input, tokens, gate.in_features)?;
4197 let pairs = tokens
4198 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4199 .ok_or("official Step grouped FP8 route count overflow")?;
4200 if selected.len() != pairs {
4201 return Err(format!(
4202 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
4203 ({pairs})",
4204 selected.len()
4205 )
4206 .into());
4207 }
4208 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
4209 let mut unique = routes.to_vec();
4210 unique.sort_unstable();
4211 unique.dedup();
4212 if unique.len() != STEP_GROUPED_FP8_TOP_K {
4213 return Err(format!(
4214 "official Step grouped FP8 token {token} routes are not top-8 unique: \
4215 {routes:?}"
4216 )
4217 .into());
4218 }
4219 }
4220
4221 let engine = self
4222 .ranks
4223 .first()
4224 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4225 let _main = engine.gpu.enter_main()?;
4226 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
4227 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
4228 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
4229 let down = upload_expert_bank_rank(engine, down, expert_range)?;
4230 let input = engine.htod(input)?;
4231 let route_csr = ExpertCsr::from_token_routes(
4232 STEP_GROUPED_FP8_EXPERTS,
4233 tokens,
4234 STEP_GROUPED_FP8_TOP_K,
4235 selected,
4236 )?
4237 .upload(engine)?;
4238 let pair_rows = (0..pairs).collect::<Vec<_>>();
4239 let down_csr =
4240 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
4241 .upload(engine)?;
4242 let gate_workspace =
4243 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
4244 let up_workspace =
4245 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
4246 let down_workspace =
4247 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
4248 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
4249 Ok(PreparedStepGroupedFp8Gate {
4250 device: engine.ctx().ordinal(),
4251 gate,
4252 up,
4253 down,
4254 input,
4255 route_csr,
4256 down_csr,
4257 gate_workspace,
4258 up_workspace,
4259 down_workspace,
4260 activation,
4261 activation_limit,
4262 tokens,
4263 pairs,
4264 })
4265 }
4266
4267 pub fn run_step_grouped_fp8_gate(
4269 &self,
4270 plan: &mut PreparedStepGroupedFp8Gate,
4271 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4272 let engine = self
4273 .ranks
4274 .first()
4275 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4276 if engine.ctx().ordinal() != plan.device {
4277 return Err(format!(
4278 "official Step grouped FP8 plan device {} != rank-zero device {}",
4279 plan.device,
4280 engine.ctx().ordinal()
4281 )
4282 .into());
4283 }
4284 let _main = engine.gpu.enter_main()?;
4285
4286 plan.gate_workspace.quantize(engine, &plan.input)?;
4287 plan.gate_workspace.project(
4288 engine,
4289 &plan.gate.codes,
4290 &plan.gate.scales,
4291 &plan.route_csr,
4292 plan.gate.code_stride,
4293 plan.gate.scale_stride,
4294 1.0,
4295 )?;
4296 plan.up_workspace.quantize(engine, &plan.input)?;
4297 plan.up_workspace.project(
4298 engine,
4299 &plan.up.codes,
4300 &plan.up.scales,
4301 &plan.route_csr,
4302 plan.up.code_stride,
4303 plan.up.scale_stride,
4304 1.0,
4305 )?;
4306 if let Some(limit) = plan.activation_limit {
4307 engine.silu_clamped_mul_host_expf(
4308 plan.gate_workspace.output(),
4309 plan.up_workspace.output(),
4310 limit,
4311 &mut plan.activation,
4312 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4313 )?;
4314 } else {
4315 engine.silu_mul_host_expf(
4316 plan.gate_workspace.output(),
4317 plan.up_workspace.output(),
4318 &mut plan.activation,
4319 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4320 )?;
4321 }
4322 plan.down_workspace.quantize(engine, &plan.activation)?;
4323 plan.down_workspace.project(
4324 engine,
4325 &plan.down.codes,
4326 &plan.down.scales,
4327 &plan.down_csr,
4328 plan.down.code_stride,
4329 plan.down.scale_stride,
4330 1.0,
4331 )?;
4332
4333 Ok(StepGroupedFp8ProjectionOutput {
4334 gate: engine.dtoh(plan.gate_workspace.output())?,
4335 up: engine.dtoh(plan.up_workspace.output())?,
4336 down: engine.dtoh(plan.down_workspace.output())?,
4337 })
4338 }
4339
4340 pub fn prepare_step_grouped_expert_parallel_gate(
4341 &self,
4342 experts: &ResidentExpertParallel,
4343 input: &[f32],
4344 tokens: usize,
4345 selected: &[usize],
4346 activation_limit: Option<f32>,
4347 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4348 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
4349 experts,
4350 input,
4351 tokens,
4352 selected,
4353 activation_limit,
4354 tokens,
4355 )
4356 }
4357
4358 #[allow(clippy::too_many_arguments)]
4359 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
4360 &self,
4361 experts: &ResidentExpertParallel,
4362 input: &[f32],
4363 tokens: usize,
4364 selected: &[usize],
4365 activation_limit: Option<f32>,
4366 max_tokens: usize,
4367 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4368 if !self.native_p2p || !self.ep_device_arithmetic {
4369 return Err(
4370 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
4371 );
4372 }
4373 validate_step_expert_activation_limit(activation_limit)?;
4374 validate_ep_residency(&self.ranks, experts)?;
4375 validate_activations(input, tokens, experts.input_width)?;
4376 if max_tokens < tokens || max_tokens > i32::MAX as usize {
4377 return Err(format!(
4378 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
4379 )
4380 .into());
4381 }
4382 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4383 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4384 {
4385 return Err(format!(
4386 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4387 STEP_GROUPED_FP8_EXPERTS,
4388 STEP_GROUPED_FP8_WIDTH,
4389 experts.expert_count,
4390 experts.expert_width,
4391 )
4392 .into());
4393 }
4394 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4395 let max_pairs = max_tokens
4396 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4397 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4398 let input_capacity = max_tokens
4399 .checked_mul(experts.input_width)
4400 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4401
4402 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4403 for engine in &self.ranks {
4404 let _main = engine.gpu.enter_main()?;
4405 rank_inputs.push(engine.uninit(input_capacity)?);
4406 }
4407
4408 let mut owners = Vec::with_capacity(self.ranks.len());
4409 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4410 if rank.gate.expert_range != rank.up.expert_range
4411 || rank.gate.expert_range != rank.down.expert_range
4412 {
4413 return Err(format!(
4414 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4415 owner_rank
4416 )
4417 .into());
4418 }
4419 let local_experts = rank.gate.expert_range.len();
4420 let engine = &self.ranks[owner_rank];
4421 let _main = engine.gpu.enter_main()?;
4422 let route_csr =
4423 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4424 let down_csr =
4425 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4426 let gate_workspace = Fp8GroupedWorkspace::new(
4427 engine,
4428 experts.input_width,
4429 experts.expert_width,
4430 max_tokens,
4431 max_pairs,
4432 )?;
4433 let up_workspace = Fp8GroupedWorkspace::new(
4434 engine,
4435 experts.input_width,
4436 experts.expert_width,
4437 max_tokens,
4438 max_pairs,
4439 )?;
4440 let down_workspace = Fp8GroupedWorkspace::new(
4441 engine,
4442 experts.expert_width,
4443 experts.input_width,
4444 max_pairs,
4445 max_pairs,
4446 )?;
4447 let activation = engine.uninit(
4448 max_pairs
4449 .checked_mul(experts.expert_width)
4450 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4451 )?;
4452 owners.push(PreparedStepGroupedExpertOwner {
4453 rank: owner_rank,
4454 global_pairs: Vec::new(),
4455 route_csr,
4456 down_csr,
4457 gate_workspace,
4458 up_workspace,
4459 down_workspace,
4460 activation,
4461 });
4462 }
4463
4464 let mut plan = PreparedStepGroupedExpertParallelGate {
4465 rank_inputs,
4466 owners,
4467 activation_limit,
4468 tokens: 0,
4469 pairs: 0,
4470 max_tokens,
4471 max_pairs,
4472 input_width: experts.input_width,
4473 expert_width: experts.expert_width,
4474 generation: 0,
4475 executed_generation: None,
4476 ready: false,
4477 };
4478 self.refresh_step_grouped_expert_parallel_gate(
4479 experts, &mut plan, input, tokens, selected,
4480 )?;
4481 Ok(plan)
4482 }
4483
4484 #[allow(clippy::type_complexity)] fn prepare_step_grouped_expert_parallel_refresh(
4486 &self,
4487 experts: &ResidentExpertParallel,
4488 plan: &PreparedStepGroupedExpertParallelGate,
4489 tokens: usize,
4490 selected: &[usize],
4491 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4492 {
4493 validate_ep_residency(&self.ranks, experts)?;
4494 if plan.rank_inputs.len() != self.ranks.len()
4495 || plan.owners.len() != self.ranks.len()
4496 || plan.input_width != experts.input_width
4497 || plan.expert_width != experts.expert_width
4498 || tokens > plan.max_tokens
4499 {
4500 return Err(format!(
4501 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4502 input={}/{} expert={}/{} tokens={}/{}",
4503 plan.rank_inputs.len(),
4504 self.ranks.len(),
4505 plan.owners.len(),
4506 self.ranks.len(),
4507 plan.input_width,
4508 experts.input_width,
4509 plan.expert_width,
4510 experts.expert_width,
4511 tokens,
4512 plan.max_tokens,
4513 )
4514 .into());
4515 }
4516 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4517 if pairs > plan.max_pairs {
4518 return Err(format!(
4519 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4520 plan.max_pairs
4521 )
4522 .into());
4523 }
4524 let next_generation = plan
4525 .generation
4526 .checked_add(1)
4527 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4528 let owner_routes = partition_expert_owner_routes(
4529 experts.expert_count,
4530 self.ranks.len(),
4531 tokens,
4532 STEP_GROUPED_FP8_TOP_K,
4533 selected,
4534 )?;
4535 let mut schedules = Vec::with_capacity(self.ranks.len());
4536 for routes in owner_routes {
4537 if routes.selected.is_empty() {
4538 schedules.push(None);
4539 continue;
4540 }
4541 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4542 let local_pairs = routes.selected.len();
4543 let route_csr = ExpertCsr::from_pair_rows(
4544 local_experts,
4545 tokens,
4546 &routes.selected,
4547 &routes.token_rows,
4548 )?;
4549 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4550 let down_csr = ExpertCsr::from_pair_rows(
4551 local_experts,
4552 local_pairs,
4553 &routes.selected,
4554 &down_rows,
4555 )?;
4556 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4557 global_pairs: routes.global_pairs,
4558 route_csr,
4559 down_csr,
4560 }));
4561 }
4562 Ok((pairs, next_generation, schedules))
4563 }
4564
4565 fn commit_step_grouped_expert_parallel_refresh(
4566 &self,
4567 plan: &mut PreparedStepGroupedExpertParallelGate,
4568 tokens: usize,
4569 pairs: usize,
4570 next_generation: u64,
4571 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4572 ) -> Result<(), Box<dyn std::error::Error>> {
4573 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4574 let engine = &self.ranks[owner.rank];
4575 let _main = engine.gpu.enter_main()?;
4576 if let Some(schedule) = schedule {
4577 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4578 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4579 owner.global_pairs = schedule.global_pairs;
4580 } else {
4581 owner.route_csr.clear();
4582 owner.down_csr.clear();
4583 owner.global_pairs.clear();
4584 }
4585 }
4586 plan.tokens = tokens;
4587 plan.pairs = pairs;
4588 plan.generation = next_generation;
4589 plan.ready = true;
4590 Ok(())
4591 }
4592
4593 pub fn refresh_step_grouped_expert_parallel_gate(
4594 &self,
4595 experts: &ResidentExpertParallel,
4596 plan: &mut PreparedStepGroupedExpertParallelGate,
4597 input: &[f32],
4598 tokens: usize,
4599 selected: &[usize],
4600 ) -> Result<(), Box<dyn std::error::Error>> {
4601 validate_activations(input, tokens, experts.input_width)?;
4602 let (pairs, next_generation, schedules) =
4603 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4604
4605 plan.ready = false;
4606 plan.executed_generation = None;
4607 {
4608 let root = &self.ranks[0];
4609 let _main = root.gpu.enter_main()?;
4610 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4611 root.stream().memcpy_htod(input, &mut destination)?;
4612 root.stream().synchronize()?;
4613 }
4614 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4615 let root_input = &root_inputs[0];
4616 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4617 let engine = &self.ranks[rank + 1];
4618 let _main = engine.gpu.enter_main()?;
4619 let mut destination = peer_input.slice_mut(0..input.len());
4620 engine
4621 .stream()
4622 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4623 }
4624 self.commit_step_grouped_expert_parallel_refresh(
4625 plan,
4626 tokens,
4627 pairs,
4628 next_generation,
4629 schedules,
4630 )
4631 }
4632
4633 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4638 &self,
4639 experts: &ResidentExpertParallel,
4640 plan: &mut PreparedStepGroupedExpertParallelGate,
4641 input: &CudaSlice<f32>,
4642 tokens: usize,
4643 selected: &[usize],
4644 ) -> Result<(), Box<dyn std::error::Error>> {
4645 let input_values = tokens
4646 .checked_mul(experts.input_width)
4647 .ok_or("Step owner-grouped FP8 input size overflow")?;
4648 let root = self
4649 .ranks
4650 .first()
4651 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4652 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4653 return Err(format!(
4654 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4655 device {}",
4656 input.len(),
4657 input.ordinal(),
4658 input_values,
4659 root.ctx().ordinal(),
4660 )
4661 .into());
4662 }
4663 let (pairs, next_generation, schedules) =
4664 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4665
4666 plan.ready = false;
4667 plan.executed_generation = None;
4668 {
4669 let _main = root.gpu.enter_main()?;
4670 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4671 root.stream()
4672 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4673 root.stream().synchronize()?;
4674 }
4675 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4676 let root_input = &root_inputs[0];
4677 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4678 let engine = &self.ranks[rank + 1];
4679 let _main = engine.gpu.enter_main()?;
4680 let mut destination = peer_input.slice_mut(0..input_values);
4681 engine
4682 .stream()
4683 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4684 }
4685 self.commit_step_grouped_expert_parallel_refresh(
4686 plan,
4687 tokens,
4688 pairs,
4689 next_generation,
4690 schedules,
4691 )
4692 }
4693
4694 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4699 &self,
4700 experts: &ResidentExpertParallel,
4701 plan: &mut PreparedStepGroupedExpertParallelGate,
4702 input: &ResidentReplicatedDeviceRows,
4703 ) -> Result<(), Box<dyn std::error::Error>> {
4704 validate_ep_residency(&self.ranks, experts)?;
4705 validate_replicated_device_rows(&self.ranks, input)?;
4706 if !plan.ready
4707 || input.tokens != plan.tokens
4708 || input.width != plan.input_width
4709 || input.tokens > plan.max_tokens
4710 || plan.rank_inputs.len() != self.ranks.len()
4711 || plan.owners.len() != self.ranks.len()
4712 || plan.input_width != experts.input_width
4713 || plan.expert_width != experts.expert_width
4714 {
4715 return Err("Step owner-grouped replicated input geometry changed".into());
4716 }
4717 let values = input
4718 .tokens
4719 .checked_mul(input.width)
4720 .ok_or("Step owner-grouped replicated input size overflow")?;
4721 let next_generation = plan
4722 .generation
4723 .checked_add(1)
4724 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4725 plan.ready = false;
4726 plan.executed_generation = None;
4727 for (rank, engine) in self.ranks.iter().enumerate() {
4728 let _main = engine.gpu.enter_main()?;
4729 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4730 engine
4731 .stream()
4732 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4733 }
4734 plan.generation = next_generation;
4735 plan.ready = true;
4736 Ok(())
4737 }
4738
4739 pub fn execute_step_grouped_expert_parallel_gate(
4740 &self,
4741 experts: &ResidentExpertParallel,
4742 plan: &mut PreparedStepGroupedExpertParallelGate,
4743 ) -> Result<(), Box<dyn std::error::Error>> {
4744 validate_ep_residency(&self.ranks, experts)?;
4745 if !plan.ready
4746 || plan.rank_inputs.len() != self.ranks.len()
4747 || plan.owners.len() != self.ranks.len()
4748 || plan.input_width != experts.input_width
4749 || plan.expert_width != experts.expert_width
4750 {
4751 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4752 }
4753 plan.executed_generation = None;
4754
4755 for owner in &mut plan.owners {
4756 if owner.global_pairs.is_empty() {
4757 continue;
4758 }
4759 let engine = &self.ranks[owner.rank];
4760 let bank = &experts.ranks[owner.rank];
4761 let _main = engine.gpu.enter_main()?;
4762 let local_pairs = owner.global_pairs.len();
4763 owner.gate_workspace.quantize_for_shape(
4764 engine,
4765 &plan.rank_inputs[owner.rank],
4766 plan.tokens,
4767 local_pairs,
4768 )?;
4769 owner.gate_workspace.project(
4770 engine,
4771 &bank.gate.codes,
4772 &bank.gate.scales,
4773 &owner.route_csr,
4774 bank.gate.code_stride,
4775 bank.gate.scale_stride,
4776 1.0,
4777 )?;
4778 owner.up_workspace.quantize_for_shape(
4779 engine,
4780 &plan.rank_inputs[owner.rank],
4781 plan.tokens,
4782 local_pairs,
4783 )?;
4784 owner.up_workspace.project(
4785 engine,
4786 &bank.up.codes,
4787 &bank.up.scales,
4788 &owner.route_csr,
4789 bank.up.code_stride,
4790 bank.up.scale_stride,
4791 1.0,
4792 )?;
4793 }
4794 for owner in &mut plan.owners {
4795 if owner.global_pairs.is_empty() {
4796 continue;
4797 }
4798 let engine = &self.ranks[owner.rank];
4799 let _main = engine.gpu.enter_main()?;
4800 let values = owner.global_pairs.len() * plan.expert_width;
4801 if let Some(limit) = plan.activation_limit {
4802 engine.silu_clamped_mul_host_expf(
4803 owner.gate_workspace.output(),
4804 owner.up_workspace.output(),
4805 limit,
4806 &mut owner.activation,
4807 values,
4808 )?;
4809 } else {
4810 engine.silu_mul_host_expf(
4811 owner.gate_workspace.output(),
4812 owner.up_workspace.output(),
4813 &mut owner.activation,
4814 values,
4815 )?;
4816 }
4817 }
4818 for owner in &mut plan.owners {
4819 if owner.global_pairs.is_empty() {
4820 continue;
4821 }
4822 let engine = &self.ranks[owner.rank];
4823 let bank = &experts.ranks[owner.rank];
4824 let _main = engine.gpu.enter_main()?;
4825 let local_pairs = owner.global_pairs.len();
4826 owner.down_workspace.quantize_for_shape(
4827 engine,
4828 &owner.activation,
4829 local_pairs,
4830 local_pairs,
4831 )?;
4832 owner.down_workspace.project(
4833 engine,
4834 &bank.down.codes,
4835 &bank.down.scales,
4836 &owner.down_csr,
4837 bank.down.code_stride,
4838 bank.down.scale_stride,
4839 1.0,
4840 )?;
4841 }
4842 plan.executed_generation = Some(plan.generation);
4843 Ok(())
4844 }
4845
4846 pub fn collect_step_grouped_expert_parallel_gate(
4847 &self,
4848 plan: &PreparedStepGroupedExpertParallelGate,
4849 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4850 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4851 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4852 }
4853 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4854 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4855 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4856 for owner in &plan.owners {
4857 if owner.global_pairs.is_empty() {
4858 continue;
4859 }
4860 let engine = &self.ranks[owner.rank];
4861 let _main = engine.gpu.enter_main()?;
4862 let owner_gate = engine.dtoh_view(
4863 &owner
4864 .gate_workspace
4865 .output()
4866 .slice(0..owner.gate_workspace.output_len()),
4867 )?;
4868 let owner_up = engine.dtoh_view(
4869 &owner
4870 .up_workspace
4871 .output()
4872 .slice(0..owner.up_workspace.output_len()),
4873 )?;
4874 let owner_down = engine.dtoh_view(
4875 &owner
4876 .down_workspace
4877 .output()
4878 .slice(0..owner.down_workspace.output_len()),
4879 )?;
4880 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4881 let local_expert = local_pair * plan.expert_width;
4882 let global_expert = global_pair * plan.expert_width;
4883 gate[global_expert..global_expert + plan.expert_width]
4884 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4885 up[global_expert..global_expert + plan.expert_width]
4886 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4887
4888 let local_hidden = local_pair * plan.input_width;
4889 let global_hidden = global_pair * plan.input_width;
4890 down[global_hidden..global_hidden + plan.input_width]
4891 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4892 }
4893 }
4894 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4895 }
4896
4897 pub fn run_step_grouped_expert_parallel_gate(
4898 &self,
4899 experts: &ResidentExpertParallel,
4900 plan: &mut PreparedStepGroupedExpertParallelGate,
4901 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4902 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4903 self.collect_step_grouped_expert_parallel_gate(plan)
4904 }
4905
4906 pub fn prepare_step_grouped_expert_parallel_combine(
4907 &self,
4908 plan: &PreparedStepGroupedExpertParallelGate,
4909 route_weights: &[f32],
4910 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4911 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4912 return Err(
4913 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4914 );
4915 }
4916 let owner_pairs = plan
4917 .owners
4918 .iter()
4919 .map(|owner| owner.global_pairs.as_slice())
4920 .collect::<Vec<_>>();
4921 let shape = validate_weighted_route_combine(
4922 plan.input_width,
4923 STEP_GROUPED_FP8_TOP_K,
4924 plan.max_tokens,
4925 plan.tokens,
4926 &owner_pairs,
4927 route_weights,
4928 )?;
4929 if shape.max_pairs != plan.max_pairs {
4930 return Err(format!(
4931 "Step owner-grouped combine capacity {} != projection capacity {}",
4932 shape.max_pairs, plan.max_pairs
4933 )
4934 .into());
4935 }
4936 let root = self
4937 .ranks
4938 .first()
4939 .ok_or("Step owner-grouped combine has no root rank")?;
4940 let slot_values = shape
4941 .max_pairs
4942 .checked_mul(plan.input_width)
4943 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4944 let output_values = plan
4945 .max_tokens
4946 .checked_mul(plan.input_width)
4947 .ok_or("Step owner-grouped combine output capacity overflow")?;
4948 let (root_device, owners, peer_staging, slots, weights, output) = {
4949 let _main = root.gpu.enter_main()?;
4950 let mut owners = Vec::with_capacity(plan.owners.len());
4951 for _ in &plan.owners {
4952 owners.push(PreparedPeerWeightedRouteOwner {
4953 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4954 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4955 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4956 active_pairs: 0,
4957 });
4958 }
4959 (
4960 root.ctx().ordinal(),
4961 owners,
4962 root.uninit(slot_values)?,
4963 root.uninit(slot_values)?,
4964 root.uninit(shape.max_pairs)?,
4965 root.uninit(output_values)?,
4966 )
4967 };
4968 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4969 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4970 for engine in self.ranks.iter().skip(1) {
4971 let _main = engine.gpu.enter_main()?;
4972 peer_devices.push(engine.ctx().ordinal());
4973 peer_outputs.push(engine.uninit(output_values)?);
4974 }
4975 let mut combine = PreparedPeerWeightedRouteCombine {
4976 root_device,
4977 owners,
4978 peer_staging,
4979 slots,
4980 weights,
4981 output,
4982 peer_devices,
4983 peer_outputs,
4984 width: plan.input_width,
4985 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4986 max_tokens: plan.max_tokens,
4987 max_pairs: shape.max_pairs,
4988 tokens: 0,
4989 pairs: 0,
4990 projection_generation: 0,
4991 output_generation: None,
4992 broadcast_generation: None,
4993 ready: false,
4994 };
4995 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4996 Ok(combine)
4997 }
4998
4999 pub fn refresh_step_grouped_expert_parallel_combine(
5000 &self,
5001 plan: &PreparedStepGroupedExpertParallelGate,
5002 combine: &mut PreparedPeerWeightedRouteCombine,
5003 route_weights: &[f32],
5004 ) -> Result<(), Box<dyn std::error::Error>> {
5005 let output_capacity = combine
5006 .max_tokens
5007 .checked_mul(combine.width)
5008 .ok_or("Step owner-grouped combine output capacity overflow")?;
5009 if !plan.ready
5010 || combine.owners.len() != plan.owners.len()
5011 || combine.peer_devices.len() + 1 != self.ranks.len()
5012 || combine.peer_outputs.len() + 1 != self.ranks.len()
5013 || combine.width != plan.input_width
5014 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
5015 || combine.max_tokens != plan.max_tokens
5016 || combine.max_pairs != plan.max_pairs
5017 || combine.output.len() < output_capacity
5018 || combine
5019 .peer_outputs
5020 .iter()
5021 .any(|output| output.len() < output_capacity)
5022 {
5023 return Err("Step owner-grouped combine/projection geometry changed".into());
5024 }
5025 if self
5026 .ranks
5027 .iter()
5028 .skip(1)
5029 .zip(&combine.peer_devices)
5030 .any(|(engine, &device)| engine.ctx().ordinal() != device)
5031 {
5032 return Err("Step owner-grouped combine peer devices changed".into());
5033 }
5034 let owner_pairs = plan
5035 .owners
5036 .iter()
5037 .map(|owner| owner.global_pairs.as_slice())
5038 .collect::<Vec<_>>();
5039 let shape = validate_weighted_route_combine(
5040 combine.width,
5041 combine.experts_per_token,
5042 combine.max_tokens,
5043 plan.tokens,
5044 &owner_pairs,
5045 route_weights,
5046 )?;
5047 if shape.max_pairs != combine.max_pairs {
5048 return Err("Step owner-grouped combine capacity changed during refresh".into());
5049 }
5050 let metadata = owner_pairs
5051 .iter()
5052 .map(|pairs| {
5053 let token_rows = pairs
5054 .iter()
5055 .map(|&pair| (pair / combine.experts_per_token) as i32)
5056 .collect::<Vec<_>>();
5057 let slots = pairs
5058 .iter()
5059 .map(|&pair| (pair % combine.experts_per_token) as i32)
5060 .collect::<Vec<_>>();
5061 let weights = pairs
5062 .iter()
5063 .map(|&pair| route_weights[pair])
5064 .collect::<Vec<_>>();
5065 (token_rows, slots, weights)
5066 })
5067 .collect::<Vec<_>>();
5068
5069 combine.ready = false;
5070 combine.output_generation = None;
5071 combine.broadcast_generation = None;
5072 let root = self
5073 .ranks
5074 .first()
5075 .ok_or("Step owner-grouped combine has no root rank")?;
5076 let _main = root.gpu.enter_main()?;
5077 if root.ctx().ordinal() != combine.root_device {
5078 return Err(format!(
5079 "Step owner-grouped combine root device changed {} != {}",
5080 root.ctx().ordinal(),
5081 combine.root_device
5082 )
5083 .into());
5084 }
5085 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
5086 if token_rows.is_empty() {
5087 owner.active_pairs = 0;
5088 continue;
5089 }
5090 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
5091 root.htod_i32_into(&mut owner.slots, &slots)?;
5092 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
5093 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
5094 owner.active_pairs = token_rows.len();
5095 }
5096 combine.tokens = plan.tokens;
5097 combine.pairs = shape.pairs;
5098 combine.projection_generation = plan.generation;
5099 combine.ready = true;
5100 Ok(())
5101 }
5102
5103 pub fn execute_step_grouped_expert_parallel_combine(
5104 &self,
5105 plan: &PreparedStepGroupedExpertParallelGate,
5106 combine: &mut PreparedPeerWeightedRouteCombine,
5107 ) -> Result<(), Box<dyn std::error::Error>> {
5108 if !plan.ready
5109 || plan.executed_generation != Some(plan.generation)
5110 || !combine.ready
5111 || combine.tokens != plan.tokens
5112 || combine.pairs != plan.pairs
5113 || combine.width != plan.input_width
5114 || combine.owners.len() != plan.owners.len()
5115 || combine.projection_generation != plan.generation
5116 {
5117 return Err("Step owner-grouped combine is stale or its geometry changed".into());
5118 }
5119 combine.output_generation = None;
5120 combine.broadcast_generation = None;
5121 for owner in &plan.owners {
5122 if owner.rank == 0 || owner.global_pairs.is_empty() {
5123 continue;
5124 }
5125 let engine = &self.ranks[owner.rank];
5126 let _main = engine.gpu.enter_main()?;
5127 engine.stream().synchronize()?;
5128 }
5129 let root = self
5130 .ranks
5131 .first()
5132 .ok_or("Step owner-grouped combine has no root rank")?;
5133 let _main = root.gpu.enter_main()?;
5134 if root.ctx().ordinal() != combine.root_device {
5135 return Err("Step owner-grouped combine is not resident on the root device".into());
5136 }
5137 for (index, owner) in plan.owners.iter().enumerate() {
5138 let metadata = &combine.owners[index];
5139 if owner.global_pairs.len() != metadata.active_pairs {
5140 return Err(format!(
5141 "Step owner-grouped combine owner {index} rows {} != metadata {}",
5142 owner.global_pairs.len(),
5143 metadata.active_pairs
5144 )
5145 .into());
5146 }
5147 if metadata.active_pairs == 0 {
5148 continue;
5149 }
5150 let values = metadata
5151 .active_pairs
5152 .checked_mul(combine.width)
5153 .ok_or("Step owner-grouped combine peer value count overflow")?;
5154 if owner.rank == 0 {
5155 root.scatter_slot(
5156 owner.down_workspace.output(),
5157 &metadata.token_rows,
5158 &metadata.slots,
5159 &metadata.weights,
5160 &mut combine.slots,
5161 &mut combine.weights,
5162 combine.width,
5163 combine.experts_per_token,
5164 metadata.active_pairs,
5165 )?;
5166 } else {
5167 let source = owner.down_workspace.output().slice(0..values);
5168 let mut destination = combine.peer_staging.slice_mut(0..values);
5169 root.stream().memcpy_dtod(&source, &mut destination)?;
5170 root.scatter_slot(
5171 &combine.peer_staging,
5172 &metadata.token_rows,
5173 &metadata.slots,
5174 &metadata.weights,
5175 &mut combine.slots,
5176 &mut combine.weights,
5177 combine.width,
5178 combine.experts_per_token,
5179 metadata.active_pairs,
5180 )?;
5181 }
5182 }
5183 root.reduce_slots_host(
5184 &combine.slots,
5185 &combine.weights,
5186 &mut combine.output,
5187 combine.width,
5188 combine.experts_per_token,
5189 combine.tokens,
5190 )?;
5191 combine.output_generation = Some(plan.generation);
5192 Ok(())
5193 }
5194
5195 pub fn collect_step_grouped_expert_parallel_combine(
5196 &self,
5197 plan: &PreparedStepGroupedExpertParallelGate,
5198 combine: &PreparedPeerWeightedRouteCombine,
5199 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5200 if !plan.ready
5201 || combine.output_generation != Some(plan.generation)
5202 || combine.projection_generation != plan.generation
5203 {
5204 return Err("Step owner-grouped combine output is stale or has not executed".into());
5205 }
5206 let root = self
5207 .ranks
5208 .first()
5209 .ok_or("Step owner-grouped combine has no root rank")?;
5210 let _main = root.gpu.enter_main()?;
5211 if root.ctx().ordinal() != combine.root_device {
5212 return Err("Step owner-grouped combine is not resident on the root device".into());
5213 }
5214 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
5215 }
5216
5217 pub fn copy_step_grouped_expert_parallel_combine_root(
5222 &self,
5223 plan: &PreparedStepGroupedExpertParallelGate,
5224 combine: &PreparedPeerWeightedRouteCombine,
5225 destination: &Engine,
5226 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5227 if !plan.ready
5228 || combine.output_generation != Some(plan.generation)
5229 || combine.projection_generation != plan.generation
5230 {
5231 return Err("Step owner-grouped combine output is stale or has not executed".into());
5232 }
5233 let root = self
5234 .ranks
5235 .first()
5236 .ok_or("Step owner-grouped combine has no root rank")?;
5237 if root.ctx().ordinal() != combine.root_device
5238 || destination.ctx().ordinal() != combine.root_device
5239 {
5240 return Err(format!(
5241 "Step owner-grouped combine root/destination devices {}/{} != {}",
5242 root.ctx().ordinal(),
5243 destination.ctx().ordinal(),
5244 combine.root_device,
5245 )
5246 .into());
5247 }
5248 let values = combine
5249 .tokens
5250 .checked_mul(combine.width)
5251 .ok_or("Step owner-grouped combine copy size overflow")?;
5252 {
5253 let _main = root.gpu.enter_main()?;
5254 root.stream().synchronize()?;
5255 }
5256 let _main = destination.gpu.enter_main()?;
5257 let mut output = destination.uninit(values)?;
5258 destination
5259 .stream()
5260 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
5261 Ok(output)
5262 }
5263
5264 pub fn broadcast_step_grouped_expert_parallel_combine(
5265 &self,
5266 plan: &PreparedStepGroupedExpertParallelGate,
5267 combine: &mut PreparedPeerWeightedRouteCombine,
5268 ) -> Result<(), Box<dyn std::error::Error>> {
5269 if !plan.ready
5270 || combine.output_generation != Some(plan.generation)
5271 || combine.projection_generation != plan.generation
5272 || combine.peer_devices.len() + 1 != self.ranks.len()
5273 || combine.peer_outputs.len() + 1 != self.ranks.len()
5274 {
5275 return Err("Step owner-grouped combine output cannot be broadcast".into());
5276 }
5277 combine.broadcast_generation = None;
5278 let values = combine
5279 .tokens
5280 .checked_mul(combine.width)
5281 .ok_or("Step owner-grouped combine broadcast size overflow")?;
5282 {
5283 let root = self
5284 .ranks
5285 .first()
5286 .ok_or("Step owner-grouped combine has no root rank")?;
5287 let _main = root.gpu.enter_main()?;
5288 if root.ctx().ordinal() != combine.root_device {
5289 return Err("Step owner-grouped combine root device changed".into());
5290 }
5291 root.stream().synchronize()?;
5292 }
5293 let source = &combine.output;
5294 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
5295 let engine = &self.ranks[index + 1];
5296 let _main = engine.gpu.enter_main()?;
5297 if engine.ctx().ordinal() != combine.peer_devices[index] {
5298 return Err(format!(
5299 "Step owner-grouped combine peer {} device changed",
5300 index + 1
5301 )
5302 .into());
5303 }
5304 let mut destination = destination_buffer.slice_mut(0..values);
5305 engine
5306 .stream()
5307 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
5308 }
5309 combine.broadcast_generation = Some(plan.generation);
5310 Ok(())
5311 }
5312
5313 pub fn collect_step_grouped_expert_parallel_broadcast(
5314 &self,
5315 plan: &PreparedStepGroupedExpertParallelGate,
5316 combine: &PreparedPeerWeightedRouteCombine,
5317 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
5318 if !plan.ready
5319 || combine.output_generation != Some(plan.generation)
5320 || combine.broadcast_generation != Some(plan.generation)
5321 || combine.peer_outputs.len() + 1 != self.ranks.len()
5322 {
5323 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
5324 }
5325 let values = combine
5326 .tokens
5327 .checked_mul(combine.width)
5328 .ok_or("Step owner-grouped combine collection size overflow")?;
5329 let mut outputs = Vec::with_capacity(self.ranks.len());
5330 {
5331 let root = &self.ranks[0];
5332 let _main = root.gpu.enter_main()?;
5333 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
5334 }
5335 for (index, output) in combine.peer_outputs.iter().enumerate() {
5336 let engine = &self.ranks[index + 1];
5337 let _main = engine.gpu.enter_main()?;
5338 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
5339 }
5340 Ok(outputs)
5341 }
5342
5343 pub fn finish_step_grouped_expert_parallel_layer(
5345 &self,
5346 plan: &PreparedStepGroupedExpertParallelGate,
5347 combine: &PreparedPeerWeightedRouteCombine,
5348 shared: &ResidentReplicatedDeviceRows,
5349 residual: &ResidentReplicatedDeviceRows,
5350 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
5351 validate_replicated_device_rows(&self.ranks, shared)?;
5352 validate_replicated_device_rows(&self.ranks, residual)?;
5353 if !plan.ready
5354 || plan.executed_generation != Some(plan.generation)
5355 || combine.output_generation != Some(plan.generation)
5356 || combine.broadcast_generation != Some(plan.generation)
5357 || combine.projection_generation != plan.generation
5358 || combine.peer_outputs.len() + 1 != self.ranks.len()
5359 || shared.tokens != combine.tokens
5360 || residual.tokens != combine.tokens
5361 || shared.width != combine.width
5362 || residual.width != combine.width
5363 {
5364 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
5365 }
5366 let values = combine
5367 .tokens
5368 .checked_mul(combine.width)
5369 .ok_or("Step full-layer output size overflow")?;
5370 let mut ranks = Vec::with_capacity(self.ranks.len());
5371 for rank in 0..self.ranks.len() {
5372 let engine = &self.ranks[rank];
5373 let _main = engine.gpu.enter_main()?;
5374 let routed = if rank == 0 {
5375 &combine.output
5376 } else {
5377 &combine.peer_outputs[rank - 1]
5378 };
5379 let mut ffn = engine.uninit(values)?;
5380 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
5381 let mut output = engine.uninit(values)?;
5382 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5383 ranks.push(output);
5384 }
5385 Ok(ResidentReplicatedDeviceRows {
5386 ranks,
5387 tokens: combine.tokens,
5388 width: combine.width,
5389 })
5390 }
5391
5392 pub fn run_step_grouped_expert_parallel_combine(
5393 &self,
5394 plan: &PreparedStepGroupedExpertParallelGate,
5395 combine: &mut PreparedPeerWeightedRouteCombine,
5396 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5397 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5398 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5399 }
5400
5401 pub fn upload_tensor_parallel(
5402 &self,
5403 gate: E4m3ExpertBank<'_>,
5404 up: E4m3ExpertBank<'_>,
5405 down: E4m3ExpertBank<'_>,
5406 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5407 gate.validate()?;
5408 up.validate()?;
5409 down.validate()?;
5410 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5411 return Err("TP gate/up/down expert counts differ".into());
5412 }
5413 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5414 return Err("TP gate/up dimensions differ".into());
5415 }
5416 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5417 return Err(format!(
5418 "TP down {}x{} does not invert gate/up {}x{}",
5419 down.out_features, down.in_features, gate.out_features, gate.in_features
5420 )
5421 .into());
5422 }
5423 let tp = self.ranks.len();
5424 validate_column_bank_shape(gate, tp)?;
5425 validate_column_bank_shape(up, tp)?;
5426 validate_row_bank_shape(down, tp)?;
5427
5428 let mut gate_ranks = Vec::with_capacity(tp);
5429 let mut up_ranks = Vec::with_capacity(tp);
5430 let mut down_ranks = Vec::with_capacity(tp);
5431 for (rank, engine) in self.ranks.iter().enumerate() {
5432 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5433 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5434 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5435 }
5436 Ok(ResidentTensorParallel {
5437 bank: ResidentTpExpertBank {
5438 gate: gate_ranks,
5439 up: up_ranks,
5440 down: down_ranks,
5441 expert_count: gate.expert_count,
5442 input_width: gate.in_features,
5443 expert_width: gate.out_features,
5444 },
5445 })
5446 }
5447
5448 pub fn run_tensor_parallel_routes(
5449 &self,
5450 experts: &ResidentTensorParallel,
5451 input: &[f32],
5452 tokens: usize,
5453 selected: &[usize],
5454 route_weights: &[f32],
5455 experts_per_token: usize,
5456 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5457 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5458 validate_activations(input, tokens, experts.bank.input_width)?;
5459 let pairs = tokens
5460 .checked_mul(experts_per_token)
5461 .ok_or("TP route count overflow")?;
5462 if selected.len() != pairs || route_weights.len() != pairs {
5463 return Err(format!(
5464 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5465 {experts_per_token} ({pairs})",
5466 selected.len(),
5467 route_weights.len(),
5468 )
5469 .into());
5470 }
5471 if !route_weights.iter().all(|weight| weight.is_finite()) {
5472 return Err("TP route weights contain a non-finite value".into());
5473 }
5474
5475 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5476 for token in 0..tokens {
5477 let input_row =
5478 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5479 for slot in 0..experts_per_token {
5480 let pair = token * experts_per_token + slot;
5481 let expert = selected[pair];
5482 if expert >= experts.bank.expert_count {
5483 return Err(format!(
5484 "TP selected expert {expert} outside 0..{}",
5485 experts.bank.expert_count
5486 )
5487 .into());
5488 }
5489 let down = if self.native_p2p {
5490 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5491 } else {
5492 let gate =
5493 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5494 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5495 let activated: Vec<f32> = gate
5496 .iter()
5497 .zip(&up)
5498 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5499 .collect();
5500 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5501 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5502 };
5503 let weight = route_weights[pair];
5504 for (sum, value) in output
5505 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5506 .iter_mut()
5507 .zip(down)
5508 {
5509 *sum += weight * value;
5510 }
5511 }
5512 }
5513 Ok(output)
5514 }
5515
5516 fn run_column_bank_expert(
5517 &self,
5518 ranks: &[ResidentE4m3ExpertBankRank],
5519 expert: usize,
5520 input: &[f32],
5521 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5522 let local_out = ranks
5523 .first()
5524 .ok_or("TP column bank has no ranks")?
5525 .out_features;
5526 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5527 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5528 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5529 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5530 }
5531 Ok(gathered)
5532 }
5533
5534 fn run_row_bank_expert(
5535 &self,
5536 ranks: &[ResidentE4m3ExpertBankRank],
5537 expert: usize,
5538 input: &[f32],
5539 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5540 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5541 if input.len() != local_in * ranks.len() {
5542 return Err(format!(
5543 "TP row input {} != {} ranks x {local_in}",
5544 input.len(),
5545 ranks.len()
5546 )
5547 .into());
5548 }
5549 let out_features = ranks[0].out_features;
5550 let mut reduced = vec![0.0f32; out_features];
5551 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5552 let blocks = bank
5553 .k_blocks
5554 .ok_or("TP row bank is not packed in native K-block order")?;
5555 if blocks * FP8_BLOCK != local_in {
5556 return Err(format!(
5557 "TP row bank has {blocks} blocks but local input width is {local_in}"
5558 )
5559 .into());
5560 }
5561 for block in 0..blocks {
5562 let global_start = rank * local_in + block * FP8_BLOCK;
5563 let partial = run_resident_bank_expert_block(
5564 engine,
5565 bank,
5566 expert,
5567 block,
5568 &input[global_start..global_start + FP8_BLOCK],
5569 )?;
5570 for (sum, value) in reduced.iter_mut().zip(partial) {
5571 *sum += value;
5572 }
5573 }
5574 }
5575 Ok(reduced)
5576 }
5577
5578 fn run_tensor_parallel_expert_native(
5579 &self,
5580 bank: &ResidentTpExpertBank,
5581 expert: usize,
5582 input: &[f32],
5583 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5584 if !self.native_p2p || self.ranks.len() < 2 {
5585 return Err("native TP expert execution requires at least two P2P ranks".into());
5586 }
5587 let local_out = bank
5588 .gate
5589 .first()
5590 .ok_or("native TP gate bank has no ranks")?
5591 .out_features;
5592 if local_out * self.ranks.len() != bank.expert_width {
5593 return Err(format!(
5594 "native TP gate shards {}x{local_out} != expert width {}",
5595 self.ranks.len(),
5596 bank.expert_width
5597 )
5598 .into());
5599 }
5600
5601 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5604 let root_input = {
5605 let root = &self.ranks[0];
5606 let _main = root.gpu.enter_main()?;
5607 root.htod(input)?
5608 };
5609 rank_inputs.push(root_input);
5610 for engine in &self.ranks[1..] {
5611 let peer_input = {
5612 let _main = engine.gpu.enter_main()?;
5613 let mut peer_input = engine.uninit(input.len())?;
5614 engine
5615 .stream()
5616 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5617 peer_input
5618 };
5619 rank_inputs.push(peer_input);
5620 }
5621
5622 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5623 let mut up_shards = Vec::with_capacity(self.ranks.len());
5624 #[allow(clippy::needless_range_loop)]
5625 for rank in 0..self.ranks.len() {
5627 gate_shards.push(run_resident_bank_expert_device(
5628 &self.ranks[rank],
5629 &bank.gate[rank],
5630 expert,
5631 &rank_inputs[rank],
5632 1,
5633 )?);
5634 up_shards.push(run_resident_bank_expert_device(
5635 &self.ranks[rank],
5636 &bank.up[rank],
5637 expert,
5638 &rank_inputs[rank],
5639 1,
5640 )?);
5641 }
5642
5643 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5647 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5648 let activated = gate
5649 .iter()
5650 .zip(&up)
5651 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5652 .collect::<Vec<_>>();
5653 debug_assert_eq!(activated.len(), bank.expert_width);
5654
5655 let root_activated = {
5656 let root = &self.ranks[0];
5657 let _main = root.gpu.enter_main()?;
5658 root.htod(&activated)?
5659 };
5660 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5661 for (rank, engine) in self.ranks.iter().enumerate() {
5662 let start = rank * local_out;
5663 let source = root_activated.slice(start..start + local_out);
5664 let local = {
5665 let _main = engine.gpu.enter_main()?;
5666 let mut local = engine.uninit(local_out)?;
5667 engine.stream().memcpy_dtod(&source, &mut local)?;
5668 local
5669 };
5670 rank_activated.push(local);
5671 }
5672
5673 let out_features = bank
5674 .down
5675 .first()
5676 .ok_or("native TP down bank has no ranks")?
5677 .out_features;
5678 let mut reduced = {
5679 let root = &self.ranks[0];
5680 let _main = root.gpu.enter_main()?;
5681 root.htod(&vec![0.0f32; out_features])?
5682 };
5683 let mut remote_partial_keepalive = Vec::new();
5684 #[allow(clippy::needless_range_loop)]
5685 for rank in 0..self.ranks.len() {
5687 let down = &bank.down[rank];
5688 let blocks = down
5689 .k_blocks
5690 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5691 if blocks * FP8_BLOCK != local_out {
5692 return Err(format!(
5693 "native TP rank {rank} has {blocks} blocks but local activation width is \
5694 {local_out}"
5695 )
5696 .into());
5697 }
5698 for block in 0..blocks {
5699 let start = block * FP8_BLOCK;
5700 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5701 let partial = run_resident_bank_expert_block_device(
5702 &self.ranks[rank],
5703 down,
5704 expert,
5705 block,
5706 &input_block,
5707 )?;
5708 let root_partial = if rank == 0 {
5709 partial
5710 } else {
5711 let root = &self.ranks[0];
5712 let _main = root.gpu.enter_main()?;
5713 let mut peer_partial = root.uninit(out_features)?;
5714 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5715 remote_partial_keepalive.push(partial);
5716 peer_partial
5717 };
5718 let next = {
5719 let root = &self.ranks[0];
5720 let _main = root.gpu.enter_main()?;
5721 let mut next = root.uninit(out_features)?;
5722 root.add(&reduced, &root_partial, &mut next, out_features)?;
5723 next
5724 };
5725 reduced = next;
5726 }
5727 }
5728 let output = {
5729 let root = &self.ranks[0];
5730 let _main = root.gpu.enter_main()?;
5731 root.dtoh(&reduced)?
5732 };
5733 drop(remote_partial_keepalive);
5734 Ok(output)
5735 }
5736
5737 pub fn gather_native_column_shards_device(
5739 &self,
5740 shards: &[CudaSlice<f32>],
5741 tokens: usize,
5742 local_out: usize,
5743 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5744 let shard_len = tokens
5745 .checked_mul(local_out)
5746 .ok_or("native TP gather shard size overflow")?;
5747 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5748 return Err("native TP gather shard geometry mismatch".into());
5749 }
5750 for engine in &self.ranks[1..] {
5754 let _main = engine.gpu.enter_main()?;
5755 engine.stream().synchronize()?;
5756 }
5757 let root = &self.ranks[0];
5758 let _main = root.gpu.enter_main()?;
5759 let global_out = shards
5760 .len()
5761 .checked_mul(local_out)
5762 .ok_or("native TP gather output width overflow")?;
5763 let gathered_len = tokens
5764 .checked_mul(global_out)
5765 .ok_or("native TP gather output size overflow")?;
5766 let mut gathered = root.uninit(gathered_len)?;
5767 if self.bulk_p2p {
5768 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5769 if shards.len() > 1 {
5770 let mut staging = root.uninit(shard_len)?;
5771 for (rank, shard) in shards.iter().enumerate().skip(1) {
5772 root.stream().memcpy_dtod(shard, &mut staging)?;
5773 root.place_rows_strided(
5774 &staging,
5775 &mut gathered,
5776 local_out,
5777 tokens,
5778 global_out,
5779 rank * local_out,
5780 )?;
5781 }
5782 }
5783 } else {
5784 for token in 0..tokens {
5785 for (rank, shard) in shards.iter().enumerate() {
5786 let source = shard.slice(token * local_out..(token + 1) * local_out);
5787 let start = token * global_out + rank * local_out;
5788 let mut destination = gathered.slice_mut(start..start + local_out);
5789 root.stream().memcpy_dtod(&source, &mut destination)?;
5790 }
5791 }
5792 }
5793 Ok(gathered)
5794 }
5795
5796 pub fn gather_native_column_shards(
5797 &self,
5798 shards: &[CudaSlice<f32>],
5799 tokens: usize,
5800 local_out: usize,
5801 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5802 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5803 let root = &self.ranks[0];
5804 let _main = root.gpu.enter_main()?;
5805 root.dtoh(&gathered)
5806 }
5807
5808 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5809 &self.decode_v2
5810 }
5811
5812 #[allow(clippy::manual_is_multiple_of)] pub(crate) fn decode_v2_ensure(
5822 &self,
5823 e: &Engine,
5824 q_m: &ResidentBf16ColumnParallel,
5825 k_m: &ResidentBf16ColumnParallel,
5826 v_m: &ResidentBf16ColumnParallel,
5827 o_m: &ResidentStepBf16RowParallel,
5828 heads: usize,
5829 ) -> Result<usize, Box<dyn std::error::Error>> {
5830 if self.ranks.len() > 1 && !self.native_p2p {
5831 return Err("step TP decode v2 requires native P2P ranks".into());
5832 }
5833 let ranks = self.ranks.len();
5834 let fused_door = step_tp_qkv_fused_enabled()?;
5838 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5839 ResidentBf16Weight::F32(_) => true,
5840 ResidentBf16Weight::Bf16(_) => fused_door,
5841 };
5842 for matrix in [q_m, k_m, v_m] {
5843 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5844 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5845 return Err("step TP decode v2 QKV geometry mismatch".into());
5846 }
5847 for rank in &matrix.ranks {
5848 if !arm_ok(&rank.weight) {
5849 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5850 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5851 .into());
5852 }
5853 }
5854 }
5855 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5856 for blocks in &o_m.ranks {
5857 for block in blocks {
5858 if !arm_ok(&block.weight) {
5859 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5860 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5861 .into());
5862 }
5863 }
5864 }
5865 if v_m.out_features != k_m.out_features
5866 || o_m.in_features != q_m.out_features
5867 || heads == 0
5868 || heads % ranks != 0
5869 {
5870 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5871 }
5872 let local_q_dim = q_m.out_features / ranks;
5873 let local_kv_dim = k_m.out_features / ranks;
5874 let o_out = o_m.out_features;
5875 let o_block_cols = o_m.canonical_chunk_cols;
5876 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5877 if blocks_per_rank == 0
5878 || o_m
5879 .ranks
5880 .iter()
5881 .any(|blocks| blocks.len() != blocks_per_rank)
5882 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5883 {
5884 return Err("step TP decode v2 O canonical block grid mismatch".into());
5885 }
5886
5887 let mut guard = self
5888 .decode_v2
5889 .lock()
5890 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5891 if let Some(index) = guard.iter().position(|ws| {
5892 ws.local_q_dim == local_q_dim
5893 && ws.local_kv_dim == local_kv_dim
5894 && ws.heads == heads
5895 && ws.o_out == o_out
5896 && ws.o_block_cols == o_block_cols
5897 && ws.blocks_per_rank == blocks_per_rank
5898 && ws.e_device == e.ctx().ordinal()
5899 && ws.q.len() == ranks
5900 }) {
5901 return Ok(index);
5902 }
5903
5904 let mut q_raw = Vec::with_capacity(ranks);
5905 let mut k_raw = Vec::with_capacity(ranks);
5906 let mut v_raw = Vec::with_capacity(ranks);
5907 let mut q = Vec::with_capacity(ranks);
5908 let mut k = Vec::with_capacity(ranks);
5909 let mut pos = Vec::with_capacity(ranks);
5910 let mut gate = Vec::with_capacity(ranks);
5911 let mut attn_out = Vec::with_capacity(ranks);
5912 let mut gated = Vec::with_capacity(ranks);
5913 let mut fuse_ctr = Vec::with_capacity(ranks);
5914 let mut o_partials = Vec::with_capacity(ranks);
5915 let mut ev_rank = Vec::with_capacity(ranks);
5916 let direct_join = oproj_direct_on();
5917 for (rank, engine) in self.ranks.iter().enumerate() {
5918 let _main = engine.gpu.enter_main()?;
5919 q_raw.push(engine.uninit(local_q_dim)?);
5920 k_raw.push(engine.uninit(local_kv_dim)?);
5921 v_raw.push(engine.uninit(local_kv_dim)?);
5922 q.push(engine.uninit(local_q_dim)?);
5923 k.push(engine.uninit(local_kv_dim)?);
5924 pos.push(engine.htod_i32(&[0])?);
5925 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5926 gate.push(engine.uninit(heads / ranks)?);
5927 attn_out.push(engine.uninit(local_q_dim)?);
5928 gated.push(engine.uninit(local_q_dim)?);
5929 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5930 for _ in 0..blocks_per_rank {
5931 if direct_join && rank != 0 {
5934 let root = &self.ranks[0];
5935 let _root_main = root.gpu.enter_main()?;
5936 rank_partials.push(root.uninit(o_out)?);
5937 } else {
5938 rank_partials.push(engine.uninit(o_out)?);
5939 }
5940 }
5941 o_partials.push(rank_partials);
5942 ev_rank.push(engine.ctx().new_event(None)?);
5943 }
5944 use cudarc::driver::DevicePtr;
5945 let mut raw_o_partials = Vec::with_capacity(ranks);
5946 let mut raw_k = Vec::with_capacity(ranks);
5947 let mut raw_v_raw = Vec::with_capacity(ranks);
5948 for rank in 0..ranks {
5949 let engine = &self.ranks[rank];
5950 {
5951 let _main = engine.gpu.enter_main()?;
5952 let stream = engine.stream();
5953 let (k_ptr, _k_guard) = k[rank].device_ptr(&stream);
5954 let (v_ptr, _v_guard) = v_raw[rank].device_ptr(&stream);
5955 raw_k.push(k_ptr);
5956 raw_v_raw.push(v_ptr);
5957 }
5958 let partial_engine = if direct_join && rank != 0 {
5959 &self.ranks[0]
5960 } else {
5961 engine
5962 };
5963 let _main = partial_engine.gpu.enter_main()?;
5964 let stream = partial_engine.stream();
5965 let mut rank_raw = Vec::with_capacity(blocks_per_rank);
5966 for partial in &o_partials[rank] {
5967 let (ptr, _guard) = partial.device_ptr(&stream);
5968 rank_raw.push(ptr);
5969 }
5970 raw_o_partials.push(rank_raw);
5971 }
5972 let root = &self.ranks[0];
5973 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5974 let _main = root.gpu.enter_main()?;
5975 (
5976 root.uninit(o_out)?,
5977 root.uninit(o_out)?,
5978 root.uninit(o_out)?,
5979 root.htod(&vec![0.0f32; o_out])?,
5980 root.uninit(ranks * local_kv_dim)?,
5981 root.uninit(ranks * local_kv_dim)?,
5982 root.ctx().new_event(None)?,
5983 root.ctx().new_event(None)?,
5984 )
5985 };
5986 let (raw_peer_partial, raw_k_shadow, raw_v_shadow) = {
5987 let _main = root.gpu.enter_main()?;
5988 let stream = root.stream();
5989 let (peer, _peer_guard) = peer_partial.device_ptr(&stream);
5990 let (k, _k_guard) = k_shadow.device_ptr(&stream);
5991 let (v, _v_guard) = v_shadow.device_ptr(&stream);
5992 (peer, k, v)
5993 };
5994 let (gate_e, ev_entry) = {
5995 let _main = e.gpu.enter_main()?;
5996 (e.uninit(heads)?, e.ctx().new_event(None)?)
5997 };
5998 let raw_attn_in = Vec::new();
5999 let raw_pos = Vec::new();
6000 guard.push(StepTpDecodeV2Ws {
6001 tcol_q: Vec::new(),
6002 tcol_k: Vec::new(),
6003 tcol_v: Vec::new(),
6004 tcol_g: Vec::new(),
6005 tcol_in: Vec::new(),
6006 tcol_cap: 0,
6007 w8_aq: Vec::new(),
6008 w8_ad: Vec::new(),
6009 w8_in: 0,
6010 w8o_aq: Vec::new(),
6011 w8o_ad: Vec::new(),
6012 w8o_in: 0,
6013 w8t_aq: Vec::new(),
6014 w8t_ad: Vec::new(),
6015 w8t_in: 0,
6016 w8t_oaq: Vec::new(),
6017 w8t_oad: Vec::new(),
6018 w8t_oin: 0,
6019 w8t_cap: 0,
6020 fa2_q: Vec::new(),
6021 fa2_gate: Vec::new(),
6022 fa2_gated: Vec::new(),
6023 fa2_cap: 0,
6024 rope_k_t: Vec::new(),
6025 rope_ctr_t: Vec::new(),
6026 rope_pos_t: Vec::new(),
6027 rows_tabs: Vec::new(),
6028 rows_tab_t: Vec::new(),
6029 rows_tab_shadow: Vec::new(),
6030 tcol_gated: Vec::new(),
6031 tcol_opart: Vec::new(),
6032 tcol_opeer: None,
6033 tcol_omix: None,
6034 tcol_ocap: 0,
6035 q_raw,
6036 k_raw,
6037 v_raw,
6038 q,
6039 k,
6040 pos,
6041 fuse_ctr,
6042 gate,
6043 attn_out,
6044 gated,
6045 o_partials,
6046 raw_o_partials,
6047 raw_k,
6048 raw_v_raw,
6049 ev_rank,
6050 peer_partial,
6051 reduce_a,
6052 reduce_b,
6053 zeros,
6054 k_shadow,
6055 v_shadow,
6056 ev_refresh,
6057 ev_oproj,
6058 gate_e,
6059 attn_in: Vec::new(),
6060 h_stage: None,
6061 pos_stage: None,
6062 raw_h_stage: 0,
6063 raw_pos_stage: 0,
6064 raw_attn_in,
6065 raw_pos,
6066 raw_o_partial1: 0,
6067 raw_peer_partial,
6068 raw_k1: 0,
6069 raw_v1: 0,
6070 raw_k_shadow,
6071 raw_v_shadow,
6072 raw_mixed_stage_e: 0,
6073 raw_reduce_a: 0,
6074 raw_shadow_stage_e: (0, 0),
6075 ev_entry,
6076 e_device: e.ctx().ordinal(),
6077 local_q_dim,
6078 local_kv_dim,
6079 heads,
6080 o_out,
6081 o_block_cols,
6082 blocks_per_rank,
6083 });
6084 eprintln!(
6085 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
6086 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
6087 residency=persistent ordering=evented performance_claim=false"
6088 );
6089 Ok(guard.len() - 1)
6090 }
6091
6092 #[allow(clippy::too_many_arguments)]
6100 #[allow(clippy::too_many_arguments)]
6105 pub fn decode_v2_input_qkv_tcol(
6106 &self,
6107 ws_index: usize,
6108 e: &Engine,
6109 h_t: &CudaSlice<f32>,
6110 t: usize,
6111 q_m: &ResidentBf16ColumnParallel,
6112 k_m: &ResidentBf16ColumnParallel,
6113 v_m: &ResidentBf16ColumnParallel,
6114 gate_shards: Option<StepTpGateShards<'_>>,
6115 ) -> Result<(), Box<dyn std::error::Error>> {
6116 let ranks = self.ranks.len();
6117 let mut guard = self
6118 .decode_v2
6119 .lock()
6120 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6121 let ws = guard
6122 .get_mut(ws_index)
6123 .ok_or("step TP decode v2 workspace index out of range")?;
6124 let in_f = q_m.in_features;
6125 if h_t.len() < t * in_f || t == 0 || t > 32 {
6126 return Err("decode_v2_input_qkv_tcol geometry".into());
6127 }
6128 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6130 ws.tcol_q.clear();
6131 ws.tcol_k.clear();
6132 ws.tcol_v.clear();
6133 ws.tcol_g.clear();
6134 ws.tcol_in.clear();
6135 for engine in &self.ranks {
6136 let _m = engine.gpu.enter_main()?;
6137 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
6138 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
6139 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
6140 ws.tcol_g
6141 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
6142 ws.tcol_in.push(engine.uninit(32 * in_f)?);
6143 }
6144 ws.tcol_cap = 32;
6145 }
6146 use cudarc::driver::DevicePtr;
6148 let raw_src = {
6149 let _main = e.gpu.enter_main()?;
6150 let stream = e.stream();
6151 let (p, _g) = h_t.device_ptr(&stream);
6152 ws.ev_entry.record(&stream)?;
6153 p
6154 };
6155 for rank in 0..ranks {
6156 let engine = &self.ranks[rank];
6157 let _main = engine.gpu.enter_main()?;
6158 engine.stream().wait(&ws.ev_entry)?;
6159 let raw_dst = {
6160 let stream = engine.stream();
6161 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
6162 p
6163 };
6164 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
6165 let out_g = match &gate_shards {
6166 Some(_) => ws.heads / ranks,
6167 None => 0,
6168 };
6169 match (
6170 &q_m.ranks[rank].weight,
6171 &k_m.ranks[rank].weight,
6172 &v_m.ranks[rank].weight,
6173 ) {
6174 (
6175 ResidentBf16Weight::Bf16(wq),
6176 ResidentBf16Weight::Bf16(wk),
6177 ResidentBf16Weight::Bf16(wv),
6178 ) => {
6179 let wg = match &gate_shards {
6180 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
6181 Some(StepTpGateShards::F32(_)) => {
6182 return Err(
6183 "tcol verify: gate shard class does not match bf16 QKV".into()
6184 );
6185 }
6186 None => wq,
6187 };
6188 let StepTpDecodeV2Ws {
6189 tcol_q,
6190 tcol_k,
6191 tcol_v,
6192 tcol_g,
6193 tcol_in,
6194 local_q_dim,
6195 local_kv_dim,
6196 w8t_aq,
6197 w8t_ad,
6198 w8t_in,
6199 w8t_cap,
6200 ..
6201 } = &mut *ws;
6202 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6205 let refk = *REFK
6206 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
6207 if refk {
6208 let lq = *local_q_dim;
6209 let lkv = *local_kv_dim;
6210 let mut hrow = engine.uninit(in_f)?;
6211 let mut qr = engine.uninit(lq)?;
6212 let mut kr = engine.uninit(lkv)?;
6213 let mut vr = engine.uninit(lkv)?;
6214 let mut gr = engine.uninit(out_g.max(1))?;
6215 for c in 0..t {
6216 {
6217 let mut dst = hrow.slice_mut(0..in_f);
6218 engine.stream().memcpy_dtod(
6219 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
6220 &mut dst,
6221 )?;
6222 }
6223 engine.matvec_bf16_qkvg_into(
6224 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
6225 lq, lkv, out_g,
6226 )?;
6227 let stream = engine.stream();
6228 {
6229 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
6230 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
6231 }
6232 {
6233 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
6234 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
6235 }
6236 {
6237 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
6238 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
6239 }
6240 if out_g > 0 {
6241 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
6242 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
6243 }
6244 }
6245 } else if crate::step_tp_w8_on()
6246 && q_m.ranks[rank].q8.is_some()
6247 && k_m.ranks[rank].q8.is_some()
6248 && v_m.ranks[rank].q8.is_some()
6249 && in_f.is_multiple_of(32)
6250 {
6251 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
6257 w8t_aq.clear();
6258 w8t_ad.clear();
6259 for e_rank in &self.ranks {
6260 let _m = e_rank.gpu.enter_main()?;
6261 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6262 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6263 }
6264 *w8t_in = in_f;
6265 *w8t_cap = 32;
6266 }
6267 engine.quantize_q8_1_into(
6268 &tcol_in[rank],
6269 t,
6270 in_f,
6271 &mut w8t_aq[rank],
6272 &mut w8t_ad[rank],
6273 )?;
6274 engine.qmatvec_q8_0_qkv_rp_t_into(
6275 q_m.ranks[rank].q8.as_ref().unwrap(),
6276 k_m.ranks[rank].q8.as_ref().unwrap(),
6277 v_m.ranks[rank].q8.as_ref().unwrap(),
6278 &w8t_aq[rank],
6279 &w8t_ad[rank],
6280 &mut tcol_q[rank],
6281 &mut tcol_k[rank],
6282 &mut tcol_v[rank],
6283 in_f,
6284 *local_q_dim,
6285 *local_kv_dim,
6286 t,
6287 )?;
6288 if out_g > 0 {
6289 engine.matvec_bf16_rows_into(
6290 wg,
6291 &tcol_in[rank],
6292 &mut tcol_g[rank],
6293 in_f,
6294 out_g,
6295 t,
6296 )?;
6297 }
6298 } else {
6299 engine.matvec_bf16_qkvg_tcol_into(
6300 wq,
6301 wk,
6302 wv,
6303 wg,
6304 &tcol_in[rank],
6305 &mut tcol_q[rank],
6306 &mut tcol_k[rank],
6307 &mut tcol_v[rank],
6308 &mut tcol_g[rank],
6309 in_f,
6310 *local_q_dim,
6311 *local_kv_dim,
6312 out_g,
6313 t,
6314 )?;
6315 }
6316 }
6317 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
6318 }
6319 }
6320 Ok(())
6321 }
6322
6323 pub(crate) fn decode_v2_oproj_tcol_eligible(
6327 &self,
6328 ws: &StepTpDecodeV2Ws,
6329 o_m: &ResidentStepBf16RowParallel,
6330 ) -> bool {
6331 self.ranks.len() == 2
6332 && ws.blocks_per_rank == 4
6333 && step_tp_qkv_fused_enabled().unwrap_or(false)
6334 && no_local_shadow_on()
6335 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
6336 && o_m
6337 .ranks
6338 .iter()
6339 .flatten()
6340 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
6341 }
6342
6343 pub(crate) fn decode_v2_stash_fa2(
6348 &self,
6349 ws: &mut StepTpDecodeV2Ws,
6350 e: &Engine,
6351 col: usize,
6352 ) -> Result<(), Box<dyn std::error::Error>> {
6353 let ranks = self.ranks.len();
6354 if col >= 32 {
6355 return Err("decode_v2_stash_fa2 column out of range".into());
6356 }
6357 let lq = ws.local_q_dim;
6358 let lg = (ws.heads / ranks).max(1);
6359 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6360 ws.fa2_q.clear();
6361 ws.fa2_gate.clear();
6362 ws.fa2_gated.clear();
6363 ws.rope_k_t.clear();
6364 ws.rope_ctr_t.clear();
6365 ws.rope_pos_t.clear();
6366 ws.rows_tab_t.clear();
6367 for engine in &self.ranks {
6368 let _m = engine.gpu.enter_main()?;
6369 ws.fa2_q.push(engine.uninit(32 * lq)?);
6370 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6371 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6372 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
6373 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6374 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6375 ws.rows_tab_t
6376 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6377 }
6378 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6379 ws.fa2_cap = 32;
6380 }
6381 for rank in 0..ranks {
6382 let engine = &self.ranks[rank];
6383 let _main = engine.gpu.enter_main()?;
6384 {
6385 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
6386 engine
6387 .stream()
6388 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
6389 }
6390 {
6391 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
6392 engine
6393 .stream()
6394 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
6395 }
6396 ws.ev_rank[rank].record(&engine.stream())?;
6397 }
6398 {
6399 let _main = e.gpu.enter_main()?;
6400 for ev in ws.ev_rank.iter() {
6401 e.stream().wait(ev)?;
6402 }
6403 }
6404 Ok(())
6405 }
6406
6407 #[allow(clippy::too_many_arguments)]
6414 #[allow(dead_code)] pub(crate) fn decode_v2_spec_fa2_join(
6416 &self,
6417 ws_index: usize,
6418 e: &Engine,
6419 o_m: &ResidentStepBf16RowParallel,
6420 kv: &ResidentTpKvCache,
6421 head_dim: usize,
6422 window: usize,
6423 bucket_max: usize,
6424 scale: f32,
6425 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6426 let ranks = self.ranks.len();
6427 static ONCE: std::sync::Once = std::sync::Once::new();
6429 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
6430 {
6431 let mut guard = self
6432 .decode_v2
6433 .lock()
6434 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6435 let ws = guard
6436 .get_mut(ws_index)
6437 .ok_or("step TP decode v2 workspace index out of range")?;
6438 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6439 return Err("spec fa2 join without stashed columns".into());
6440 }
6441 let lq = ws.local_q_dim;
6442 let local_heads = (ws.heads / ranks).max(1);
6443 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6444 let capacity = kv.physical_capacity();
6445 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6446 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6448 ws.tcol_gated.clear();
6449 ws.tcol_opart.clear();
6450 for engine in &self.ranks {
6451 let _m = engine.gpu.enter_main()?;
6452 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6453 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6454 }
6455 let root = &self.ranks[0];
6456 let _m = root.gpu.enter_main()?;
6457 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6458 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6459 ws.tcol_ocap = 32;
6460 }
6461 for rank in 0..ranks {
6462 let engine = &self.ranks[rank];
6463 let _main = engine.gpu.enter_main()?;
6464 let rank_cache = kv
6465 .rank(rank)
6466 .ok_or("spec fa2 join lost its KV cache rank")?;
6467 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6468 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6469 {
6470 let StepTpDecodeV2Ws {
6471 fa2_q,
6472 fa2_gate,
6473 fa2_gated,
6474 ..
6475 } = &mut *ws;
6476 engine.fa_decode_dcw2(
6477 &fa2_q[rank],
6478 &k_ring,
6479 &v_ring,
6480 &mut fa2_gated[rank],
6481 head_dim,
6482 local_heads,
6483 local_kv_heads,
6484 rank_cache.len_d(),
6485 rank_cache.base_d(),
6486 window,
6487 bucket_max,
6488 scale,
6489 k_tok_bytes,
6490 v_tok_bytes,
6491 &fa2_gate[rank],
6492 )?;
6493 }
6494 let StepTpDecodeV2Ws {
6497 fa2_gated,
6498 tcol_gated,
6499 ..
6500 } = &mut *ws;
6501 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6502 engine
6503 .stream()
6504 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6505 }
6506 }
6507 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6508 }
6509
6510 #[allow(clippy::too_many_arguments)]
6520 pub(crate) fn decode_v2_rope_fa_rows(
6521 &self,
6522 ws_index: usize,
6523 e: &Engine,
6524 o_m: &ResidentStepBf16RowParallel,
6525 session_parts: &[Vec<[u64; 4]>],
6526 tab_keys: &[u64],
6527 positions: &[i32],
6528 stage_pos: bool,
6529 same_session: bool,
6530 q_norms: &[CudaSlice<f32>],
6531 k_norms: &[CudaSlice<f32>],
6532 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6533 t: usize,
6534 head_dim: usize,
6535 n_rot: usize,
6536 window: usize,
6537 max_ns: usize,
6538 scale: f32,
6539 k_tok_bytes: usize,
6540 v_tok_bytes: usize,
6541 eps: f32,
6542 rope_base: f32,
6543 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6544 use cudarc::driver::DevicePtr;
6545 let ranks = self.ranks.len();
6546 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6547 return Err("rope fa rows geometry".into());
6548 }
6549 {
6550 let mut guard = self
6551 .decode_v2
6552 .lock()
6553 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6554 let ws = guard
6555 .get_mut(ws_index)
6556 .ok_or("step TP decode v2 workspace index out of range")?;
6557 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6558 return Err("rope fa rows without tcol slabs".into());
6559 }
6560 let lq = ws.local_q_dim;
6561 let lkv = ws.local_kv_dim;
6562 let lg = (ws.heads / ranks).max(1);
6563 let local_heads = (ws.heads / ranks).max(1);
6564 let local_kv_heads = (lkv / head_dim).max(1);
6565 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6567 ws.fa2_q.clear();
6568 ws.fa2_gate.clear();
6569 ws.fa2_gated.clear();
6570 ws.rope_k_t.clear();
6571 ws.rope_ctr_t.clear();
6572 ws.rope_pos_t.clear();
6573 ws.rows_tab_t.clear();
6574 for engine in &self.ranks {
6575 let _m = engine.gpu.enter_main()?;
6576 ws.fa2_q.push(engine.uninit(32 * lq)?);
6577 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6578 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6579 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6580 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6581 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6582 ws.rows_tab_t
6583 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6584 }
6585 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6586 ws.fa2_cap = 32;
6587 }
6588 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6589 ws.tcol_gated.clear();
6590 ws.tcol_opart.clear();
6591 for engine in &self.ranks {
6592 let _m = engine.gpu.enter_main()?;
6593 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6594 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6595 }
6596 let root = &self.ranks[0];
6597 let _m = root.gpu.enter_main()?;
6598 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6599 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6600 ws.tcol_ocap = 32;
6601 }
6602 for rank in 0..ranks {
6603 let engine = &self.ranks[rank];
6604 let _main = engine.gpu.enter_main()?;
6605 if stage_pos {
6606 let host: Vec<i32> = positions[..t].to_vec();
6607 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6608 engine.stream().memcpy_htod(&host, &mut view)?;
6609 }
6610 let ctr_base = {
6629 let s = engine.stream();
6630 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6631 p
6632 };
6633 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6634 if rows_tab_stale_scan() {
6640 if ws.rows_tab_shadow.len() != ranks {
6641 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6642 }
6643 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6644 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank])
6645 && prev != &host
6646 {
6647 let words = ["k", "v", "len", "base", "ctr", "back"];
6648 let moved: Vec<String> = (0..host.len())
6649 .filter(|&i| prev.get(i) != Some(&host[i]))
6650 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6651 .collect();
6652 let stale =
6653 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6654 eprintln!(
6655 "[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",
6656 tab_keys[rank],
6657 moved.join(",")
6658 );
6659 }
6660 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
6661 }
6662 let legacy_memo = !rows_tab_restage_on();
6663 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6664 let tab = engine.stream().clone_htod(&host)?;
6665 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6666 }
6667 if !legacy_memo {
6668 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
6669 engine.stream().memcpy_htod(&host, &mut view)?;
6670 }
6671 let StepTpDecodeV2Ws {
6672 tcol_q,
6673 tcol_k,
6674 tcol_v,
6675 tcol_g,
6676 fa2_q,
6677 fa2_gated,
6678 rope_k_t,
6679 rope_pos_t,
6680 rows_tabs,
6681 rows_tab_t,
6682 ..
6683 } = &mut *ws;
6684 let tab = if legacy_memo {
6685 rows_tabs[rank]
6686 .get(&tab_keys[rank])
6687 .ok_or("rows tab memo lost its entry")?
6688 } else {
6689 &rows_tab_t[rank]
6690 };
6691 engine.qk_norm_rope_append_inc_dcw_rows(
6692 &tcol_q[rank],
6693 &tcol_k[rank],
6694 &tcol_v[rank],
6695 &q_norms[rank],
6696 &k_norms[rank],
6697 &mut fa2_q[rank],
6698 &mut rope_k_t[rank],
6699 tab,
6700 &rope_pos_t[rank],
6701 same_session,
6702 t,
6703 lkv,
6704 lkv,
6705 k_tok_bytes,
6706 v_tok_bytes,
6707 head_dim,
6708 n_rot,
6709 local_heads,
6710 local_kv_heads,
6711 eps,
6712 rope_base,
6713 1.0,
6714 rope_freqs[rank],
6715 )?;
6716 engine.fa_decode_dcw_rows(
6717 &fa2_q[rank],
6718 tab,
6719 &mut fa2_gated[rank],
6720 t,
6721 head_dim,
6722 local_heads,
6723 local_kv_heads,
6724 window,
6725 max_ns,
6726 scale,
6727 k_tok_bytes,
6728 v_tok_bytes,
6729 &tcol_g[rank],
6730 )?;
6731 let StepTpDecodeV2Ws {
6732 fa2_gated,
6733 tcol_gated,
6734 ..
6735 } = &mut *ws;
6736 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6737 engine
6738 .stream()
6739 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6740 }
6741 }
6742 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6743 }
6744
6745 #[allow(clippy::too_many_arguments)]
6752 pub(crate) fn decode_v2_fa_rows_join(
6753 &self,
6754 ws_index: usize,
6755 e: &Engine,
6756 o_m: &ResidentStepBf16RowParallel,
6757 tabs: &[&crate::CudaSlice<u64>],
6758 t: usize,
6759 head_dim: usize,
6760 window: usize,
6761 max_ns: usize,
6762 scale: f32,
6763 k_tok_bytes: usize,
6764 v_tok_bytes: usize,
6765 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6766 let ranks = self.ranks.len();
6767 if tabs.len() != ranks {
6768 return Err("fa rows join needs one table per rank".into());
6769 }
6770 {
6771 let mut guard = self
6772 .decode_v2
6773 .lock()
6774 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6775 let ws = guard
6776 .get_mut(ws_index)
6777 .ok_or("step TP decode v2 workspace index out of range")?;
6778 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6779 return Err("fa rows join without stashed rows".into());
6780 }
6781 let lq = ws.local_q_dim;
6782 let local_heads = (ws.heads / ranks).max(1);
6783 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6784 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6785 ws.tcol_gated.clear();
6786 ws.tcol_opart.clear();
6787 for engine in &self.ranks {
6788 let _m = engine.gpu.enter_main()?;
6789 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6790 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6791 }
6792 let root = &self.ranks[0];
6793 let _m = root.gpu.enter_main()?;
6794 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6795 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6796 ws.tcol_ocap = 32;
6797 }
6798 for rank in 0..ranks {
6799 let engine = &self.ranks[rank];
6800 let _main = engine.gpu.enter_main()?;
6801 {
6802 let StepTpDecodeV2Ws {
6803 fa2_q,
6804 fa2_gate,
6805 fa2_gated,
6806 ..
6807 } = &mut *ws;
6808 engine.fa_decode_dcw_rows(
6809 &fa2_q[rank],
6810 tabs[rank],
6811 &mut fa2_gated[rank],
6812 t,
6813 head_dim,
6814 local_heads,
6815 local_kv_heads,
6816 window,
6817 max_ns,
6818 scale,
6819 k_tok_bytes,
6820 v_tok_bytes,
6821 &fa2_gate[rank],
6822 )?;
6823 }
6824 let StepTpDecodeV2Ws {
6825 fa2_gated,
6826 tcol_gated,
6827 ..
6828 } = &mut *ws;
6829 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6830 engine
6831 .stream()
6832 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6833 }
6834 }
6835 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6836 }
6837
6838 pub(crate) fn decode_v2_stash_gated(
6843 &self,
6844 ws: &mut StepTpDecodeV2Ws,
6845 e: &Engine,
6846 col: usize,
6847 ) -> Result<(), Box<dyn std::error::Error>> {
6848 let ranks = self.ranks.len();
6849 if col >= 32 {
6853 return Err("decode_v2_stash_gated column out of range".into());
6854 }
6855 let lq = ws.local_q_dim;
6856 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6857 ws.tcol_gated.clear();
6858 ws.tcol_opart.clear();
6859 for engine in &self.ranks {
6860 let _m = engine.gpu.enter_main()?;
6861 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6862 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6863 }
6864 let root = &self.ranks[0];
6865 let _m = root.gpu.enter_main()?;
6866 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6867 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6868 ws.tcol_ocap = 32;
6869 }
6870 for rank in 0..ranks {
6871 let engine = &self.ranks[rank];
6872 let _main = engine.gpu.enter_main()?;
6873 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6874 engine
6875 .stream()
6876 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6877 ws.ev_rank[rank].record(&engine.stream())?;
6881 }
6882 {
6883 let _main = e.gpu.enter_main()?;
6884 for ev in ws.ev_rank.iter() {
6885 e.stream().wait(ev)?;
6886 }
6887 }
6888 Ok(())
6889 }
6890
6891 pub(crate) fn decode_v2_oproj_tcol(
6897 &self,
6898 ws_index: usize,
6899 e: &Engine,
6900 o_m: &ResidentStepBf16RowParallel,
6901 t: usize,
6902 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6903 let ranks = self.ranks.len();
6904 let mut guard = self
6905 .decode_v2
6906 .lock()
6907 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6908 let ws = guard
6909 .get_mut(ws_index)
6910 .ok_or("step TP decode v2 workspace index out of range")?;
6911 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6912 return Err("decode_v2_oproj_tcol geometry".into());
6913 }
6914 for rank in 0..ranks {
6915 let engine = &self.ranks[rank];
6916 let _main = engine.gpu.enter_main()?;
6917 let mut weights = Vec::with_capacity(4);
6918 for block in 0..4 {
6919 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6920 return Err("tcol o_proj requires bf16-resident O blocks".into());
6921 };
6922 weights.push(weight);
6923 }
6924 {
6925 let StepTpDecodeV2Ws {
6926 tcol_gated,
6927 tcol_opart,
6928 local_q_dim,
6929 o_block_cols,
6930 o_out,
6931 w8t_oaq,
6932 w8t_oad,
6933 w8t_oin,
6934 w8t_cap,
6935 ..
6936 } = &mut *ws;
6937 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6940 let refk = *REFK
6941 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6942 if refk {
6943 let lq = *local_q_dim;
6944 let mut xr = engine.uninit(lq)?;
6945 let mut yr = engine.uninit(*o_out)?;
6946 for c in 0..t {
6947 {
6948 let mut dst = xr.slice_mut(0..lq);
6949 engine.stream().memcpy_dtod(
6950 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6951 &mut dst,
6952 )?;
6953 }
6954 engine.matvec_bf16_b4_into(
6955 [weights[0], weights[1], weights[2], weights[3]],
6956 &xr,
6957 &mut yr,
6958 *o_block_cols,
6959 *o_out,
6960 )?;
6961 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6962 engine
6963 .stream()
6964 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6965 }
6966 } else if crate::step_tp_w8_on()
6967 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
6968 && (4 * *o_block_cols) % 32 == 0
6969 {
6970 let in_f = 4 * *o_block_cols;
6974 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
6975 w8t_oaq.clear();
6976 w8t_oad.clear();
6977 for e_rank in &self.ranks {
6978 let _m = e_rank.gpu.enter_main()?;
6979 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6980 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6981 }
6982 *w8t_oin = in_f;
6983 *w8t_cap = (*w8t_cap).max(32);
6984 }
6985 engine.quantize_q8_1_into(
6986 &tcol_gated[rank],
6987 t,
6988 in_f,
6989 &mut w8t_oaq[rank],
6990 &mut w8t_oad[rank],
6991 )?;
6992 engine.qmatvec_q8_0_b4_rp_t_into(
6993 [
6994 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6995 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6996 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6997 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6998 ],
6999 &w8t_oaq[rank],
7000 &w8t_oad[rank],
7001 &mut tcol_opart[rank],
7002 *o_block_cols,
7003 *o_out,
7004 t,
7005 )?;
7006 } else {
7007 engine.matvec_bf16_b4_tcol_into(
7008 [weights[0], weights[1], weights[2], weights[3]],
7009 &tcol_gated[rank],
7010 &mut tcol_opart[rank],
7011 *o_block_cols,
7012 *o_out,
7013 t,
7014 )?;
7015 }
7016 }
7017 if rank != 0 {
7018 ws.ev_rank[rank].record(&engine.stream())?;
7019 }
7020 }
7021 let root = &self.ranks[0];
7022 {
7023 let _main = root.gpu.enter_main()?;
7024 for ev in ws.ev_rank.iter().skip(1) {
7025 root.stream().wait(ev)?;
7026 }
7027 {
7028 let StepTpDecodeV2Ws {
7029 tcol_opart,
7030 tcol_opeer,
7031 tcol_omix,
7032 o_out,
7033 ..
7034 } = &mut *ws;
7035 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
7036 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
7037 {
7038 let mut dst = opeer.slice_mut(0..t * *o_out);
7039 root.stream()
7040 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
7041 }
7042 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
7045 }
7046 ws.ev_oproj.record(&root.stream())?;
7047 }
7048 let _main = e.gpu.enter_main()?;
7049 e.stream().wait(&ws.ev_oproj)?;
7050 let mut out = e.uninit(t * ws.o_out)?;
7051 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
7052 e.stream().memcpy_dtod(
7053 &omix.slice(0..t * ws.o_out),
7054 &mut out.slice_mut(0..t * ws.o_out),
7055 )?;
7056 Ok(out)
7057 }
7058
7059 #[allow(clippy::too_many_arguments)] pub(crate) fn decode_v2_input_qkv(
7061 &self,
7062 ws: &mut StepTpDecodeV2Ws,
7063 e: &Engine,
7064 h: &CudaSlice<f32>,
7065 pos_d: &CudaSlice<i32>,
7066 gate_raw: Option<&CudaSlice<f32>>,
7067 gate_shards: Option<StepTpGateShards<'_>>,
7068 decode_input: &mut ResidentReplicatedDeviceRows,
7069 q_m: &ResidentBf16ColumnParallel,
7070 k_m: &ResidentBf16ColumnParallel,
7071 v_m: &ResidentBf16ColumnParallel,
7072 q_norm: &[CudaSlice<f32>],
7073 k_norm: &[CudaSlice<f32>],
7074 head_dim: usize,
7075 n_rot: usize,
7076 rope_base: f32,
7077 rope_freqs: &[Option<&CudaSlice<f32>>],
7078 rms_eps: f32,
7079 has_gate: bool,
7080 defer_norm_rope: bool,
7081 tcol_col: Option<usize>,
7082 ) -> Result<(), Box<dyn std::error::Error>> {
7083 let ranks = self.ranks.len();
7084 validate_replicated_device_rows(&self.ranks, decode_input)?;
7085 let gate_sources = usize::from(gate_raw.is_some()) + usize::from(gate_shards.is_some());
7086 if decode_input.tokens != 1
7087 || decode_input.width != q_m.in_features
7088 || pos_d.len() != 1
7089 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
7090 || (has_gate && gate_sources != 1)
7091 || (!has_gate && gate_sources != 0)
7092 || gate_shards.as_ref().is_some_and(|shards| match shards {
7093 StepTpGateShards::F32(shards) => shards.len() != ranks,
7094 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
7095 })
7096 || q_norm.len() != ranks
7097 || k_norm.len() != ranks
7098 || rope_freqs.len() != ranks
7099 || e.ctx().ordinal() != ws.e_device
7100 {
7101 return Err("step TP decode v2 input geometry mismatch".into());
7102 }
7103
7104 let qkv_fused = step_tp_qkv_fused_enabled()?;
7105 if gate_shards.is_some() && !qkv_fused {
7106 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
7107 }
7108 let values = decode_input.width;
7109 if h.len() != values {
7110 return Err(format!(
7111 "step TP decode v2 hidden width {} != replicated width {values}",
7112 h.len()
7113 )
7114 .into());
7115 }
7116
7117 if qkv_fused {
7118 if ws.h_stage.is_none() {
7122 use cudarc::driver::DevicePtr;
7123 let _main = e.gpu.enter_main()?;
7124 let h_stage = e.uninit(values)?;
7125 let pos_stage = e.htod_i32(&[0])?;
7126 {
7127 let stream = e.stream();
7128 let (hp, _g0) = h_stage.device_ptr(&stream);
7129 let (pp, _g1) = pos_stage.device_ptr(&stream);
7130 ws.raw_h_stage = hp;
7131 ws.raw_pos_stage = pp;
7132 }
7133 ws.h_stage = Some(h_stage);
7134 ws.pos_stage = Some(pos_stage);
7135 for rank in 0..ranks {
7136 use cudarc::driver::DevicePtr;
7137 let engine = &self.ranks[rank];
7138 let _rmain = engine.gpu.enter_main()?;
7139 let attn_in = engine.uninit(values)?;
7140 let (dp, pp) = {
7141 let stream = engine.stream();
7142 let (dp, _g2) = attn_in.device_ptr(&stream);
7143 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
7144 (dp, pp)
7145 };
7146 ws.raw_attn_in.push(dp);
7147 ws.raw_pos.push(pp);
7148 ws.attn_in.push(attn_in);
7149 }
7150 {
7151 use cudarc::driver::DevicePtr;
7152 let root = &self.ranks[0];
7153 let _rmain = root.gpu.enter_main()?;
7154 let stream = root.stream();
7155 let (a, _g) = ws.peer_partial.device_ptr(&stream);
7156 let (b, _g) = ws.k_shadow.device_ptr(&stream);
7157 let (c, _g) = ws.v_shadow.device_ptr(&stream);
7158 ws.raw_peer_partial = a;
7159 ws.raw_k_shadow = b;
7160 ws.raw_v_shadow = c;
7161 }
7162 {
7163 use cudarc::driver::DevicePtr;
7164 let rank1 = &self.ranks[1];
7165 let _rmain = rank1.gpu.enter_main()?;
7166 let stream = rank1.stream();
7167 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
7168 let (b, _g) = ws.k[1].device_ptr(&stream);
7169 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
7170 ws.raw_o_partial1 = a;
7171 ws.raw_k1 = b;
7172 ws.raw_v1 = c;
7173 }
7174 }
7175 {
7176 let _main = e.gpu.enter_main()?;
7177 {
7178 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
7181 let mut dst = h_stage.slice_mut(0..values);
7182 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
7183 }
7184 {
7185 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
7186 let mut dst = pos_stage.slice_mut(0..1);
7187 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
7188 }
7189 ws.ev_entry.record(&e.stream())?;
7190 }
7191 for rank in 0..ranks {
7192 let engine = &self.ranks[rank];
7193 let _main = engine.gpu.enter_main()?;
7194 engine.stream().wait(&ws.ev_entry)?;
7195 }
7196 } else {
7197 {
7199 let _main = e.gpu.enter_main()?;
7200 if let Some(gate_raw) = gate_raw {
7201 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
7202 e.stream()
7203 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
7204 }
7205 ws.ev_entry.record(&e.stream())?;
7206 }
7207 {
7208 let root = &self.ranks[0];
7209 let _main = root.gpu.enter_main()?;
7210 root.stream().wait(&ws.ev_entry)?;
7211 let mut destination = decode_input.ranks[0].slice_mut(0..values);
7212 root.stream()
7213 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
7214 ws.ev_refresh.record(&root.stream())?;
7215 }
7216 for rank in 1..ranks {
7217 let engine = &self.ranks[rank];
7218 let _main = engine.gpu.enter_main()?;
7219 engine.stream().wait(&ws.ev_refresh)?;
7220 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
7221 let mut destination = peer_rows[0].slice_mut(0..values);
7222 engine
7223 .stream()
7224 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
7225 }
7226 }
7227 for rank in 0..ranks {
7228 self.decode_v2_input_qkv_rank(
7229 ws,
7230 pos_d,
7231 decode_input,
7232 q_m,
7233 k_m,
7234 v_m,
7235 q_norm,
7236 k_norm,
7237 head_dim,
7238 n_rot,
7239 rope_base,
7240 rope_freqs,
7241 rms_eps,
7242 gate_shards.as_ref(),
7243 has_gate,
7244 qkv_fused,
7245 defer_norm_rope,
7246 rank,
7247 tcol_col,
7248 )?;
7249 }
7250 Ok(())
7251 }
7252
7253 #[allow(clippy::too_many_arguments)]
7256 pub(crate) fn decode_v2_input_qkv_rank(
7257 &self,
7258 ws: &mut StepTpDecodeV2Ws,
7259 pos_d: &CudaSlice<i32>,
7260 decode_input: &mut ResidentReplicatedDeviceRows,
7261 q_m: &ResidentBf16ColumnParallel,
7262 k_m: &ResidentBf16ColumnParallel,
7263 v_m: &ResidentBf16ColumnParallel,
7264 q_norm: &[CudaSlice<f32>],
7265 k_norm: &[CudaSlice<f32>],
7266 head_dim: usize,
7267 n_rot: usize,
7268 rope_base: f32,
7269 rope_freqs: &[Option<&CudaSlice<f32>>],
7270 rms_eps: f32,
7271 gate_shards: Option<&StepTpGateShards<'_>>,
7272 has_gate: bool,
7273 qkv_fused: bool,
7274 defer_norm_rope: bool,
7275 rank: usize,
7276 tcol_col: Option<usize>,
7277 ) -> Result<(), Box<dyn std::error::Error>> {
7278 let ranks = self.ranks.len();
7279 let local_heads = ws.local_q_dim / head_dim;
7280 let local_kv_heads = ws.local_kv_dim / head_dim;
7281 let engine = &self.ranks[rank];
7282 let _main = engine.gpu.enter_main()?;
7283 let ws_e_device = ws.e_device;
7284 if qkv_fused && tcol_col.is_some() {
7289 #[allow(clippy::unnecessary_unwrap)]
7290 let c = tcol_col.expect("checked");
7292 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
7293 return Err("tcol select without precompute".into());
7294 }
7295 if engine.ctx().ordinal() != ws_e_device {
7299 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7300 }
7301 let StepTpDecodeV2Ws {
7302 tcol_q,
7303 tcol_k,
7304 tcol_v,
7305 tcol_g,
7306 q_raw,
7307 k_raw,
7308 v_raw,
7309 gate,
7310 local_q_dim,
7311 local_kv_dim,
7312 heads,
7313 ..
7314 } = &mut *ws;
7315 let lg = *heads / ranks;
7316 let stream = engine.stream();
7317 {
7318 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
7319 stream.memcpy_dtod(
7320 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
7321 &mut dst,
7322 )?;
7323 }
7324 {
7325 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
7326 stream.memcpy_dtod(
7327 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7328 &mut dst,
7329 )?;
7330 }
7331 {
7332 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
7333 stream.memcpy_dtod(
7334 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7335 &mut dst,
7336 )?;
7337 }
7338 if has_gate && lg > 0 {
7339 let mut dst = gate[rank].slice_mut(0..lg);
7340 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
7341 }
7342 if !defer_norm_rope {
7343 } else {
7347 return Ok(());
7348 }
7349 }
7350 if qkv_fused {
7351 let same_dev = engine.ctx().ordinal() == ws.e_device;
7356 if !same_dev {
7357 raw_copy_bytes(
7358 ws.raw_attn_in[rank],
7359 ws.raw_h_stage,
7360 q_m.in_features * 4,
7361 engine,
7362 )?;
7363 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7364 }
7365 let StepTpDecodeV2Ws {
7366 q_raw,
7367 k_raw,
7368 v_raw,
7369 gate,
7370 gate_e,
7371 attn_in,
7372 h_stage,
7373 heads,
7374 local_q_dim,
7375 local_kv_dim,
7376 w8_aq,
7377 w8_ad,
7378 w8_in,
7379 ..
7380 } = &mut *ws;
7381 let input_ref: &CudaSlice<f32> = if same_dev {
7382 h_stage
7383 .as_ref()
7384 .ok_or("step TP decode v2 stage not armed")?
7385 } else {
7386 &attn_in[rank]
7387 };
7388 match (
7389 &q_m.ranks[rank].weight,
7390 &k_m.ranks[rank].weight,
7391 &v_m.ranks[rank].weight,
7392 ) {
7393 (
7394 ResidentBf16Weight::F32(wq),
7395 ResidentBf16Weight::F32(wk),
7396 ResidentBf16Weight::F32(wv),
7397 ) => {
7398 let (wg, out_g) = match &gate_shards {
7399 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
7400 Some(StepTpGateShards::Bf16(_)) => {
7401 return Err("step TP decode v2 gate shard class does not \
7402 match the F32 projections"
7403 .into());
7404 }
7405 None => (&*gate_e, 0),
7407 };
7408 engine.matvec_f32_qkv_into(
7409 wq,
7410 wk,
7411 wv,
7412 wg,
7413 input_ref,
7414 &mut q_raw[rank],
7415 &mut k_raw[rank],
7416 &mut v_raw[rank],
7417 &mut gate[rank],
7418 q_m.in_features,
7419 *local_q_dim,
7420 *local_kv_dim,
7421 out_g,
7422 )?;
7423 }
7424 (
7425 ResidentBf16Weight::Bf16(wq),
7426 ResidentBf16Weight::Bf16(wk),
7427 ResidentBf16Weight::Bf16(wv),
7428 ) => {
7429 let (wg, out_g) = match &gate_shards {
7430 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
7431 Some(StepTpGateShards::F32(_)) => {
7432 return Err("step TP decode v2 gate shard class does not \
7433 match the bf16 projections"
7434 .into());
7435 }
7436 None => (wq, 0),
7437 };
7438 let in_f = q_m.in_features;
7445 let q8_ready = crate::step_tp_w8_on()
7446 && q_m.ranks[rank].q8.is_some()
7447 && k_m.ranks[rank].q8.is_some()
7448 && v_m.ranks[rank].q8.is_some();
7449 if q8_ready {
7450 if *w8_in != in_f || w8_aq.len() != ranks {
7451 w8_aq.clear();
7452 w8_ad.clear();
7453 for e_rank in &self.ranks {
7454 let _m = e_rank.gpu.enter_main()?;
7455 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7456 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7457 }
7458 *w8_in = in_f;
7459 }
7460 engine.quantize_q8_1_into(
7461 input_ref,
7462 1,
7463 in_f,
7464 &mut w8_aq[rank],
7465 &mut w8_ad[rank],
7466 )?;
7467 engine.qmatvec_q8_0_qkv_rp_into(
7472 q_m.ranks[rank].q8.as_ref().unwrap(),
7473 k_m.ranks[rank].q8.as_ref().unwrap(),
7474 v_m.ranks[rank].q8.as_ref().unwrap(),
7475 &w8_aq[rank],
7476 &w8_ad[rank],
7477 &mut q_raw[rank],
7478 &mut k_raw[rank],
7479 &mut v_raw[rank],
7480 in_f,
7481 *local_q_dim,
7482 *local_kv_dim,
7483 )?;
7484 if out_g > 0 {
7485 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7486 }
7487 } else {
7488 engine.matvec_bf16_qkvg_into(
7489 wq,
7490 wk,
7491 wv,
7492 wg,
7493 input_ref,
7494 &mut q_raw[rank],
7495 &mut k_raw[rank],
7496 &mut v_raw[rank],
7497 &mut gate[rank],
7498 q_m.in_features,
7499 *local_q_dim,
7500 *local_kv_dim,
7501 out_g,
7502 )?;
7503 }
7504 }
7505 _ => {
7506 return Err("step TP decode v2 QKV projections mix residency classes".into());
7507 }
7508 }
7509 } else {
7510 for (matrix, local_out, raw) in [
7511 (q_m, ws.local_q_dim, &mut ws.q_raw),
7512 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7513 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7514 ] {
7515 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7516 return Err("step TP decode v2 lost its F32 projection residency".into());
7517 };
7518 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7519 engine.linear_f32_resident_canonical_rows_t1_into(
7520 &decode_input.ranks[rank],
7521 values_w,
7522 &mut raw[rank],
7523 matrix.in_features,
7524 local_out,
7525 chunk_rows,
7526 )?;
7527 }
7528 }
7529 if qkv_fused && defer_norm_rope {
7530 } else if qkv_fused {
7532 let StepTpDecodeV2Ws {
7535 q_raw,
7536 k_raw,
7537 q,
7538 k,
7539 pos,
7540 pos_stage,
7541 ..
7542 } = &mut *ws;
7543 let same_dev = engine.ctx().ordinal() == ws_e_device;
7544 let pos_ref: &CudaSlice<i32> = if same_dev {
7545 pos_stage
7546 .as_ref()
7547 .ok_or("step TP decode v2 pos stage not armed")?
7548 } else {
7549 &pos[rank]
7550 };
7551 engine.qk_norm_rope_into(
7552 &q_raw[rank],
7553 &k_raw[rank],
7554 &q_norm[rank],
7555 &k_norm[rank],
7556 &mut q[rank],
7557 &mut k[rank],
7558 pos_ref,
7559 head_dim,
7560 n_rot,
7561 local_heads,
7562 local_kv_heads,
7563 rms_eps,
7564 rope_base,
7565 1.0,
7566 rope_freqs[rank],
7567 )?;
7568 } else {
7569 engine.rms_norm(
7570 &ws.q_raw[rank],
7571 &q_norm[rank],
7572 &mut ws.q[rank],
7573 head_dim,
7574 local_heads,
7575 rms_eps,
7576 )?;
7577 engine.rms_norm(
7578 &ws.k_raw[rank],
7579 &k_norm[rank],
7580 &mut ws.k[rank],
7581 head_dim,
7582 local_kv_heads,
7583 rms_eps,
7584 )?;
7585 {
7586 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7587 engine
7588 .stream()
7589 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7590 }
7591 engine.rope_neox2(
7592 &mut ws.q[rank],
7593 &mut ws.k[rank],
7594 &ws.pos[rank],
7595 head_dim,
7596 n_rot,
7597 local_heads,
7598 local_kv_heads,
7599 1,
7600 rope_base,
7601 1.0,
7602 rope_freqs[rank],
7603 )?;
7604 }
7605 if has_gate && gate_shards.is_none() {
7606 let gate_start = rank * (ws.heads / ranks);
7607 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7608 engine.stream().memcpy_dtod(
7609 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7610 &mut gate_dst,
7611 )?;
7612 }
7613 Ok(())
7614 }
7615
7616 pub(crate) fn decode_v2_finish_rank_partial(
7620 &self,
7621 ws: &mut StepTpDecodeV2Ws,
7622 o_m: &ResidentStepBf16RowParallel,
7623 o_fused: bool,
7624 rank: usize,
7625 ) -> Result<(), Box<dyn std::error::Error>> {
7626 let engine = &self.ranks[rank];
7627 let _main = engine.gpu.enter_main()?;
7628 if o_fused {
7629 let StepTpDecodeV2Ws {
7630 gated,
7631 o_partials,
7632 o_block_cols,
7633 o_out,
7634 w8o_aq,
7635 w8o_ad,
7636 w8o_in,
7637 ..
7638 } = &mut *ws;
7639 let all_f32 = o_m.ranks[rank]
7640 .iter()
7641 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7642 if all_f32 {
7643 let mut weights = Vec::with_capacity(4);
7644 for block in 0..4 {
7645 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7646 unreachable!("all_f32 checked above");
7647 };
7648 weights.push(weight);
7649 }
7650 engine.matvec_f32_b4_into(
7651 [weights[0], weights[1], weights[2], weights[3]],
7652 &gated[rank],
7653 &mut o_partials[rank][0],
7654 *o_block_cols,
7655 *o_out,
7656 )?;
7657 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
7658 let in_f = 4 * *o_block_cols;
7663 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
7664 w8o_aq.clear();
7665 w8o_ad.clear();
7666 for e_rank in &self.ranks {
7667 let _m = e_rank.gpu.enter_main()?;
7668 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7669 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7670 }
7671 *w8o_in = in_f;
7672 }
7673 engine.quantize_q8_1_into(
7674 &gated[rank],
7675 1,
7676 in_f,
7677 &mut w8o_aq[rank],
7678 &mut w8o_ad[rank],
7679 )?;
7680 engine.qmatvec_q8_0_b4_rp_into(
7681 [
7682 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7683 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7684 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7685 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7686 ],
7687 &w8o_aq[rank],
7688 &w8o_ad[rank],
7689 &mut o_partials[rank][0],
7690 *o_block_cols,
7691 *o_out,
7692 )?;
7693 } else {
7694 let mut weights = Vec::with_capacity(4);
7695 for block in 0..4 {
7696 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7697 return Err("step TP decode v2 O projections mix residency classes".into());
7698 };
7699 weights.push(weight);
7700 }
7701 engine.matvec_bf16_b4_into(
7702 [weights[0], weights[1], weights[2], weights[3]],
7703 &gated[rank],
7704 &mut o_partials[rank][0],
7705 *o_block_cols,
7706 *o_out,
7707 )?;
7708 }
7709 } else {
7710 for block in 0..ws.blocks_per_rank {
7711 let x =
7712 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7713 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7714 match &o_m.ranks[rank][block].weight {
7715 ResidentBf16Weight::F32(weight) => {
7716 let w = weight.slice(0..weight.len());
7717 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7718 }
7719 ResidentBf16Weight::Bf16(weight) => {
7720 engine.matvec_bf16_views_into(
7721 weight,
7722 &x,
7723 &mut y,
7724 ws.o_block_cols,
7725 ws.o_out,
7726 )?;
7727 }
7728 }
7729 }
7730 }
7731 Ok(())
7732 }
7733
7734 pub(crate) fn decode_v2_finish(
7742 &self,
7743 ws: &mut StepTpDecodeV2Ws,
7744 e: &Engine,
7745 o_m: &ResidentStepBf16RowParallel,
7746 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7747 let ranks = self.ranks.len();
7748 if e.ctx().ordinal() != ws.e_device {
7749 return Err("step TP decode v2 finish engine changed".into());
7750 }
7751 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7756
7757 for rank in 0..ranks {
7760 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7761 if rank == 0 {
7762 continue;
7765 }
7766 let engine = &self.ranks[rank];
7767 let _main = engine.gpu.enter_main()?;
7768 ws.ev_rank[rank].record(&engine.stream())?;
7769 }
7770
7771 let root = &self.ranks[0];
7773 #[allow(unused_assignments)]
7774 let mut final_in_a = false;
7775 {
7776 let _main = root.gpu.enter_main()?;
7777 for ev in ws.ev_rank.iter().skip(1) {
7778 root.stream().wait(ev)?;
7779 }
7780 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7781 ws.ev_oproj.record(&root.stream())?;
7787 let _main = e.gpu.enter_main()?;
7788 e.stream().wait(&ws.ev_oproj)?;
7789 let mut output = e.uninit(ws.o_out)?;
7790 if oproj_tail_on() && oproj_tail_eligible() {
7791 use cudarc::driver::DevicePtr;
7794 let stream = e.stream();
7795 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7796 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7797 set_oproj_tail((p0, p1));
7798 return Ok(output);
7799 }
7800 e.add(
7801 &ws.o_partials[0][0],
7802 &ws.o_partials[1][0],
7803 &mut output,
7804 ws.o_out,
7805 )?;
7806 return Ok(output);
7807 }
7808 if o_fused {
7809 self.decode_v2_finish_root_fused(ws)?;
7810 ws.ev_oproj.record(&root.stream())?;
7811 let _main = e.gpu.enter_main()?;
7812 e.stream().wait(&ws.ev_oproj)?;
7813 let mut output = e.uninit(ws.o_out)?;
7814 e.stream().memcpy_dtod(
7815 &ws.reduce_a.slice(0..ws.o_out),
7816 &mut output.slice_mut(0..ws.o_out),
7817 )?;
7818 return Ok(output);
7819 }
7820 let mut first = true;
7821 let mut current_is_a = false;
7822 for rank in 0..ranks {
7823 for block in 0..ws.blocks_per_rank {
7824 let use_peer = rank != 0;
7825 if use_peer {
7826 raw_copy_bytes(
7827 ws.raw_peer_partial,
7828 ws.raw_o_partials[rank][block],
7829 ws.o_out * std::mem::size_of::<f32>(),
7830 root,
7831 )?;
7832 }
7833 match (first, current_is_a, use_peer) {
7835 (true, _, true) => {
7836 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7837 }
7838 (true, _, false) => root.add(
7839 &ws.zeros,
7840 &ws.o_partials[0][block],
7841 &mut ws.reduce_a,
7842 ws.o_out,
7843 )?,
7844 (false, true, true) => {
7845 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7846 }
7847 (false, true, false) => root.add(
7848 &ws.reduce_a,
7849 &ws.o_partials[0][block],
7850 &mut ws.reduce_b,
7851 ws.o_out,
7852 )?,
7853 (false, false, true) => {
7854 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7855 }
7856 (false, false, false) => root.add(
7857 &ws.reduce_b,
7858 &ws.o_partials[0][block],
7859 &mut ws.reduce_a,
7860 ws.o_out,
7861 )?,
7862 }
7863 current_is_a = first || !current_is_a;
7864 first = false;
7865 }
7866 }
7867 final_in_a = current_is_a;
7868
7869 if !no_local_shadow_on() {
7870 let bytes = ws.local_kv_dim * std::mem::size_of::<f32>();
7871 for rank in 0..ranks {
7872 let offset = rank * bytes;
7873 raw_copy_bytes(ws.raw_k_shadow + offset as u64, ws.raw_k[rank], bytes, root)?;
7874 raw_copy_bytes(
7875 ws.raw_v_shadow + offset as u64,
7876 ws.raw_v_raw[rank],
7877 bytes,
7878 root,
7879 )?;
7880 }
7881 }
7882 ws.ev_oproj.record(&root.stream())?;
7883 }
7884
7885 let _main = e.gpu.enter_main()?;
7889 e.stream().wait(&ws.ev_oproj)?;
7890 let mut output = e.uninit(ws.o_out)?;
7891 let source = if final_in_a {
7892 &ws.reduce_a
7893 } else {
7894 &ws.reduce_b
7895 };
7896 e.stream().memcpy_dtod(
7897 &source.slice(0..ws.o_out),
7898 &mut output.slice_mut(0..ws.o_out),
7899 )?;
7900 Ok(output)
7901 }
7902
7903 #[allow(clippy::too_many_arguments)] pub fn run_routed_experts(
7905 &self,
7906 experts: &ResidentExpertParallel,
7907 input: &[f32],
7908 tokens: usize,
7909 selected: &[usize],
7910 route_weights: &[f32],
7911 experts_per_token: usize,
7912 activation_limit: Option<f32>,
7913 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7914 validate_step_expert_activation_limit(activation_limit)?;
7915 validate_ep_residency(&self.ranks, experts)?;
7916 validate_activations(input, tokens, experts.input_width)?;
7917 let pairs = tokens
7918 .checked_mul(experts_per_token)
7919 .ok_or("EP route count overflow")?;
7920 if selected.len() != pairs || route_weights.len() != pairs {
7921 return Err(format!(
7922 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7923 {experts_per_token} ({pairs})",
7924 selected.len(),
7925 route_weights.len(),
7926 )
7927 .into());
7928 }
7929 if !route_weights.iter().all(|weight| weight.is_finite()) {
7930 return Err("EP route weights contain a non-finite value".into());
7931 }
7932 if self.native_p2p {
7933 return self.run_routed_experts_native(
7934 experts,
7935 input,
7936 tokens,
7937 selected,
7938 route_weights,
7939 experts_per_token,
7940 activation_limit,
7941 );
7942 }
7943
7944 let mut output = vec![0.0f32; tokens * experts.input_width];
7945 let per_rank = experts.expert_count / experts.ranks.len();
7946 for token in 0..tokens {
7947 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7948 for slot in 0..experts_per_token {
7949 let pair = token * experts_per_token + slot;
7950 let expert = selected[pair];
7951 if expert >= experts.expert_count {
7952 return Err(format!(
7953 "EP selected expert {expert} outside 0..{}",
7954 experts.expert_count
7955 )
7956 .into());
7957 }
7958 let owner = expert / per_rank;
7959 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7960 let rank = &experts.ranks[owner];
7961 let engine = &self.ranks[owner];
7962 let gate =
7963 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7964 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7965 let activated: Vec<f32> = gate
7966 .iter()
7967 .zip(&up)
7968 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7969 .collect();
7970 debug_assert_eq!(activated.len(), experts.expert_width);
7971 let down =
7972 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7973 let weight = route_weights[pair];
7974 for (sum, value) in output
7975 [token * experts.input_width..(token + 1) * experts.input_width]
7976 .iter_mut()
7977 .zip(down)
7978 {
7979 *sum += weight * value;
7980 }
7981 }
7982 }
7983 Ok(output)
7984 }
7985
7986 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native(
7988 &self,
7989 experts: &ResidentExpertParallel,
7990 input: &[f32],
7991 tokens: usize,
7992 selected: &[usize],
7993 route_weights: &[f32],
7994 experts_per_token: usize,
7995 activation_limit: Option<f32>,
7996 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7997 if !self.native_p2p || self.ranks.len() < 2 {
7998 return Err("native EP execution requires at least two P2P ranks".into());
7999 }
8000 if self.ep_device_arithmetic {
8001 return self.run_routed_experts_native_device(
8002 experts,
8003 input,
8004 tokens,
8005 selected,
8006 route_weights,
8007 experts_per_token,
8008 activation_limit,
8009 );
8010 }
8011 let mut output = vec![0.0f32; tokens * experts.input_width];
8012 let per_rank = experts.expert_count / experts.ranks.len();
8013 for token in 0..tokens {
8014 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8015 let mut rank_inputs = (0..self.ranks.len())
8016 .map(|_| None)
8017 .collect::<Vec<Option<CudaSlice<f32>>>>();
8018 rank_inputs[0] = Some({
8019 let root = &self.ranks[0];
8020 let _main = root.gpu.enter_main()?;
8021 root.htod(input_row)?
8022 });
8023
8024 for slot in 0..experts_per_token {
8025 let pair = token * experts_per_token + slot;
8026 let expert = selected[pair];
8027 if expert >= experts.expert_count {
8028 return Err(format!(
8029 "EP selected expert {expert} outside 0..{}",
8030 experts.expert_count
8031 )
8032 .into());
8033 }
8034 let owner = expert / per_rank;
8035 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8036 if rank_inputs[owner].is_none() {
8037 let peer_input = {
8038 let root_input = rank_inputs[0]
8039 .as_ref()
8040 .ok_or("native EP lost its root input")?;
8041 let engine = &self.ranks[owner];
8042 let _main = engine.gpu.enter_main()?;
8043 let mut peer_input = engine.uninit(experts.input_width)?;
8044 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8045 peer_input
8046 };
8047 rank_inputs[owner] = Some(peer_input);
8048 }
8049
8050 let rank = &experts.ranks[owner];
8051 let engine = &self.ranks[owner];
8052 let owner_input = rank_inputs[owner]
8053 .as_ref()
8054 .ok_or("native EP owner input is absent after dispatch")?;
8055 let gate = run_resident_bank_expert_device(
8056 engine,
8057 &rank.gate,
8058 local_expert,
8059 owner_input,
8060 1,
8061 )?;
8062 let up = run_resident_bank_expert_device(
8063 engine,
8064 &rank.up,
8065 local_expert,
8066 owner_input,
8067 1,
8068 )?;
8069 let (gate, up) = {
8070 let _main = engine.gpu.enter_main()?;
8071 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
8072 };
8073 let activated = gate
8074 .iter()
8075 .zip(&up)
8076 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8077 .collect::<Vec<_>>();
8078 debug_assert_eq!(activated.len(), experts.expert_width);
8079 let activated = {
8080 let _main = engine.gpu.enter_main()?;
8081 engine.htod(&activated)?
8082 };
8083 let down = run_resident_bank_expert_device(
8084 engine,
8085 &rank.down,
8086 local_expert,
8087 &activated,
8088 1,
8089 )?;
8090 let down = if owner == 0 {
8091 let _main = engine.gpu.enter_main()?;
8092 engine.dtoh(&down)?
8093 } else {
8094 let root = &self.ranks[0];
8095 let _main = root.gpu.enter_main()?;
8096 let mut root_down = root.uninit(experts.input_width)?;
8097 root.stream().memcpy_dtod(&down, &mut root_down)?;
8098 root.dtoh(&root_down)?
8099 };
8100 let weight = route_weights[pair];
8101 for (sum, value) in output
8102 [token * experts.input_width..(token + 1) * experts.input_width]
8103 .iter_mut()
8104 .zip(down)
8105 {
8106 *sum += weight * value;
8107 }
8108 }
8109 }
8110 Ok(output)
8111 }
8112
8113 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native_device(
8115 &self,
8116 experts: &ResidentExpertParallel,
8117 input: &[f32],
8118 tokens: usize,
8119 selected: &[usize],
8120 route_weights: &[f32],
8121 experts_per_token: usize,
8122 activation_limit: Option<f32>,
8123 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8124 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
8125 return Err(
8126 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
8127 );
8128 }
8129 let mut output = Vec::with_capacity(tokens * experts.input_width);
8130 let per_rank = experts.expert_count / experts.ranks.len();
8131 let root = &self.ranks[0];
8132 for token in 0..tokens {
8133 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8134 let mut rank_inputs = (0..self.ranks.len())
8135 .map(|_| None)
8136 .collect::<Vec<Option<CudaSlice<f32>>>>();
8137 rank_inputs[0] = Some({
8138 let _main = root.gpu.enter_main()?;
8139 root.htod(input_row)?
8140 });
8141 let mut root_output = {
8142 let _main = root.gpu.enter_main()?;
8143 root.zeros(experts.input_width)?
8144 };
8145 let mut remote_down_keepalive = Vec::new();
8146
8147 for slot in 0..experts_per_token {
8148 let pair = token * experts_per_token + slot;
8149 let expert = selected[pair];
8150 if expert >= experts.expert_count {
8151 return Err(format!(
8152 "EP selected expert {expert} outside 0..{}",
8153 experts.expert_count
8154 )
8155 .into());
8156 }
8157 let owner = expert / per_rank;
8158 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8159 if rank_inputs[owner].is_none() {
8160 let peer_input = {
8161 let root_input = rank_inputs[0]
8162 .as_ref()
8163 .ok_or("native EP lost its root input")?;
8164 let engine = &self.ranks[owner];
8165 let _main = engine.gpu.enter_main()?;
8166 let mut peer_input = engine.uninit(experts.input_width)?;
8167 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8168 peer_input
8169 };
8170 rank_inputs[owner] = Some(peer_input);
8171 }
8172
8173 let rank = &experts.ranks[owner];
8174 let engine = &self.ranks[owner];
8175 let owner_input = rank_inputs[owner]
8176 .as_ref()
8177 .ok_or("native EP owner input is absent after dispatch")?;
8178 let gate = run_resident_bank_expert_device(
8179 engine,
8180 &rank.gate,
8181 local_expert,
8182 owner_input,
8183 1,
8184 )?;
8185 let up = run_resident_bank_expert_device(
8186 engine,
8187 &rank.up,
8188 local_expert,
8189 owner_input,
8190 1,
8191 )?;
8192 let activated = {
8193 let _main = engine.gpu.enter_main()?;
8194 let mut activated = engine.uninit(experts.expert_width)?;
8195 if let Some(limit) = activation_limit {
8196 engine.silu_clamped_mul_host_expf(
8197 &gate,
8198 &up,
8199 limit,
8200 &mut activated,
8201 experts.expert_width,
8202 )?;
8203 } else {
8204 engine.silu_mul_host_expf(
8205 &gate,
8206 &up,
8207 &mut activated,
8208 experts.expert_width,
8209 )?;
8210 }
8211 activated
8212 };
8213 let down = run_resident_bank_expert_device(
8214 engine,
8215 &rank.down,
8216 local_expert,
8217 &activated,
8218 1,
8219 )?;
8220 let root_down = if owner == 0 {
8221 down
8222 } else {
8223 let _main = root.gpu.enter_main()?;
8224 let mut root_down = root.uninit(experts.input_width)?;
8225 root.stream().memcpy_dtod(&down, &mut root_down)?;
8226 remote_down_keepalive.push(down);
8230 root_down
8231 };
8232 let _main = root.gpu.enter_main()?;
8233 let mut destination = root_output.slice_mut(0..experts.input_width);
8234 root.axpy_host_into(
8235 &root_down.slice(0..root_down.len()),
8236 route_weights[pair],
8237 &mut destination,
8238 experts.input_width,
8239 )?;
8240 }
8241
8242 let _main = root.gpu.enter_main()?;
8243 let root_output = root.dtoh(&root_output)?;
8244 drop(remote_down_keepalive);
8245 output.extend(root_output);
8246 }
8247 Ok(output)
8248 }
8249}
8250
8251#[allow(clippy::manual_is_multiple_of)] fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8253 if matrix.out_features % tp != 0 {
8254 return Err(format!(
8255 "column-parallel out_features {} is not divisible by TP={tp}",
8256 matrix.out_features
8257 ));
8258 }
8259 let local_out = matrix.out_features / tp;
8260 if !local_out.is_multiple_of(FP8_BLOCK) {
8261 return Err(format!(
8262 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
8263 E4M3 scale block"
8264 ));
8265 }
8266 Ok(())
8267}
8268
8269#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
8271 if !matches!(tp, 1 | 2 | 4 | 8) {
8272 return Err(format!(
8273 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8274 ));
8275 }
8276 if out_features == 0 || !out_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8277 return Err(format!(
8278 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
8279 ));
8280 }
8281 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
8282 let local_out = out_features / tp;
8283 if local_out % canonical_rows != 0 {
8284 return Err(format!(
8285 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
8286 {canonical_rows}-row chunks"
8287 ));
8288 }
8289 Ok(canonical_rows)
8290}
8291
8292#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
8294 if !matches!(tp, 1 | 2 | 4 | 8) {
8295 return Err(format!(
8296 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8297 ));
8298 }
8299 if in_features == 0 || !in_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8300 return Err(format!(
8301 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
8302 ));
8303 }
8304 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
8305 let local_in = in_features / tp;
8306 if local_in % canonical_cols != 0 {
8307 return Err(format!(
8308 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
8309 {canonical_cols}-column chunks"
8310 ));
8311 }
8312 Ok(canonical_cols)
8313}
8314
8315#[allow(clippy::manual_is_multiple_of)] fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8317 if matrix.in_features % tp != 0 {
8318 return Err(format!(
8319 "row-parallel in_features {} is not divisible by TP={tp}",
8320 matrix.in_features
8321 ));
8322 }
8323 let local_in = matrix.in_features / tp;
8324 if !local_in.is_multiple_of(FP8_BLOCK) {
8325 return Err(format!(
8326 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
8327 E4M3 scale block"
8328 ));
8329 }
8330 Ok(())
8331}
8332
8333fn upload_rank(
8334 engine: &Engine,
8335 matrix: E4m3BlockMatrix<'_>,
8336) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
8337 let _main = engine.gpu.enter_main()?;
8338 matrix.validate()?;
8339 Ok(ResidentE4m3Rank {
8340 codes: engine.htod_bytes(matrix.codes)?,
8341 scales: engine.htod(matrix.scales)?,
8342 out_features: matrix.out_features,
8343 in_features: matrix.in_features,
8344 })
8345}
8346
8347fn upload_bf16_rank(
8348 engine: &Engine,
8349 matrix: Bf16Matrix<'_>,
8350 f32_mirror: bool,
8351) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
8352 let _main = engine.gpu.enter_main()?;
8353 matrix.validate()?;
8354 let bytes = engine.htod_bytes(matrix.bytes)?;
8355 let weight = if f32_mirror {
8356 let values = matrix
8357 .out_features
8358 .checked_mul(matrix.in_features)
8359 .ok_or("resident BF16 mirror element count overflow")?;
8360 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
8361 } else {
8362 ResidentBf16Weight::Bf16(bytes)
8363 };
8364 let q8 = if crate::step_tp_w8_on() && matrix.in_features.is_multiple_of(32) {
8368 if let ResidentBf16Weight::Bf16(bytes) = &weight {
8369 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
8376 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
8377 engine.encode_q8_0_from_bf16(
8378 bytes,
8379 &mut interleaved,
8380 matrix.in_features,
8381 matrix.out_features,
8382 )?;
8383 let mirror =
8384 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
8385 Some(mirror)
8386 } else {
8387 None
8388 }
8389 } else {
8390 None
8391 };
8392 Ok(ResidentBf16Rank {
8393 weight,
8394 out_features: matrix.out_features,
8395 in_features: matrix.in_features,
8396 q8,
8397 })
8398}
8399
8400fn upload_expert_bank_rank(
8401 engine: &Engine,
8402 bank: E4m3ExpertBank<'_>,
8403 expert_range: Range<usize>,
8404) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8405 let _main = engine.gpu.enter_main()?;
8406 bank.validate()?;
8407 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
8408 return Err(format!(
8409 "invalid EP expert range {expert_range:?} for {} experts",
8410 bank.expert_count
8411 )
8412 .into());
8413 }
8414 let code_stride = bank.out_features * bank.in_features;
8415 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
8416 Ok(ResidentE4m3ExpertBankRank {
8417 codes: engine.htod_bytes(
8418 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
8419 )?,
8420 scales: engine.htod(
8421 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
8422 )?,
8423 expert_range,
8424 out_features: bank.out_features,
8425 in_features: bank.in_features,
8426 code_stride,
8427 scale_stride,
8428 k_blocks: None,
8429 })
8430}
8431
8432#[allow(clippy::manual_is_multiple_of)] fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8434 if bank.out_features % tp != 0 {
8435 return Err(format!(
8436 "TP expert output width {} is not divisible by TP={tp}",
8437 bank.out_features
8438 ));
8439 }
8440 let local_out = bank.out_features / tp;
8441 if !local_out.is_multiple_of(FP8_BLOCK) {
8442 return Err(format!(
8443 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
8444 ));
8445 }
8446 Ok(())
8447}
8448
8449#[allow(clippy::manual_is_multiple_of)] fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8451 if bank.in_features % tp != 0 {
8452 return Err(format!(
8453 "TP expert input width {} is not divisible by TP={tp}",
8454 bank.in_features
8455 ));
8456 }
8457 let local_in = bank.in_features / tp;
8458 if !local_in.is_multiple_of(FP8_BLOCK) {
8459 return Err(format!(
8460 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
8461 ));
8462 }
8463 Ok(())
8464}
8465
8466fn upload_column_bank_rank(
8467 engine: &Engine,
8468 bank: E4m3ExpertBank<'_>,
8469 tp: usize,
8470 rank: usize,
8471) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8472 let _main = engine.gpu.enter_main()?;
8473 let packed = pack_column_bank_rank(bank, tp, rank)?;
8474 Ok(ResidentE4m3ExpertBankRank {
8475 codes: engine.htod_bytes(&packed.codes)?,
8476 scales: engine.htod(&packed.scales)?,
8477 expert_range: packed.expert_range,
8478 out_features: packed.out_features,
8479 in_features: packed.in_features,
8480 code_stride: packed.code_stride,
8481 scale_stride: packed.scale_stride,
8482 k_blocks: packed.k_blocks,
8483 })
8484}
8485
8486fn pack_column_bank_rank(
8487 bank: E4m3ExpertBank<'_>,
8488 tp: usize,
8489 rank: usize,
8490) -> Result<PackedE4m3ExpertBankRank, String> {
8491 bank.validate()?;
8492 validate_column_bank_shape(bank, tp)?;
8493 if rank >= tp {
8494 return Err(format!("TP rank {rank} outside 0..{tp}"));
8495 }
8496 let local_out = bank.out_features / tp;
8497 let full_code_stride = bank.out_features * bank.in_features;
8498 let local_code_stride = local_out * bank.in_features;
8499 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8500 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8501 let local_scale_rows = local_out / FP8_BLOCK;
8502 let local_scale_stride = local_scale_rows * scale_cols;
8503 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8504 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8505 let row_start = rank * local_out;
8506 let scale_row_start = rank * local_scale_rows;
8507 for expert in 0..bank.expert_count {
8508 let code_start = expert * full_code_stride + row_start * bank.in_features;
8509 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8510 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8511 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8512 }
8513 Ok(PackedE4m3ExpertBankRank {
8514 codes,
8515 scales,
8516 expert_range: 0..bank.expert_count,
8517 out_features: local_out,
8518 in_features: bank.in_features,
8519 code_stride: local_code_stride,
8520 scale_stride: local_scale_stride,
8521 k_blocks: None,
8522 })
8523}
8524
8525fn upload_row_bank_rank(
8526 engine: &Engine,
8527 bank: E4m3ExpertBank<'_>,
8528 tp: usize,
8529 rank: usize,
8530) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8531 let _main = engine.gpu.enter_main()?;
8532 let packed = pack_row_bank_rank(bank, tp, rank)?;
8533 Ok(ResidentE4m3ExpertBankRank {
8534 codes: engine.htod_bytes(&packed.codes)?,
8535 scales: engine.htod(&packed.scales)?,
8536 expert_range: packed.expert_range,
8537 out_features: packed.out_features,
8538 in_features: packed.in_features,
8539 code_stride: packed.code_stride,
8540 scale_stride: packed.scale_stride,
8541 k_blocks: packed.k_blocks,
8542 })
8543}
8544
8545fn pack_row_bank_rank(
8546 bank: E4m3ExpertBank<'_>,
8547 tp: usize,
8548 rank: usize,
8549) -> Result<PackedE4m3ExpertBankRank, String> {
8550 bank.validate()?;
8551 validate_row_bank_shape(bank, tp)?;
8552 if rank >= tp {
8553 return Err(format!("TP rank {rank} outside 0..{tp}"));
8554 }
8555 let local_in = bank.in_features / tp;
8556 let full_code_stride = bank.out_features * bank.in_features;
8557 let local_code_stride = bank.out_features * local_in;
8558 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8559 let local_scale_cols = local_in / FP8_BLOCK;
8560 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8561 let full_scale_stride = scale_rows * full_scale_cols;
8562 let local_scale_stride = scale_rows * local_scale_cols;
8563 let global_block_start = rank * local_scale_cols;
8564 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8565 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8566 for expert in 0..bank.expert_count {
8567 let expert_code_start = expert * full_code_stride;
8568 let expert_scale_start = expert * full_scale_stride;
8569 for local_block in 0..local_scale_cols {
8570 let global_block = global_block_start + local_block;
8571 let column_start = global_block * FP8_BLOCK;
8572 for row in 0..bank.out_features {
8573 let start = expert_code_start + row * bank.in_features + column_start;
8574 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8575 }
8576 for row in 0..scale_rows {
8577 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8578 }
8579 }
8580 }
8581 Ok(PackedE4m3ExpertBankRank {
8582 codes,
8583 scales,
8584 expert_range: 0..bank.expert_count,
8585 out_features: bank.out_features,
8586 in_features: local_in,
8587 code_stride: local_code_stride,
8588 scale_stride: local_scale_stride,
8589 k_blocks: Some(local_scale_cols),
8590 })
8591}
8592
8593fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8594 if engines.len() != ranks.len() {
8595 return Err(format!(
8596 "resident TP rank count {} != runtime rank count {}",
8597 ranks.len(),
8598 engines.len()
8599 ));
8600 }
8601 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8602 let device = engine.ctx().ordinal();
8603 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8604 return Err(format!(
8605 "resident TP rank {rank} is not owned by runtime device {device}"
8606 ));
8607 }
8608 }
8609 Ok(())
8610}
8611
8612fn validate_tp_bank_residency(
8613 engines: &[Engine],
8614 experts: &ResidentTpExpertBank,
8615) -> Result<(), String> {
8616 if engines.len() != experts.gate.len()
8617 || engines.len() != experts.up.len()
8618 || engines.len() != experts.down.len()
8619 {
8620 return Err(format!(
8621 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8622 experts.gate.len(),
8623 experts.up.len(),
8624 experts.down.len(),
8625 engines.len()
8626 ));
8627 }
8628 for (rank, engine) in engines.iter().enumerate() {
8629 let device = engine.ctx().ordinal();
8630 for (projection, bank) in [
8631 ("gate", &experts.gate[rank]),
8632 ("up", &experts.up[rank]),
8633 ("down", &experts.down[rank]),
8634 ] {
8635 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8636 return Err(format!(
8637 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8638 {device}"
8639 ));
8640 }
8641 }
8642 }
8643 Ok(())
8644}
8645
8646fn validate_ep_residency(
8647 engines: &[Engine],
8648 experts: &ResidentExpertParallel,
8649) -> Result<(), String> {
8650 if engines.len() != experts.ranks.len() {
8651 return Err(format!(
8652 "resident EP rank count {} != runtime rank count {}",
8653 experts.ranks.len(),
8654 engines.len()
8655 ));
8656 }
8657 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
8658 let device = engine.ctx().ordinal();
8659 for (projection, bank) in [
8660 ("gate", &resident.gate),
8661 ("up", &resident.up),
8662 ("down", &resident.down),
8663 ] {
8664 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8665 return Err(format!(
8666 "resident EP rank {rank} {projection} bank is not owned by runtime device \
8667 {device}"
8668 ));
8669 }
8670 }
8671 }
8672 Ok(())
8673}
8674
8675fn run_rank(
8676 engine: &Engine,
8677 matrix: E4m3BlockMatrix<'_>,
8678 activations: &[f32],
8679 tokens: usize,
8680) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8681 let _main = engine.gpu.enter_main()?;
8682 let codes = engine.htod_bytes(matrix.codes)?;
8683 let scales = engine.htod(matrix.scales)?;
8684 let activations = engine.htod(activations)?;
8685 let output = engine.qmatvec_mmq_fp8_blk(
8686 &codes,
8687 &scales,
8688 &activations,
8689 tokens,
8690 matrix.in_features,
8691 matrix.out_features,
8692 )?;
8693 engine.dtoh(&output)
8694}
8695
8696fn run_resident_rank(
8697 engine: &Engine,
8698 matrix: &ResidentE4m3Rank,
8699 activations: &[f32],
8700 tokens: usize,
8701) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8702 let _main = engine.gpu.enter_main()?;
8703 let activations = engine.htod(activations)?;
8704 let output = engine.qmatvec_mmq_fp8_blk(
8705 &matrix.codes,
8706 &matrix.scales,
8707 &activations,
8708 tokens,
8709 matrix.in_features,
8710 matrix.out_features,
8711 )?;
8712 engine.dtoh(&output)
8713}
8714
8715fn run_resident_bf16_rank(
8716 engine: &Engine,
8717 matrix: &ResidentBf16Rank,
8718 activations: &[f32],
8719 tokens: usize,
8720 canonical_chunk_rows: Option<usize>,
8721) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8722 let _main = engine.gpu.enter_main()?;
8723 let activations = engine.htod(activations)?;
8724 let output = run_resident_bf16_rank_device(
8725 engine,
8726 matrix,
8727 &activations,
8728 tokens,
8729 canonical_chunk_rows,
8730 false,
8731 )?;
8732 engine.dtoh(&output)
8733}
8734
8735fn run_resident_bf16_rank_device(
8736 engine: &Engine,
8737 matrix: &ResidentBf16Rank,
8738 activations: &CudaSlice<f32>,
8739 tokens: usize,
8740 canonical_chunk_rows: Option<usize>,
8741 strided_chunk_output: bool,
8742) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8743 let _main = engine.gpu.enter_main()?;
8744 if activations.ordinal() != engine.ctx().ordinal() {
8745 return Err(format!(
8746 "resident BF16 activation device {} != rank device {}",
8747 activations.ordinal(),
8748 engine.ctx().ordinal()
8749 )
8750 .into());
8751 }
8752 if activations.len() != tokens * matrix.in_features {
8753 return Err(format!(
8754 "resident BF16 activation count {} != {tokens}x{}",
8755 activations.len(),
8756 matrix.in_features
8757 )
8758 .into());
8759 }
8760 match (&matrix.weight, canonical_chunk_rows) {
8761 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8762 .linear_bf16_resident_canonical_rows(
8763 activations,
8764 bytes,
8765 tokens,
8766 matrix.in_features,
8767 matrix.out_features,
8768 rows,
8769 ),
8770 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8771 activations,
8772 bytes,
8773 tokens,
8774 matrix.in_features,
8775 matrix.out_features,
8776 ),
8777 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8778 .linear_f32_resident_canonical_rows_strided(
8779 activations,
8780 values,
8781 tokens,
8782 matrix.in_features,
8783 matrix.out_features,
8784 rows,
8785 ),
8786 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8787 activations,
8788 values,
8789 tokens,
8790 matrix.in_features,
8791 matrix.out_features,
8792 rows,
8793 ),
8794 (ResidentBf16Weight::F32(values), None) => engine.linear(
8795 activations,
8796 values,
8797 tokens,
8798 matrix.in_features,
8799 matrix.out_features,
8800 ),
8801 }
8802}
8803
8804fn validate_resident_bf16_ranks(
8805 engines: &[Engine],
8806 ranks: &[ResidentBf16Rank],
8807) -> Result<(), String> {
8808 if engines.len() != ranks.len() {
8809 return Err(format!(
8810 "resident BF16 TP rank count {} != runtime rank count {}",
8811 ranks.len(),
8812 engines.len(),
8813 ));
8814 }
8815 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8816 let device = engine.ctx().ordinal();
8817 if matrix.weight.ordinal() != device {
8818 return Err(format!(
8819 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8820 ));
8821 }
8822 }
8823 Ok(())
8824}
8825
8826fn validate_step_bf16_row_residency(
8827 engines: &[Engine],
8828 matrix: &ResidentStepBf16RowParallel,
8829) -> Result<(), String> {
8830 if engines.len() != matrix.ranks.len() {
8831 return Err(format!(
8832 "resident Step BF16 row rank count {} != runtime rank count {}",
8833 matrix.ranks.len(),
8834 engines.len(),
8835 ));
8836 }
8837 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8838 if matrix.canonical_chunk_cols != canonical_cols {
8839 return Err(format!(
8840 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8841 matrix.canonical_chunk_cols
8842 ));
8843 }
8844 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8845 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8846 if blocks.len() != blocks_per_rank {
8847 return Err(format!(
8848 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8849 blocks.len()
8850 ));
8851 }
8852 let device = engine.ctx().ordinal();
8853 for (block, resident) in blocks.iter().enumerate() {
8854 if resident.weight.ordinal() != device
8855 || resident.in_features != canonical_cols
8856 || resident.out_features != matrix.out_features
8857 {
8858 return Err(format!(
8859 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8860 device or geometry"
8861 ));
8862 }
8863 }
8864 }
8865 Ok(())
8866}
8867
8868fn validate_replicated_device_rows(
8869 engines: &[Engine],
8870 rows: &ResidentReplicatedDeviceRows,
8871) -> Result<(), String> {
8872 let rank_lengths = rows
8873 .ranks
8874 .iter()
8875 .map(|rank_rows| rank_rows.len())
8876 .collect::<Vec<_>>();
8877 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8878 if rows
8879 .ranks
8880 .iter()
8881 .zip(engines)
8882 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8883 {
8884 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8885 }
8886 Ok(())
8887}
8888
8889fn replicated_device_row_values(
8890 tokens: usize,
8891 width: usize,
8892 expected_ranks: usize,
8893 rank_lengths: &[usize],
8894) -> Result<usize, String> {
8895 let values = tokens
8896 .checked_mul(width)
8897 .ok_or("replicated device row size overflow")?;
8898 if tokens == 0
8899 || width == 0
8900 || expected_ranks == 0
8901 || rank_lengths.len() != expected_ranks
8902 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8903 {
8904 return Err(format!(
8905 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8906 tokens,
8907 width,
8908 rank_lengths.len(),
8909 expected_ranks
8910 ));
8911 }
8912 Ok(values)
8913}
8914
8915fn replicated_device_row_source_values(
8916 tokens: usize,
8917 width: usize,
8918 source_len: usize,
8919 source_device: usize,
8920 root_device: usize,
8921) -> Result<usize, String> {
8922 let values = tokens
8923 .checked_mul(width)
8924 .ok_or("replicated device row size overflow")?;
8925 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8926 return Err(format!(
8927 "replicated device row source has inconsistent geometry/device \
8928 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8929 ));
8930 }
8931 Ok(values)
8932}
8933
8934#[allow(clippy::manual_is_multiple_of)] fn bf16_column_shard(
8936 matrix: Bf16Matrix<'_>,
8937 tp: usize,
8938 rank: usize,
8939) -> Result<Bf16Matrix<'_>, String> {
8940 matrix.validate()?;
8941 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8942 return Err(format!(
8943 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8944 matrix.out_features
8945 ));
8946 }
8947 let local_out = matrix.out_features / tp;
8948 let row_bytes = matrix.in_features * 2;
8949 let start = rank * local_out * row_bytes;
8950 Ok(Bf16Matrix {
8951 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8952 out_features: local_out,
8953 in_features: matrix.in_features,
8954 })
8955}
8956
8957#[allow(clippy::manual_is_multiple_of)] fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8959 matrix.validate()?;
8960 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8961 return Err(format!(
8962 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8963 matrix.in_features
8964 ));
8965 }
8966 let local_in = matrix.in_features / tp;
8967 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8968 for row in 0..matrix.out_features {
8969 let start = (row * matrix.in_features + rank * local_in) * 2;
8970 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8971 }
8972 Ok(bytes)
8973}
8974
8975fn bf16_row_block(
8976 matrix: Bf16Matrix<'_>,
8977 col_start: usize,
8978 block_cols: usize,
8979) -> Result<Vec<u8>, String> {
8980 matrix.validate()?;
8981 let col_end = col_start
8982 .checked_add(block_cols)
8983 .ok_or("BF16 row block column overflow")?;
8984 if block_cols == 0 || col_end > matrix.in_features {
8985 return Err(format!(
8986 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8987 matrix.in_features
8988 ));
8989 }
8990 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8991 for row in 0..matrix.out_features {
8992 let start = (row * matrix.in_features + col_start) * 2;
8993 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8994 }
8995 Ok(bytes)
8996}
8997
8998fn run_resident_bank_expert(
8999 engine: &Engine,
9000 bank: &ResidentE4m3ExpertBankRank,
9001 local_expert: usize,
9002 activations: &[f32],
9003 tokens: usize,
9004) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9005 let _main = engine.gpu.enter_main()?;
9006 if bank.k_blocks.is_some() {
9007 return Err("block-major TP row bank requires canonical block execution".into());
9008 }
9009 let local_count = bank.expert_range.end - bank.expert_range.start;
9010 if local_expert >= local_count {
9011 return Err(format!(
9012 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
9013 bank.expert_range
9014 )
9015 .into());
9016 }
9017 validate_activations(activations, tokens, bank.in_features)?;
9018 let activations = engine.htod(activations)?;
9019 let weight = bank
9020 .codes
9021 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9022 let scales = bank
9023 .scales
9024 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9025 let input = activations.slice(0..activations.len());
9026 let output = engine.qmatvec_mmq_fp8_blk_view(
9027 &weight,
9028 &scales,
9029 &input,
9030 tokens,
9031 bank.in_features,
9032 bank.out_features,
9033 )?;
9034 engine.dtoh(&output)
9035}
9036
9037fn run_resident_bank_expert_block(
9038 engine: &Engine,
9039 bank: &ResidentE4m3ExpertBankRank,
9040 local_expert: usize,
9041 block: usize,
9042 activations: &[f32],
9043) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9044 let _main = engine.gpu.enter_main()?;
9045 let local_count = bank.expert_range.end - bank.expert_range.start;
9046 if local_expert >= local_count {
9047 return Err(format!(
9048 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9049 bank.expert_range
9050 )
9051 .into());
9052 }
9053 let blocks = bank
9054 .k_blocks
9055 .ok_or("TP row bank is not packed in native K-block order")?;
9056 if block >= blocks {
9057 return Err(format!("TP row block {block} outside 0..{blocks}").into());
9058 }
9059 validate_activations(activations, 1, FP8_BLOCK)?;
9060 let block_code_stride = bank.out_features * FP8_BLOCK;
9061 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9062 if bank.in_features != blocks * FP8_BLOCK
9063 || bank.code_stride != blocks * block_code_stride
9064 || bank.scale_stride != blocks * block_scale_stride
9065 {
9066 return Err("TP row bank block-major geometry is inconsistent".into());
9067 }
9068
9069 let expert_code_start = local_expert * bank.code_stride;
9070 let expert_scale_start = local_expert * bank.scale_stride;
9071 let weight = bank.codes.slice(
9072 expert_code_start + block * block_code_stride
9073 ..expert_code_start + (block + 1) * block_code_stride,
9074 );
9075 let scales = bank.scales.slice(
9076 expert_scale_start + block * block_scale_stride
9077 ..expert_scale_start + (block + 1) * block_scale_stride,
9078 );
9079 let activations = engine.htod(activations)?;
9080 let input = activations.slice(0..activations.len());
9081 let output = engine.qmatvec_mmq_fp8_blk_view(
9082 &weight,
9083 &scales,
9084 &input,
9085 1,
9086 FP8_BLOCK,
9087 bank.out_features,
9088 )?;
9089 engine.dtoh(&output)
9090}
9091
9092fn run_resident_bank_expert_device(
9093 engine: &Engine,
9094 bank: &ResidentE4m3ExpertBankRank,
9095 local_expert: usize,
9096 activations: &CudaSlice<f32>,
9097 tokens: usize,
9098) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9099 let _main = engine.gpu.enter_main()?;
9100 if bank.k_blocks.is_some() {
9101 return Err("block-major TP row bank requires canonical block execution".into());
9102 }
9103 let local_count = bank.expert_range.end - bank.expert_range.start;
9104 if local_expert >= local_count {
9105 return Err(format!(
9106 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9107 bank.expert_range
9108 )
9109 .into());
9110 }
9111 let expected = tokens
9112 .checked_mul(bank.in_features)
9113 .ok_or("native TP activation size overflow")?;
9114 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
9115 return Err(format!(
9116 "native TP activation len/device {}/{} != expected {expected}/{}",
9117 activations.len(),
9118 activations.ordinal(),
9119 engine.ctx().ordinal()
9120 )
9121 .into());
9122 }
9123 let weight = bank
9124 .codes
9125 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9126 let scales = bank
9127 .scales
9128 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9129 let input = activations.slice(0..activations.len());
9130 engine.qmatvec_mmq_fp8_blk_view(
9131 &weight,
9132 &scales,
9133 &input,
9134 tokens,
9135 bank.in_features,
9136 bank.out_features,
9137 )
9138}
9139
9140fn run_resident_bank_expert_block_device(
9141 engine: &Engine,
9142 bank: &ResidentE4m3ExpertBankRank,
9143 local_expert: usize,
9144 block: usize,
9145 activations: &cudarc::driver::CudaView<'_, f32>,
9146) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9147 let _main = engine.gpu.enter_main()?;
9148 let local_count = bank.expert_range.end - bank.expert_range.start;
9149 if local_expert >= local_count {
9150 return Err(format!(
9151 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9152 bank.expert_range
9153 )
9154 .into());
9155 }
9156 let blocks = bank
9157 .k_blocks
9158 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
9159 if block >= blocks {
9160 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
9161 }
9162 let activation_device = activations.stream().context().ordinal();
9163 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
9164 return Err(format!(
9165 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
9166 activations.len(),
9167 activation_device,
9168 engine.ctx().ordinal()
9169 )
9170 .into());
9171 }
9172 let block_code_stride = bank.out_features * FP8_BLOCK;
9173 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9174 if bank.in_features != blocks * FP8_BLOCK
9175 || bank.code_stride != blocks * block_code_stride
9176 || bank.scale_stride != blocks * block_scale_stride
9177 {
9178 return Err("native TP row bank block-major geometry is inconsistent".into());
9179 }
9180 let expert_code_start = local_expert * bank.code_stride;
9181 let expert_scale_start = local_expert * bank.scale_stride;
9182 let weight = bank.codes.slice(
9183 expert_code_start + block * block_code_stride
9184 ..expert_code_start + (block + 1) * block_code_stride,
9185 );
9186 let scales = bank.scales.slice(
9187 expert_scale_start + block * block_scale_stride
9188 ..expert_scale_start + (block + 1) * block_scale_stride,
9189 );
9190 engine.qmatvec_mmq_fp8_blk_view(
9191 &weight,
9192 &scales,
9193 activations,
9194 1,
9195 FP8_BLOCK,
9196 bank.out_features,
9197 )
9198}
9199
9200pub(crate) fn grant_peer_access(
9219 accessor: &Engine,
9220 owner: &Engine,
9221 label: &str,
9222) -> Result<(), Box<dyn std::error::Error>> {
9223 let (a_dev, o_dev) = (accessor.ctx().ordinal(), owner.ctx().ordinal());
9224 let mut can_access = 0;
9225 unsafe {
9226 cudarc::driver::sys::cuDeviceCanAccessPeer(
9227 &mut can_access,
9228 accessor.ctx().cu_device(),
9229 owner.ctx().cu_device(),
9230 )
9231 .result()?;
9232 }
9233 if can_access == 0 {
9234 return Err(
9235 format!("{label} requires P2P, but dev{a_dev} cannot access dev{o_dev}").into(),
9236 );
9237 }
9238 accessor.ctx().bind_to_thread()?;
9239 let rc = unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(owner.ctx().cu_ctx(), 0) };
9240 use cudarc::driver::sys::cudaError_enum as E;
9241 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
9242 return Err(format!(
9243 "{label} cuCtxEnablePeerAccess(dev{a_dev} -> dev{o_dev}) failed: {rc:?}"
9244 )
9245 .into());
9246 }
9247 let device = cudarc::driver::result::device::get(o_dev as i32)?;
9248 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
9249 unsafe {
9250 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
9251 }
9252 let desc = cudarc::driver::sys::CUmemAccessDesc {
9253 location: cudarc::driver::sys::CUmemLocation {
9254 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
9255 id: a_dev as i32,
9256 },
9257 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
9258 };
9259 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
9260 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
9261 return Err(format!(
9262 "{label} cuMemPoolSetAccess(dev{o_dev} pool -> dev{a_dev}) failed: {rc:?}"
9263 )
9264 .into());
9265 }
9266 Ok(())
9267}
9268
9269fn configure_native_p2p(
9270 ranks: &[Engine],
9271 devices: &[usize],
9272) -> Result<(), Box<dyn std::error::Error>> {
9273 if ranks.len() != devices.len() || ranks.len() < 2 {
9274 return Err("native TP P2P setup requires matching multi-rank devices".into());
9275 }
9276 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
9277 if engine.ctx().ordinal() != device {
9278 return Err(format!(
9279 "native TP rank {rank} context device {} != requested device {device}",
9280 engine.ctx().ordinal()
9281 )
9282 .into());
9283 }
9284 }
9285
9286 for src in 0..ranks.len() {
9287 for dst in 0..ranks.len() {
9288 if src == dst {
9289 continue;
9290 }
9291 grant_peer_access(&ranks[src], &ranks[dst], "native TP")?;
9292 }
9293 }
9294
9295 for src in 0..ranks.len() {
9296 for dst in 0..ranks.len() {
9297 if src == dst {
9298 continue;
9299 }
9300 for &words in NATIVE_P2P_PROBE_WORDS {
9301 let expected = (0..words)
9302 .map(|index| {
9303 (index as u32)
9304 .wrapping_mul(0x9e37_79b9)
9305 .wrapping_add(((src as u32) << 16) | dst as u32)
9306 })
9307 .collect::<Vec<_>>();
9308 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
9309 let source = ranks[src].htod_u32_v(&expected)?;
9310 let mut destination = ranks[dst].htod_u32_v(&poison)?;
9311 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
9312 let actual = ranks[dst].dtoh_u32(&destination)?;
9313 if actual != expected {
9314 let mismatches = actual
9315 .iter()
9316 .zip(&expected)
9317 .filter(|(actual, expected)| actual != expected)
9318 .count();
9319 return Err(format!(
9320 "native TP peer probe dev{}->dev{} failed at {} bytes: \
9321 {mismatches}/{} words differ",
9322 devices[src],
9323 devices[dst],
9324 words * std::mem::size_of::<u32>(),
9325 expected.len()
9326 )
9327 .into());
9328 }
9329 }
9330 }
9331 }
9332 ranks[0].ctx().bind_to_thread()?;
9333 eprintln!(
9334 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
9335 directions={} byte_ladder={:?} mismatches=0",
9336 ranks.len() * (ranks.len() - 1),
9337 NATIVE_P2P_PROBE_WORDS
9338 .iter()
9339 .map(|words| words * std::mem::size_of::<u32>())
9340 .collect::<Vec<_>>(),
9341 );
9342 Ok(())
9343}
9344
9345fn validate_activations(
9346 activations: &[f32],
9347 tokens: usize,
9348 in_features: usize,
9349) -> Result<(), String> {
9350 let expected = tokens
9351 .checked_mul(in_features)
9352 .ok_or_else(|| "activation size overflow".to_string())?;
9353 if activations.len() != expected {
9354 return Err(format!(
9355 "activation count {} != {tokens}x{in_features} ({expected})",
9356 activations.len()
9357 ));
9358 }
9359 if !activations.iter().all(|value| value.is_finite()) {
9360 return Err("activations contain a non-finite value".to_string());
9361 }
9362 Ok(())
9363}
9364
9365fn column_shard(
9366 matrix: E4m3BlockMatrix<'_>,
9367 tp: usize,
9368 rank: usize,
9369) -> Result<E4m3BlockMatrix<'_>, String> {
9370 let local_out = matrix.out_features / tp;
9371 let row_start = rank * local_out;
9372 let code_start = row_start * matrix.in_features;
9373 let code_end = code_start + local_out * matrix.in_features;
9374 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9375 let local_scale_rows = local_out / FP8_BLOCK;
9376 let scale_start = rank * local_scale_rows * scale_cols;
9377 let scale_end = scale_start + local_scale_rows * scale_cols;
9378 Ok(E4m3BlockMatrix {
9379 codes: &matrix.codes[code_start..code_end],
9380 scales: &matrix.scales[scale_start..scale_end],
9381 out_features: local_out,
9382 in_features: matrix.in_features,
9383 })
9384}
9385
9386fn row_shard(
9387 matrix: E4m3BlockMatrix<'_>,
9388 tp: usize,
9389 rank: usize,
9390) -> Result<(Vec<u8>, Vec<f32>), String> {
9391 let local_in = matrix.in_features / tp;
9392 let col_start = rank * local_in;
9393 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
9394 for row in 0..matrix.out_features {
9395 let start = row * matrix.in_features + col_start;
9396 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
9397 }
9398
9399 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
9400 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9401 let local_scale_cols = local_in / FP8_BLOCK;
9402 let scale_col_start = rank * local_scale_cols;
9403 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
9404 for row in 0..scale_rows {
9405 let start = row * scale_cols + scale_col_start;
9406 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
9407 }
9408 Ok((codes, scales))
9409}
9410
9411fn activation_shard(
9412 activations: &[f32],
9413 tokens: usize,
9414 in_features: usize,
9415 tp: usize,
9416 rank: usize,
9417) -> Vec<f32> {
9418 let local_in = in_features / tp;
9419 let col_start = rank * local_in;
9420 let mut shard = Vec::with_capacity(tokens * local_in);
9421 for token in 0..tokens {
9422 let start = token * in_features + col_start;
9423 shard.extend_from_slice(&activations[start..start + local_in]);
9424 }
9425 shard
9426}
9427
9428#[derive(Clone, Copy)]
9448pub struct Nvfp4BlockMatrix<'a> {
9449 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
9453 pub in_features: usize,
9454}
9455
9456impl Nvfp4BlockMatrix<'_> {
9457 pub fn validate(&self) -> Result<(), String> {
9458 if self.in_features == 0 || self.out_features == 0 {
9459 return Err("NVFP4 matrix has a zero dimension".to_string());
9460 }
9461 if !self.in_features.is_multiple_of(64) {
9462 return Err(format!(
9463 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
9464 self.in_features
9465 ));
9466 }
9467 if self.codes.len() != self.out_features * self.in_features / 2 {
9468 return Err(format!(
9469 "NVFP4 code bytes {} != {}x{}/2",
9470 self.codes.len(),
9471 self.out_features,
9472 self.in_features
9473 ));
9474 }
9475 if self.scales.len() != self.out_features * self.in_features / 16 {
9476 return Err(format!(
9477 "NVFP4 scale bytes {} != {}x{}/16",
9478 self.scales.len(),
9479 self.out_features,
9480 self.in_features
9481 ));
9482 }
9483 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9484 return Err(format!(
9485 "NVFP4 macro scale {} is not finite-positive",
9486 self.macro_scale
9487 ));
9488 }
9489 Ok(())
9490 }
9491}
9492
9493#[derive(Clone, Copy)]
9495pub struct Nvfp4ExpertBank<'a> {
9496 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9500 pub out_features: usize,
9501 pub in_features: usize,
9502}
9503
9504impl Nvfp4ExpertBank<'_> {
9505 pub fn validate(&self) -> Result<(), String> {
9506 if self.expert_count == 0 {
9507 return Err("NVFP4 expert bank is empty".to_string());
9508 }
9509 if self.macros.len() != self.expert_count {
9510 return Err(format!(
9511 "NVFP4 bank macros {} != expert count {}",
9512 self.macros.len(),
9513 self.expert_count
9514 ));
9515 }
9516 self.expert(0).map(|_| ())
9517 }
9518
9519 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9520 if expert >= self.expert_count {
9521 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9522 }
9523 let code_stride = self.out_features * self.in_features / 2;
9524 let scale_stride = self.out_features * self.in_features / 16;
9525 if self.codes.len() != self.expert_count * code_stride
9526 || self.scales.len() != self.expert_count * scale_stride
9527 {
9528 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9529 }
9530 let matrix = Nvfp4BlockMatrix {
9531 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9532 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9533 macro_scale: self.macros[expert],
9534 out_features: self.out_features,
9535 in_features: self.in_features,
9536 };
9537 matrix.validate()?;
9538 Ok(matrix)
9539 }
9540}
9541
9542pub struct ResidentNvfp4Rank {
9544 blocks: crate::CudaSlice<u8>,
9545 macro_scale: f32,
9546 out_features: usize,
9547 in_features: usize,
9548 row_bytes: usize,
9549}
9550
9551pub struct ResidentNvfp4ColumnParallel {
9552 ranks: Vec<ResidentNvfp4Rank>,
9553 pub out_features: usize,
9554 pub in_features: usize,
9555}
9556
9557pub struct ResidentNvfp4RowParallel {
9558 ranks: Vec<ResidentNvfp4Rank>,
9559 pub out_features: usize,
9560 pub in_features: usize,
9561}
9562
9563pub struct ResidentTpNvfp4Expert {
9564 gate: ResidentNvfp4ColumnParallel,
9565 up: ResidentNvfp4ColumnParallel,
9566 down: ResidentNvfp4RowParallel,
9567 pub input_width: usize,
9568 pub expert_width: usize,
9569}
9570
9571pub struct ResidentNvfp4ColumnBankRank {
9575 bank: crate::CudaSlice<u8>,
9579 expert_bytes: usize,
9580 local_out: usize,
9581 in_features: usize,
9582 row_bytes: usize,
9583 slot_major: bool,
9590}
9591
9592impl ResidentNvfp4ColumnBankRank {
9593 fn host_canonical_expert(
9598 &self,
9599 engine: &Engine,
9600 expert: usize,
9601 activations: &crate::CudaSlice<f32>,
9602 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9603 let w = self.expert(expert);
9604 if self.slot_major {
9605 engine.qmatvec_nvfp4_fast_v2(
9606 &w,
9607 activations,
9608 1,
9609 self.in_features,
9610 self.local_out,
9611 self.row_bytes,
9612 )
9613 } else {
9614 engine.qmatvec_nvfp4_fast(
9615 &w,
9616 activations,
9617 1,
9618 self.in_features,
9619 self.local_out,
9620 self.row_bytes,
9621 )
9622 }
9623 }
9624
9625 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9626 self.bank
9627 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9628 }
9629}
9630
9631pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9637
9638pub struct ResidentNvfp4RowBankRank {
9639 bank: crate::CudaSlice<u8>,
9641 expert_bytes: usize,
9642 device_rank: usize, out_features: usize,
9644 local_in: usize,
9645 row_bytes: usize,
9646 slot_major: bool,
9648}
9649
9650impl ResidentNvfp4RowBankRank {
9651 fn host_canonical_expert(
9654 &self,
9655 engine: &Engine,
9656 expert: usize,
9657 activations: &crate::CudaSlice<f32>,
9658 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9659 let w = self.expert(expert);
9660 if self.slot_major {
9661 engine.qmatvec_nvfp4_fast_v2(
9662 &w,
9663 activations,
9664 1,
9665 self.local_in,
9666 self.out_features,
9667 self.row_bytes,
9668 )
9669 } else {
9670 engine.qmatvec_nvfp4_fast(
9671 &w,
9672 activations,
9673 1,
9674 self.local_in,
9675 self.out_features,
9676 self.row_bytes,
9677 )
9678 }
9679 }
9680
9681 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9682 self.bank
9683 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9684 }
9685}
9686
9687impl ResidentNvfp4TensorParallel {
9688 pub(crate) fn device_workspace_handle(
9689 &self,
9690 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
9691 &self.device_workspace
9692 }
9693}
9694
9695pub struct ResidentNvfp4TensorParallel {
9696 gate: Vec<ResidentNvfp4ColumnBankRank>,
9697 up: Vec<ResidentNvfp4ColumnBankRank>,
9698 down: Vec<ResidentNvfp4RowBankRank>,
9699 macros_gate: Vec<f32>,
9700 macros_up: Vec<f32>,
9701 macros_down: Vec<f32>,
9702 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
9706 macros_up_dev: Vec<crate::CudaSlice<f32>>,
9707 macros_down_dev: Vec<crate::CudaSlice<f32>>,
9708 pub expert_count: usize,
9709 pub input_width: usize,
9710 pub expert_width: usize,
9711 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
9714 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
9718 pub(crate) ep2: bool,
9722}
9723
9724struct RoutesGraph {
9731 exec: cudarc::driver::sys::CUgraphExec,
9732 parent: cudarc::driver::sys::CUgraph,
9733 _children: Vec<cudarc::driver::CudaGraph>,
9734}
9735unsafe impl Send for RoutesGraph {}
9738
9739impl Drop for RoutesGraph {
9740 fn drop(&mut self) {
9741 unsafe {
9742 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
9743 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
9744 }
9745 }
9746}
9747
9748impl Nvfp4DeviceRoutesWorkspace {
9749 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
9750 self.in_stage_e.as_ref()
9751 }
9752 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9753 self.in_stage_e.as_mut()
9754 }
9755 #[allow(dead_code)] pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9757 self.out_stage_e.as_mut()
9758 }
9759 pub(crate) fn arm_stages(
9761 &mut self,
9762 e: &Engine,
9763 width: usize,
9764 n_sel: usize,
9765 ) -> Result<(), Box<dyn std::error::Error>> {
9766 let _main = e.gpu.enter_main()?;
9767 if self.in_stage_e.is_none() {
9768 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9769 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9770 }
9771 if self.dev_route_e.is_none() {
9772 self.dev_route_e = Some((
9773 e.htod_i32(&vec![0i32; n_sel])?,
9774 e.htod(&vec![0.0f32; n_sel])?,
9775 ));
9776 }
9777 Ok(())
9778 }
9779
9780 pub(crate) fn in_and_out_stages_mut(
9782 &mut self,
9783 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9784 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9785 (Some(input), Some(output)) => Some((input, output)),
9786 _ => None,
9787 }
9788 }
9789 pub(crate) fn dev_route_e_mut(
9790 &mut self,
9791 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9792 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9793 }
9794}
9795
9796pub struct Nvfp4DeviceRoutesWorkspace {
9797 gate_out: Vec<crate::CudaSlice<f32>>,
9800 up_out: Vec<crate::CudaSlice<f32>>,
9801 act_q: Vec<crate::CudaSlice<i8>>,
9802 act_d: Vec<crate::CudaSlice<f32>>,
9803 sel: Vec<crate::CudaSlice<i32>>,
9804 partial: Vec<crate::CudaSlice<f32>>,
9805 accumulator: Vec<crate::CudaSlice<f32>>,
9806 combine_w: Vec<crate::CudaSlice<f32>>,
9808 route_w: Vec<crate::CudaSlice<f32>>,
9811 in_q: Vec<crate::CudaSlice<i8>>,
9814 in_d: Vec<crate::CudaSlice<f32>>,
9815 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9819 prestaged: bool,
9822 rank1_routed: bool,
9825 fence_flags_raw: u64,
9829 fence_ticket: u32,
9830 ev_input: Option<(CudaEvent, usize)>,
9832 in_stage_e: Option<crate::CudaSlice<f32>>,
9835 out_stage_e: Option<crate::CudaSlice<f32>>,
9836 routes_graph: Option<RoutesGraph>,
9837 raw_dev_route_e: Option<(u64, u64)>,
9839 raw_combine: Option<(u64, u64, u64, u64)>,
9840 raw_input: Vec<u64>,
9841 raw_sel: Vec<u64>,
9842 raw_route_w: Vec<u64>,
9843 remote: crate::CudaSlice<f32>,
9844 combined: crate::CudaSlice<f32>,
9845 n_sel: usize,
9846 input: Vec<crate::CudaSlice<f32>>,
9850 ev_rank: Vec<CudaEvent>,
9851 ev_done: Option<CudaEvent>,
9852 ev_entry: Option<(CudaEvent, usize)>,
9853}
9854
9855struct ResidentNvfp4EpRank {
9857 gate: crate::CudaSlice<u8>,
9858 up: crate::CudaSlice<u8>,
9859 down: crate::CudaSlice<u8>,
9860 gate_expert_bytes: usize,
9861 down_expert_bytes: usize,
9862 macros_gate: crate::CudaSlice<f32>,
9863 macros_up: crate::CudaSlice<f32>,
9864 macros_down: crate::CudaSlice<f32>,
9865 expert_range: Range<usize>,
9866}
9867
9868struct Nvfp4EpDeviceWorkspace {
9869 input: Vec<crate::CudaSlice<f32>>,
9870 input_bf16: Vec<crate::CudaSlice<u8>>,
9871 input_q8: Vec<crate::CudaSlice<i8>>,
9872 input_q8_scales: Vec<crate::CudaSlice<f32>>,
9873 sel: Vec<crate::CudaSlice<i32>>,
9874 token_rows: Vec<crate::CudaSlice<i32>>,
9875 global_pairs: Vec<crate::CudaSlice<i32>>,
9876 route_w: Vec<crate::CudaSlice<f32>>,
9877 gate_out: Vec<crate::CudaSlice<f32>>,
9878 up_out: Vec<crate::CudaSlice<f32>>,
9879 activation_bf16: Vec<crate::CudaSlice<u8>>,
9880 activation_q8: Vec<crate::CudaSlice<i8>>,
9881 activation_q8_scales: Vec<crate::CudaSlice<f32>>,
9882 slot_rows: crate::CudaSlice<f32>,
9883 slot_rows_raw: u64,
9884 route_weights: crate::CudaSlice<f32>,
9885 graph_input: crate::CudaSlice<f32>,
9886 graph_output: crate::CudaSlice<f32>,
9887 graph_routes: Option<(u64, u64)>,
9888 graphs: Vec<Option<RoutesGraph>>,
9889 ev_entry: CudaEvent,
9890 ev_entry_device: usize,
9891 ev_rank: Vec<CudaEvent>,
9892 phase_events: Option<Nvfp4EpPhaseEvents>,
9893 capacity_tokens: usize,
9894 experts_per_token: usize,
9895}
9896
9897struct Nvfp4EpPhaseEvents {
9898 head: Vec<CudaEvent>,
9899 copy_done: Vec<CudaEvent>,
9900 gate_up_done: Vec<CudaEvent>,
9901 activation_done: Vec<CudaEvent>,
9902 down_done: Vec<CudaEvent>,
9903}
9904
9905pub(crate) const NVFP4_EP_DEVICE_BATCH_CAP: usize = 128;
9906pub(crate) const NVFP4_EP_DEVICE_ROUTER_BATCH_CAP: usize = 32;
9907pub(crate) const NVFP4_EP_Q8_BATCH_CAP: usize = 32;
9908const NVFP4_EP_GRAPH_BATCH_CAP: usize = 1;
9909
9910fn nvfp4_ep_active_input_values(
9911 input_values: usize,
9912 tokens: usize,
9913 input_width: usize,
9914) -> Result<usize, String> {
9915 if !(1..=NVFP4_EP_DEVICE_BATCH_CAP).contains(&tokens) {
9916 return Err(format!(
9917 "W4A16 NVFP4 device EP batch {tokens} is outside 1..={NVFP4_EP_DEVICE_BATCH_CAP}"
9918 ));
9919 }
9920 let active_values = tokens
9921 .checked_mul(input_width)
9922 .ok_or("W4A16 NVFP4 device EP active input size overflows usize")?;
9923 if input_values < active_values {
9924 return Err(format!(
9925 "W4A16 NVFP4 device EP input {input_values} is smaller than active \
9926 tokens {tokens} x width {input_width} ({active_values})"
9927 ));
9928 }
9929 Ok(active_values)
9930}
9931
9932pub struct ResidentNvfp4ExpertParallel {
9933 ranks: Vec<ResidentNvfp4EpRank>,
9934 macros_gate: Vec<f32>,
9935 macros_up: Vec<f32>,
9936 macros_down: Vec<f32>,
9937 pub expert_count: usize,
9938 pub input_width: usize,
9939 pub expert_width: usize,
9940 gate_row_bytes: usize,
9941 down_row_bytes: usize,
9942 device_workspace: std::sync::Mutex<Option<Nvfp4EpDeviceWorkspace>>,
9943}
9944
9945fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9946 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9947 matrix.codes,
9948 matrix.scales,
9949 matrix.out_features,
9950 matrix.in_features,
9951 )
9952}
9953
9954fn nvfp4_row_bytes(in_features: usize) -> usize {
9955 in_features / 64 * 36 }
9957
9958pub(crate) fn fuse_rope_append_on() -> bool {
9964 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9965 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9966}
9967
9968pub(crate) fn no_local_shadow_on() -> bool {
9969 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9970 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9971}
9972
9973pub fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9989 assert_eq!(
9995 in_features % 64,
9996 0,
9997 "v2 permute needs whole 64-element superblocks, got in_features={in_features}"
9998 );
9999 let row_bytes = nvfp4_row_bytes(in_features);
10000 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
10001 let n_slots = in_features / 32;
10002 let mut out = Vec::with_capacity(v1.len());
10003 for row in 0..out_features {
10004 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
10005 for g in 0..n_slots {
10006 let (sblk, h) = (g / 2, g % 2);
10007 let b = &r[sblk * 36..sblk * 36 + 36];
10008 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
10009 }
10010 for g in 0..n_slots {
10011 let (sblk, h) = (g / 2, g % 2);
10012 let b = &r[sblk * 36..sblk * 36 + 36];
10013 out.push(b[2 * h]);
10014 out.push(b[2 * h + 1]);
10015 }
10016 }
10017 out
10018}
10019
10020fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
10024 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
10025 let v1 = nvfp4_repack_matrix(matrix);
10026 if slot_major {
10027 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
10028 } else {
10029 v1
10030 }
10031}
10032
10033#[allow(clippy::manual_is_multiple_of)] fn nvfp4_column_shard<'a>(
10037 matrix: Nvfp4BlockMatrix<'a>,
10038 tp: usize,
10039 rank: usize,
10040) -> Result<Nvfp4BlockMatrix<'a>, String> {
10041 if matrix.out_features % tp != 0 {
10042 return Err(format!(
10043 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
10044 matrix.out_features
10045 ));
10046 }
10047 let local_out = matrix.out_features / tp;
10048 let code_row = matrix.in_features / 2;
10049 let scale_row = matrix.in_features / 16;
10050 Ok(Nvfp4BlockMatrix {
10051 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
10052 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
10053 macro_scale: matrix.macro_scale,
10054 out_features: local_out,
10055 in_features: matrix.in_features,
10056 })
10057}
10058
10059#[allow(clippy::manual_is_multiple_of)] fn nvfp4_row_shard(
10063 matrix: Nvfp4BlockMatrix<'_>,
10064 tp: usize,
10065 rank: usize,
10066) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
10067 if matrix.in_features % tp != 0 {
10068 return Err(format!(
10069 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
10070 matrix.in_features
10071 ));
10072 }
10073 let local_in = matrix.in_features / tp;
10074 if !local_in.is_multiple_of(64) {
10075 return Err(format!(
10076 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
10077 ));
10078 }
10079 let code_row = matrix.in_features / 2;
10080 let scale_row = matrix.in_features / 16;
10081 let local_code = local_in / 2;
10082 let local_scale = local_in / 16;
10083 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
10084 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
10085 for row in 0..matrix.out_features {
10086 let code_start = row * code_row + rank * local_code;
10087 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
10088 let scale_start = row * scale_row + rank * local_scale;
10089 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
10090 }
10091 Ok((codes, scales, local_in))
10092}
10093
10094fn run_rank_nvfp4(
10098 engine: &Engine,
10099 matrix: Nvfp4BlockMatrix<'_>,
10100 activations: &[f32],
10101 tokens: usize,
10102) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10103 matrix.validate()?;
10104 validate_activations(activations, tokens, matrix.in_features)?;
10105 let _main = engine.gpu.enter_main()?;
10106 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
10107 let activations = engine.htod(activations)?;
10108 let output = engine.qmatvec_nvfp4_fast(
10109 &blocks.slice(0..blocks.len()),
10110 &activations,
10111 tokens,
10112 matrix.in_features,
10113 matrix.out_features,
10114 nvfp4_row_bytes(matrix.in_features),
10115 )?;
10116 engine.dtoh(&output)
10117}
10118
10119fn upload_rank_nvfp4(
10120 engine: &Engine,
10121 matrix: Nvfp4BlockMatrix<'_>,
10122) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
10123 matrix.validate()?;
10124 let _main = engine.gpu.enter_main()?;
10125 Ok(ResidentNvfp4Rank {
10126 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
10127 macro_scale: matrix.macro_scale,
10128 out_features: matrix.out_features,
10129 in_features: matrix.in_features,
10130 row_bytes: nvfp4_row_bytes(matrix.in_features),
10131 })
10132}
10133
10134fn run_resident_rank_nvfp4(
10135 engine: &Engine,
10136 rank: &ResidentNvfp4Rank,
10137 activations: &[f32],
10138 tokens: usize,
10139) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10140 validate_activations(activations, tokens, rank.in_features)?;
10141 let _main = engine.gpu.enter_main()?;
10142 let activations = engine.htod(activations)?;
10143 let output = engine.qmatvec_nvfp4_fast(
10144 &rank.blocks.slice(0..rank.blocks.len()),
10145 &activations,
10146 tokens,
10147 rank.in_features,
10148 rank.out_features,
10149 rank.row_bytes,
10150 )?;
10151 engine.dtoh(&output)
10152}
10153
10154fn apply_macro(values: &mut [f32], macro_scale: f32) {
10155 for value in values.iter_mut() {
10156 *value *= macro_scale;
10157 }
10158}
10159
10160impl TpE4m3HostBounce {
10161 pub fn full_nvfp4(
10163 &self,
10164 matrix: Nvfp4BlockMatrix<'_>,
10165 activations: &[f32],
10166 tokens: usize,
10167 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10168 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
10169 apply_macro(&mut output, matrix.macro_scale);
10170 Ok(output)
10171 }
10172
10173 pub fn column_parallel_nvfp4(
10176 &self,
10177 matrix: Nvfp4BlockMatrix<'_>,
10178 activations: &[f32],
10179 tokens: usize,
10180 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
10181 matrix.validate()?;
10182 validate_activations(activations, tokens, matrix.in_features)?;
10183 let tp = self.ranks.len();
10184 let local_out = matrix.out_features / tp;
10185 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10186 let mut rank_outputs = Vec::with_capacity(tp);
10187 for (rank_index, rank) in self.ranks.iter().enumerate() {
10188 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
10189 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
10190 let row_start = rank_index * local_out;
10191 for token in 0..tokens {
10192 gathered[token * matrix.out_features + row_start
10193 ..token * matrix.out_features + row_start + local_out]
10194 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10195 }
10196 rank_outputs.push(output);
10197 }
10198 apply_macro(&mut gathered, matrix.macro_scale);
10199 Ok(ColumnParallelResult {
10200 gathered,
10201 rank_outputs,
10202 })
10203 }
10204
10205 pub fn row_parallel_nvfp4(
10208 &self,
10209 matrix: Nvfp4BlockMatrix<'_>,
10210 activations: &[f32],
10211 tokens: usize,
10212 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
10213 matrix.validate()?;
10214 validate_activations(activations, tokens, matrix.in_features)?;
10215 let tp = self.ranks.len();
10216 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10217 let mut rank_partials = Vec::with_capacity(tp);
10218 for (rank_index, rank) in self.ranks.iter().enumerate() {
10219 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
10220 let local_activations =
10221 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10222 let shard = Nvfp4BlockMatrix {
10223 codes: &codes,
10224 scales: &scales,
10225 macro_scale: matrix.macro_scale,
10226 out_features: matrix.out_features,
10227 in_features: local_in,
10228 };
10229 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
10230 for (sum, value) in reduced.iter_mut().zip(&partial) {
10231 *sum += *value;
10232 }
10233 rank_partials.push(partial);
10234 }
10235 apply_macro(&mut reduced, matrix.macro_scale);
10236 Ok(RowParallelResult {
10237 reduced,
10238 rank_partials,
10239 })
10240 }
10241
10242 pub fn upload_expert_nvfp4(
10243 &self,
10244 gate: Nvfp4BlockMatrix<'_>,
10245 up: Nvfp4BlockMatrix<'_>,
10246 down: Nvfp4BlockMatrix<'_>,
10247 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
10248 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10249 return Err("NVFP4 TP expert gate/up dimensions differ".into());
10250 }
10251 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10252 return Err(format!(
10253 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
10254 down.out_features, down.in_features, gate.out_features, gate.in_features
10255 )
10256 .into());
10257 }
10258 let tp = self.ranks.len();
10259 let mut gate_ranks = Vec::with_capacity(tp);
10260 let mut up_ranks = Vec::with_capacity(tp);
10261 let mut down_ranks = Vec::with_capacity(tp);
10262 for (rank_index, engine) in self.ranks.iter().enumerate() {
10263 gate_ranks.push(upload_rank_nvfp4(
10264 engine,
10265 nvfp4_column_shard(gate, tp, rank_index)?,
10266 )?);
10267 up_ranks.push(upload_rank_nvfp4(
10268 engine,
10269 nvfp4_column_shard(up, tp, rank_index)?,
10270 )?);
10271 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
10272 down_ranks.push(upload_rank_nvfp4(
10273 engine,
10274 Nvfp4BlockMatrix {
10275 codes: &codes,
10276 scales: &scales,
10277 macro_scale: down.macro_scale,
10278 out_features: down.out_features,
10279 in_features: local_in,
10280 },
10281 )?);
10282 }
10283 Ok(ResidentTpNvfp4Expert {
10284 gate: ResidentNvfp4ColumnParallel {
10285 ranks: gate_ranks,
10286 out_features: gate.out_features,
10287 in_features: gate.in_features,
10288 },
10289 up: ResidentNvfp4ColumnParallel {
10290 ranks: up_ranks,
10291 out_features: up.out_features,
10292 in_features: up.in_features,
10293 },
10294 down: ResidentNvfp4RowParallel {
10295 ranks: down_ranks,
10296 out_features: down.out_features,
10297 in_features: down.in_features,
10298 },
10299 input_width: gate.in_features,
10300 expert_width: gate.out_features,
10301 })
10302 }
10303
10304 fn column_parallel_resident_nvfp4(
10305 &self,
10306 matrix: &ResidentNvfp4ColumnParallel,
10307 activations: &[f32],
10308 tokens: usize,
10309 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10310 validate_activations(activations, tokens, matrix.in_features)?;
10311 let local_out = matrix.out_features / self.ranks.len();
10312 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10313 let mut macro_scale = None;
10314 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10315 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
10316 let row_start = rank_index * local_out;
10317 for token in 0..tokens {
10318 gathered[token * matrix.out_features + row_start
10319 ..token * matrix.out_features + row_start + local_out]
10320 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10321 }
10322 macro_scale = Some(shard.macro_scale);
10323 }
10324 apply_macro(
10325 &mut gathered,
10326 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
10327 );
10328 Ok(gathered)
10329 }
10330
10331 fn row_parallel_resident_nvfp4(
10332 &self,
10333 matrix: &ResidentNvfp4RowParallel,
10334 activations: &[f32],
10335 tokens: usize,
10336 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10337 validate_activations(activations, tokens, matrix.in_features)?;
10338 let tp = self.ranks.len();
10339 let local_in = matrix.in_features / tp;
10340 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10341 let mut macro_scale = None;
10342 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10343 if shard.in_features != local_in {
10344 return Err(format!(
10345 "NVFP4 resident row shard in_features {} != expected {local_in}",
10346 shard.in_features
10347 )
10348 .into());
10349 }
10350 let local_activations =
10351 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10352 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
10353 for (sum, value) in reduced.iter_mut().zip(&partial) {
10354 *sum += *value;
10355 }
10356 macro_scale = Some(shard.macro_scale);
10357 }
10358 apply_macro(
10359 &mut reduced,
10360 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
10361 );
10362 Ok(reduced)
10363 }
10364
10365 pub fn run_expert_nvfp4(
10366 &self,
10367 expert: &ResidentTpNvfp4Expert,
10368 input: &[f32],
10369 tokens: usize,
10370 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10371 validate_activations(input, tokens, expert.input_width)?;
10372 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
10373 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
10374 let activated: Vec<f32> = gate
10375 .iter()
10376 .zip(&up)
10377 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
10378 .collect();
10379 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
10380 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
10381 }
10382
10383 #[allow(clippy::manual_is_multiple_of)] pub fn upload_tensor_parallel_nvfp4(
10386 &self,
10387 gate: Nvfp4ExpertBank<'_>,
10388 up: Nvfp4ExpertBank<'_>,
10389 down: Nvfp4ExpertBank<'_>,
10390 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
10391 gate.validate()?;
10392 up.validate()?;
10393 down.validate()?;
10394 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10395 return Err("NVFP4 TP gate/up/down expert counts differ".into());
10396 }
10397 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10398 return Err("NVFP4 TP gate/up dimensions differ".into());
10399 }
10400 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10401 return Err(format!(
10402 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
10403 down.out_features, down.in_features, gate.out_features, gate.in_features
10404 )
10405 .into());
10406 }
10407 let tp = self.ranks.len();
10408 if gate.out_features % tp != 0 {
10409 return Err(format!(
10410 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
10411 gate.out_features
10412 )
10413 .into());
10414 }
10415 if !down.in_features.is_multiple_of(NVFP4_CANONICAL_ROW_SHARDS)
10416 || !(down.in_features / NVFP4_CANONICAL_ROW_SHARDS).is_multiple_of(64)
10417 {
10418 return Err(format!(
10419 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
10420 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
10421 down.in_features
10422 )
10423 .into());
10424 }
10425 if tp > NVFP4_CANONICAL_ROW_SHARDS {
10426 return Err(format!(
10427 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
10428 ({NVFP4_CANONICAL_ROW_SHARDS})"
10429 )
10430 .into());
10431 }
10432
10433 let ep2 = step_nvfp4_ep2_on() && tp == 2;
10434 let slot_major = ep2 || bank_slot_major_on();
10439 eprintln!(
10447 "[nvfp4-bank] layout={} source={} tp={tp} experts={} in_f={} out_f={}",
10448 if slot_major {
10449 "slot-major"
10450 } else {
10451 "block-nvfp4-v1"
10452 },
10453 if ep2 {
10458 "ep2-always"
10459 } else {
10460 bank_slot_major_source().1
10461 },
10462 gate.expert_count,
10463 gate.in_features,
10464 gate.out_features
10465 );
10466 let mut gate_ranks = Vec::with_capacity(tp);
10467 let mut up_ranks = Vec::with_capacity(tp);
10468 let mut macros_gate_dev = Vec::with_capacity(tp);
10469 let mut macros_up_dev = Vec::with_capacity(tp);
10470 let mut macros_down_dev = Vec::with_capacity(tp);
10471 for (rank_index, engine) in self.ranks.iter().enumerate() {
10472 let _main = engine.gpu.enter_main()?;
10473 let mut gate_host: Vec<u8> = Vec::new();
10479 let mut up_host: Vec<u8> = Vec::new();
10480 let mut owned = 0usize;
10481 for expert in 0..gate.expert_count {
10482 if ep2 {
10483 if expert % 2 != rank_index {
10484 continue;
10485 }
10486 owned += 1;
10487 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10488 gate.expert(expert)?,
10489 slot_major,
10490 ));
10491 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10492 up.expert(expert)?,
10493 slot_major,
10494 ));
10495 } else {
10496 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
10497 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, slot_major));
10498 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
10499 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, slot_major));
10500 }
10501 }
10502 let bank_experts = if ep2 { owned } else { gate.expert_count };
10503 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
10504 let up_expert_bytes = up_host.len() / bank_experts.max(1);
10505 let local_out = if ep2 {
10506 gate.out_features
10507 } else {
10508 gate.out_features / tp
10509 };
10510 gate_ranks.push(ResidentNvfp4ColumnBankRank {
10511 bank: engine.htod_bytes(&gate_host)?,
10512 expert_bytes: gate_expert_bytes,
10513 local_out,
10514 in_features: gate.in_features,
10515 row_bytes: nvfp4_row_bytes(gate.in_features),
10516 slot_major,
10517 });
10518 up_ranks.push(ResidentNvfp4ColumnBankRank {
10519 bank: engine.htod_bytes(&up_host)?,
10520 expert_bytes: up_expert_bytes,
10521 local_out,
10522 in_features: up.in_features,
10523 row_bytes: nvfp4_row_bytes(up.in_features),
10524 slot_major,
10525 });
10526 macros_gate_dev.push(engine.htod(gate.macros)?);
10527 macros_up_dev.push(engine.htod(up.macros)?);
10528 macros_down_dev.push(engine.htod(down.macros)?);
10529 }
10530 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
10534 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
10535 let device_rank = shard_index % tp;
10536 let engine = &self.ranks[device_rank];
10537 let _main = engine.gpu.enter_main()?;
10538 let mut down_host: Vec<u8> = Vec::new();
10539 let mut owned = 0usize;
10540 for expert in 0..down.expert_count {
10541 let down_matrix = down.expert(expert)?;
10542 if ep2 {
10543 if expert % 2 != device_rank {
10546 continue;
10547 }
10548 owned += 1;
10549 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix, slot_major));
10550 } else {
10551 let (codes, scales, local_in) =
10552 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
10553 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10554 Nvfp4BlockMatrix {
10555 codes: &codes,
10556 scales: &scales,
10557 macro_scale: down_matrix.macro_scale,
10558 out_features: down_matrix.out_features,
10559 in_features: local_in,
10560 },
10561 slot_major,
10562 ));
10563 }
10564 }
10565 let bank_experts = if ep2 { owned } else { down.expert_count };
10566 let down_expert_bytes = down_host.len() / bank_experts.max(1);
10567 let local_in = if ep2 {
10568 down.in_features
10569 } else {
10570 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
10571 };
10572 down_ranks.push(ResidentNvfp4RowBankRank {
10573 bank: engine.htod_bytes(&down_host)?,
10574 expert_bytes: down_expert_bytes,
10575 device_rank,
10576 out_features: down.out_features,
10577 local_in,
10578 row_bytes: nvfp4_row_bytes(local_in),
10579 slot_major,
10580 });
10581 }
10582 Ok(ResidentNvfp4TensorParallel {
10583 gate: gate_ranks,
10584 up: up_ranks,
10585 down: down_ranks,
10586 macros_gate: gate.macros.to_vec(),
10587 macros_up: up.macros.to_vec(),
10588 macros_down: down.macros.to_vec(),
10589 macros_gate_dev,
10590 macros_up_dev,
10591 macros_down_dev,
10592 expert_count: gate.expert_count,
10593 input_width: gate.in_features,
10594 expert_width: gate.out_features,
10595 device_workspace: std::sync::Mutex::new(None),
10596 prime_tables: std::sync::Mutex::new(Vec::new()),
10597 ep2,
10598 })
10599 }
10600
10601 fn run_full_bank_expert_nvfp4(
10605 &self,
10606 ranks: &[ResidentNvfp4ColumnBankRank],
10607 macros: &[f32],
10608 expert: usize,
10609 input: &[f32],
10610 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10611 let owner = expert & 1;
10612 let slot = expert >> 1;
10613 let bank = ranks
10614 .get(owner)
10615 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
10616 let engine = &self.ranks[owner];
10617 let _main = engine.gpu.enter_main()?;
10618 let activations = engine.htod(input)?;
10619 let output = bank.host_canonical_expert(engine, slot, &activations)?;
10620 let mut out = engine.dtoh(&output)?;
10621 apply_macro(&mut out, macros[expert]);
10622 Ok(out)
10623 }
10624
10625 fn run_full_down_expert_nvfp4(
10628 &self,
10629 shards: &[ResidentNvfp4RowBankRank],
10630 macros: &[f32],
10631 expert: usize,
10632 input: &[f32],
10633 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10634 let owner = expert & 1;
10635 let slot = expert >> 1;
10636 let shard = shards
10637 .get(owner)
10638 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
10639 let engine = &self.ranks[owner];
10640 let _main = engine.gpu.enter_main()?;
10641 let activations = engine.htod(input)?;
10642 let output = shard.host_canonical_expert(engine, slot, &activations)?;
10643 let mut out = engine.dtoh(&output)?;
10644 apply_macro(&mut out, macros[expert]);
10645 Ok(out)
10646 }
10647
10648 fn run_column_bank_expert_nvfp4(
10649 &self,
10650 ranks: &[ResidentNvfp4ColumnBankRank],
10651 macros: &[f32],
10652 expert: usize,
10653 input: &[f32],
10654 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10655 let local_out = ranks
10656 .first()
10657 .ok_or("NVFP4 TP column bank has no ranks")?
10658 .local_out;
10659 let mut gathered = vec![0.0f32; local_out * ranks.len()];
10660 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
10661 let _main = engine.gpu.enter_main()?;
10662 let activations = engine.htod(input)?;
10663 let output = bank.host_canonical_expert(engine, expert, &activations)?;
10664 let output = engine.dtoh(&output)?;
10665 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
10666 }
10667 apply_macro(&mut gathered, macros[expert]);
10668 Ok(gathered)
10669 }
10670
10671 fn run_row_bank_expert_nvfp4(
10675 &self,
10676 shards: &[ResidentNvfp4RowBankRank],
10677 macros: &[f32],
10678 expert: usize,
10679 input: &[f32],
10680 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10681 let out_features = shards
10682 .first()
10683 .ok_or("NVFP4 TP row bank has no canonical shards")?
10684 .out_features;
10685 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
10686 let mut reduced = vec![0.0f32; out_features];
10687 for (shard_index, shard) in shards.iter().enumerate() {
10688 let engine = self
10689 .ranks
10690 .get(shard.device_rank)
10691 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
10692 let _main = engine.gpu.enter_main()?;
10693 let local_activations =
10694 activation_shard(input, 1, in_features, shards.len(), shard_index);
10695 let activations = engine.htod(&local_activations)?;
10696 let output = shard.host_canonical_expert(engine, expert, &activations)?;
10697 let partial = engine.dtoh(&output)?;
10698 for (sum, value) in reduced.iter_mut().zip(&partial) {
10699 *sum += *value;
10700 }
10701 }
10702 apply_macro(&mut reduced, macros[expert]);
10703 Ok(reduced)
10704 }
10705
10706 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel_nvfp4(
10711 &self,
10712 gate: Nvfp4ExpertBank<'_>,
10713 up: Nvfp4ExpertBank<'_>,
10714 down: Nvfp4ExpertBank<'_>,
10715 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10716 gate.validate()?;
10717 up.validate()?;
10718 down.validate()?;
10719 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10720 return Err("NVFP4 EP gate/up/down expert counts differ".into());
10721 }
10722 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10723 return Err("NVFP4 EP gate/up dimensions differ".into());
10724 }
10725 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10726 return Err(format!(
10727 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
10728 down.out_features, down.in_features, gate.out_features, gate.in_features
10729 )
10730 .into());
10731 }
10732 let world = self.ranks.len();
10733 if gate.expert_count % world != 0 {
10734 return Err(format!(
10735 "NVFP4 EP expert count {} is not divisible by {world} ranks",
10736 gate.expert_count
10737 )
10738 .into());
10739 }
10740 let experts_per_rank = gate.expert_count / world;
10741 let mut ranks = Vec::with_capacity(world);
10742 for (rank_index, engine) in self.ranks.iter().enumerate() {
10743 let _main = engine.gpu.enter_main()?;
10744 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10745 let mut gate_host = Vec::new();
10746 let mut up_host = Vec::new();
10747 let mut down_host = Vec::new();
10748 for expert in expert_range.clone() {
10749 gate_host.extend_from_slice(&nvfp4_repack_matrix(gate.expert(expert)?));
10750 up_host.extend_from_slice(&nvfp4_repack_matrix(up.expert(expert)?));
10751 down_host.extend_from_slice(&nvfp4_repack_matrix(down.expert(expert)?));
10752 }
10753 let gate_expert_bytes = gate_host.len() / experts_per_rank;
10754 let up_expert_bytes = up_host.len() / experts_per_rank;
10755 if gate_expert_bytes != up_expert_bytes {
10756 return Err("NVFP4 EP gate/up packed expert bytes differ".into());
10757 }
10758 let down_expert_bytes = down_host.len() / experts_per_rank;
10759 ranks.push(ResidentNvfp4EpRank {
10760 gate: engine.htod_bytes(&gate_host)?,
10761 up: engine.htod_bytes(&up_host)?,
10762 down: engine.htod_bytes(&down_host)?,
10763 gate_expert_bytes,
10764 down_expert_bytes,
10765 macros_gate: engine.htod(&gate.macros[expert_range.clone()])?,
10766 macros_up: engine.htod(&up.macros[expert_range.clone()])?,
10767 macros_down: engine.htod(&down.macros[expert_range.clone()])?,
10768 expert_range,
10769 });
10770 }
10771 Ok(ResidentNvfp4ExpertParallel {
10772 ranks,
10773 macros_gate: gate.macros.to_vec(),
10774 macros_up: up.macros.to_vec(),
10775 macros_down: down.macros.to_vec(),
10776 expert_count: gate.expert_count,
10777 input_width: gate.in_features,
10778 expert_width: gate.out_features,
10779 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
10780 down_row_bytes: nvfp4_row_bytes(down.in_features),
10781 device_workspace: std::sync::Mutex::new(None),
10782 })
10783 }
10784
10785 pub fn upload_expert_parallel_nvfp4_normalized(
10791 &self,
10792 gate: &crate::model::HostExps,
10793 up: &crate::model::HostExps,
10794 down: &crate::model::HostExps,
10795 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10796 for (label, bank) in [("gate", gate), ("up", up), ("down", down)] {
10797 if bank.qtype != crate::QT_NVFP4 || !bank.is_uniform_layout() {
10798 return Err(format!(
10799 "NVFP4 EP normalized {label} bank requires one uniform NVFP4 layout, \
10800 got qtype={} uniform={}",
10801 bank.qtype,
10802 bank.is_uniform_layout()
10803 )
10804 .into());
10805 }
10806 if bank.n_expert == 0
10807 || bank.expert_stride != bank.out_f * bank.row_bytes
10808 || (0..bank.n_expert)
10809 .any(|expert| bank.expert_bytes(expert).len() != bank.expert_stride)
10810 {
10811 return Err(format!("NVFP4 EP normalized {label} bank geometry is invalid").into());
10812 }
10813 }
10814 if gate.n_expert != up.n_expert || gate.n_expert != down.n_expert {
10815 return Err("NVFP4 EP normalized gate/up/down expert counts differ".into());
10816 }
10817 if gate.in_f != up.in_f || gate.out_f != up.out_f {
10818 return Err("NVFP4 EP normalized gate/up dimensions differ".into());
10819 }
10820 if down.in_f != gate.out_f || down.out_f != gate.in_f {
10821 return Err(format!(
10822 "NVFP4 EP normalized down {}x{} does not invert gate/up {}x{}",
10823 down.out_f, down.in_f, gate.out_f, gate.in_f
10824 )
10825 .into());
10826 }
10827 let macros = |bank: &crate::model::HostExps| -> Result<Vec<f32>, String> {
10828 let values = bank
10829 .macros
10830 .clone()
10831 .unwrap_or_else(|| vec![1.0; bank.n_expert]);
10832 if values.len() != bank.n_expert
10833 || !values.iter().all(|value| value.is_finite() && *value > 0.0)
10834 {
10835 return Err("NVFP4 EP normalized macro row is not finite-positive".to_string());
10836 }
10837 Ok(values)
10838 };
10839 let macros_gate = macros(gate)?;
10840 let macros_up = macros(up)?;
10841 let macros_down = macros(down)?;
10842 let world = self.ranks.len();
10843 if !gate.n_expert.is_multiple_of(world) {
10844 return Err(format!(
10845 "NVFP4 EP normalized expert count {} is not divisible by {world} ranks",
10846 gate.n_expert
10847 )
10848 .into());
10849 }
10850 let experts_per_rank = gate.n_expert / world;
10851 let mut ranks = Vec::with_capacity(world);
10852 for (rank_index, engine) in self.ranks.iter().enumerate() {
10853 let _main = engine.gpu.enter_main()?;
10854 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10855 let mut gate_host = Vec::with_capacity(experts_per_rank * gate.expert_stride);
10856 let mut up_host = Vec::with_capacity(experts_per_rank * up.expert_stride);
10857 let mut down_host = Vec::with_capacity(experts_per_rank * down.expert_stride);
10858 for expert in expert_range.clone() {
10859 gate_host.extend_from_slice(gate.expert_bytes(expert));
10860 up_host.extend_from_slice(up.expert_bytes(expert));
10861 down_host.extend_from_slice(down.expert_bytes(expert));
10862 }
10863 ranks.push(ResidentNvfp4EpRank {
10864 gate: engine.htod_bytes(&gate_host)?,
10865 up: engine.htod_bytes(&up_host)?,
10866 down: engine.htod_bytes(&down_host)?,
10867 gate_expert_bytes: gate.expert_stride,
10868 down_expert_bytes: down.expert_stride,
10869 macros_gate: engine.htod(¯os_gate[expert_range.clone()])?,
10870 macros_up: engine.htod(¯os_up[expert_range.clone()])?,
10871 macros_down: engine.htod(¯os_down[expert_range.clone()])?,
10872 expert_range,
10873 });
10874 }
10875 Ok(ResidentNvfp4ExpertParallel {
10876 ranks,
10877 macros_gate,
10878 macros_up,
10879 macros_down,
10880 expert_count: gate.n_expert,
10881 input_width: gate.in_f,
10882 expert_width: gate.out_f,
10883 gate_row_bytes: gate.row_bytes,
10884 down_row_bytes: down.row_bytes,
10885 device_workspace: std::sync::Mutex::new(None),
10886 })
10887 }
10888
10889 #[allow(clippy::too_many_arguments)]
10895 pub fn run_routed_experts_nvfp4(
10896 &self,
10897 experts: &ResidentNvfp4ExpertParallel,
10898 input: &[f32],
10899 tokens: usize,
10900 selected: &[usize],
10901 route_weights: &[f32],
10902 experts_per_token: usize,
10903 activation_limit: Option<f32>,
10904 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10905 validate_activations(input, tokens, experts.input_width)?;
10906 let pairs = tokens
10907 .checked_mul(experts_per_token)
10908 .ok_or("NVFP4 EP route count overflow")?;
10909 if selected.len() != pairs || route_weights.len() != pairs {
10910 return Err(format!(
10911 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
10912 {experts_per_token} ({pairs})",
10913 selected.len(),
10914 route_weights.len(),
10915 )
10916 .into());
10917 }
10918 if !route_weights.iter().all(|weight| weight.is_finite()) {
10919 return Err("NVFP4 EP route weights contain a non-finite value".into());
10920 }
10921 let experts_per_rank = experts.expert_count / experts.ranks.len();
10922 let mut output = vec![0.0f32; tokens * experts.input_width];
10923 for token in 0..tokens {
10924 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
10925 for slot in 0..experts_per_token {
10926 let pair = token * experts_per_token + slot;
10927 let expert = selected[pair];
10928 if expert >= experts.expert_count {
10929 return Err(format!(
10930 "NVFP4 EP selected expert {expert} outside 0..{}",
10931 experts.expert_count
10932 )
10933 .into());
10934 }
10935 let owner = expert / experts_per_rank;
10936 let local = expert - owner * experts_per_rank;
10937 let rank = &experts.ranks[owner];
10938 let engine = &self.ranks[owner];
10939 let _main = engine.gpu.enter_main()?;
10940 let device_input = engine.htod(input_row)?;
10941 let gate_out = engine.qmatvec_nvfp4_fast(
10942 &rank.gate.slice(
10943 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
10944 ),
10945 &device_input,
10946 1,
10947 experts.input_width,
10948 experts.expert_width,
10949 experts.gate_row_bytes,
10950 )?;
10951 let up_out = engine.qmatvec_nvfp4_fast(
10952 &rank.up.slice(
10953 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
10954 ),
10955 &device_input,
10956 1,
10957 experts.input_width,
10958 experts.expert_width,
10959 experts.gate_row_bytes,
10960 )?;
10961 let mut gate_host = engine.dtoh(&gate_out)?;
10962 let mut up_host = engine.dtoh(&up_out)?;
10963 apply_macro(&mut gate_host, experts.macros_gate[expert]);
10964 apply_macro(&mut up_host, experts.macros_up[expert]);
10965 let activated: Vec<f32> = gate_host
10966 .iter()
10967 .zip(&up_host)
10968 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
10969 .collect();
10970 let device_activated = engine.htod(&activated)?;
10971 let down_out = engine.qmatvec_nvfp4_fast(
10972 &rank.down.slice(
10973 local * rank.down_expert_bytes..(local + 1) * rank.down_expert_bytes,
10974 ),
10975 &device_activated,
10976 1,
10977 experts.expert_width,
10978 experts.input_width,
10979 experts.down_row_bytes,
10980 )?;
10981 let mut down_host = engine.dtoh(&down_out)?;
10982 apply_macro(&mut down_host, experts.macros_down[expert]);
10983 let weight = route_weights[pair];
10984 for (sum, value) in output
10985 [token * experts.input_width..(token + 1) * experts.input_width]
10986 .iter_mut()
10987 .zip(down_host)
10988 {
10989 *sum += weight * value;
10990 }
10991 }
10992 }
10993 Ok(output)
10994 }
10995
10996 #[allow(clippy::too_many_arguments)]
11005 pub fn run_routed_experts_nvfp4_w4a16_device_io(
11006 &self,
11007 experts: &ResidentNvfp4ExpertParallel,
11008 e: &Engine,
11009 input_dev: &crate::CudaSlice<f32>,
11010 tokens: usize,
11011 selected: &[usize],
11012 route_weights: &[f32],
11013 experts_per_token: usize,
11014 activation_limit: Option<f32>,
11015 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11016 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11020 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11021 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11022 let started = timing.then(std::time::Instant::now);
11023 if !self.native_p2p {
11024 return Err("W4A16 NVFP4 device EP requires native P2P".into());
11025 }
11026 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11027 return Err(format!(
11028 "W4A16 NVFP4 device EP root device {:?} != model engine device {}",
11029 self.devices.first(),
11030 e.ctx().ordinal()
11031 )
11032 .into());
11033 }
11034 let active_input_values =
11038 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11039 let pairs = tokens
11040 .checked_mul(experts_per_token)
11041 .ok_or("W4A16 NVFP4 device EP route count overflow")?;
11042 if selected.len() != pairs || route_weights.len() != pairs {
11043 return Err(format!(
11044 "W4A16 NVFP4 device EP routes selected={} weights={} != tokens {tokens} x \
11045 experts/token {experts_per_token} ({pairs})",
11046 selected.len(),
11047 route_weights.len(),
11048 )
11049 .into());
11050 }
11051 if !route_weights.iter().all(|weight| weight.is_finite()) {
11052 return Err("W4A16 NVFP4 device EP route weights contain a non-finite value".into());
11053 }
11054 let world = self.ranks.len();
11055 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11056 return Err(format!(
11057 "W4A16 NVFP4 device EP runtime ranks {world} != bank ranks {}",
11058 experts.ranks.len()
11059 )
11060 .into());
11061 }
11062 let owner_routes = partition_expert_owner_routes(
11063 experts.expert_count,
11064 world,
11065 tokens,
11066 experts_per_token,
11067 selected,
11068 )?;
11069
11070 let mut workspace_guard = experts
11071 .device_workspace
11072 .lock()
11073 .map_err(|_| "W4A16 NVFP4 device EP workspace lock is poisoned")?;
11074 if workspace_guard.is_none() {
11075 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11076 let capacity_pairs = capacity_tokens * experts_per_token;
11077 let mut input = Vec::with_capacity(world);
11078 let mut input_bf16 = Vec::with_capacity(world);
11079 let mut input_q8 = Vec::with_capacity(world);
11080 let mut input_q8_scales = Vec::with_capacity(world);
11081 let mut sel = Vec::with_capacity(world);
11082 let mut token_rows = Vec::with_capacity(world);
11083 let mut global_pairs = Vec::with_capacity(world);
11084 let mut route_w = Vec::with_capacity(world);
11085 let mut gate_out = Vec::with_capacity(world);
11086 let mut up_out = Vec::with_capacity(world);
11087 let mut activation_bf16 = Vec::with_capacity(world);
11088 let mut activation_q8 = Vec::with_capacity(world);
11089 let mut activation_q8_scales = Vec::with_capacity(world);
11090 let mut ev_rank = Vec::with_capacity(world);
11091 for engine in &self.ranks {
11092 let _main = engine.gpu.enter_main()?;
11093 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11094 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11095 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11096 input_q8_scales
11097 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11098 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11099 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11100 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11101 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11102 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11103 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11104 activation_bf16
11105 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11106 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11107 activation_q8_scales
11108 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11109 ev_rank.push(engine.ctx().new_event(None)?);
11110 }
11111 let _main = e.gpu.enter_main()?;
11112 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11113 let slot_rows_raw = {
11114 use cudarc::driver::DevicePtr;
11115 let stream = e.stream();
11116 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11117 pointer
11118 };
11119 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11120 input,
11121 input_bf16,
11122 input_q8,
11123 input_q8_scales,
11124 sel,
11125 token_rows,
11126 global_pairs,
11127 route_w,
11128 gate_out,
11129 up_out,
11130 activation_bf16,
11131 activation_q8,
11132 activation_q8_scales,
11133 slot_rows,
11134 slot_rows_raw,
11135 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11136 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11137 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11138 graph_routes: None,
11139 graphs: std::iter::repeat_with(|| None)
11140 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11141 .collect(),
11142 ev_entry: e.ctx().new_event(None)?,
11143 ev_entry_device: e.ctx().ordinal(),
11144 ev_rank,
11145 phase_events: None,
11146 capacity_tokens,
11147 experts_per_token,
11148 });
11149 }
11150 let workspace = workspace_guard
11151 .as_mut()
11152 .expect("W4A16 NVFP4 device EP workspace initialized above");
11153 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11154 return Err(format!(
11155 "W4A16 NVFP4 device EP workspace tokens={} experts/token={} cannot serve \
11156 tokens={tokens} experts/token={experts_per_token}",
11157 workspace.capacity_tokens, workspace.experts_per_token,
11158 )
11159 .into());
11160 }
11161 if workspace.ev_entry_device != e.ctx().ordinal() {
11162 return Err("W4A16 NVFP4 device EP model engine changed".into());
11163 }
11164
11165 {
11166 let _main = e.gpu.enter_main()?;
11167 let mut destination = workspace.route_weights.slice_mut(0..pairs);
11168 e.stream()
11169 .memcpy_htod(&route_weights[..pairs], &mut destination)?;
11170 workspace.ev_entry.record(&e.stream())?;
11171 }
11172 for (rank_index, engine) in self.ranks.iter().enumerate() {
11173 let _main = engine.gpu.enter_main()?;
11174 engine.stream().wait(&workspace.ev_entry)?;
11175 {
11176 let mut destination = workspace.input[rank_index].slice_mut(0..active_input_values);
11177 engine
11178 .stream()
11179 .memcpy_dtod(&input_dev.slice(0..active_input_values), &mut destination)?;
11180 }
11181 engine.f32_to_bf16_into(
11182 &workspace.input[rank_index],
11183 &mut workspace.input_bf16[rank_index],
11184 tokens * experts.input_width,
11185 )?;
11186 let owner = &owner_routes[rank_index];
11187 debug_assert_eq!(owner.rank, rank_index);
11188 let local_count = owner.selected.len();
11189 if local_count > 0 {
11190 let local_selected = owner
11191 .selected
11192 .iter()
11193 .map(|&expert| expert as i32)
11194 .collect::<Vec<_>>();
11195 let local_token_rows = owner
11196 .token_rows
11197 .iter()
11198 .map(|&token| token as i32)
11199 .collect::<Vec<_>>();
11200 let local_global_pairs = owner
11201 .global_pairs
11202 .iter()
11203 .map(|&pair| pair as i32)
11204 .collect::<Vec<_>>();
11205 {
11206 let mut destination = workspace.sel[rank_index].slice_mut(0..local_count);
11207 engine
11208 .stream()
11209 .memcpy_htod(&local_selected, &mut destination)?;
11210 }
11211 {
11212 let mut destination =
11213 workspace.token_rows[rank_index].slice_mut(0..local_count);
11214 engine
11215 .stream()
11216 .memcpy_htod(&local_token_rows, &mut destination)?;
11217 }
11218 {
11219 let mut destination =
11220 workspace.global_pairs[rank_index].slice_mut(0..local_count);
11221 engine
11222 .stream()
11223 .memcpy_htod(&local_global_pairs, &mut destination)?;
11224 }
11225 let rank = &experts.ranks[rank_index];
11226 engine.qmatvec_nvfp4_bf16_sel_dual_rows_into(
11227 &rank.gate,
11228 &rank.up,
11229 &workspace.sel[rank_index],
11230 &workspace.token_rows[rank_index],
11231 &workspace.input_bf16[rank_index],
11232 &mut workspace.gate_out[rank_index],
11233 &mut workspace.up_out[rank_index],
11234 local_count,
11235 experts.input_width,
11236 experts.expert_width,
11237 experts.gate_row_bytes,
11238 rank.gate_expert_bytes,
11239 tokens,
11240 )?;
11241 engine.silu_mul_scaled_host_expf_bf16_sel_into(
11242 &workspace.gate_out[rank_index],
11243 &workspace.up_out[rank_index],
11244 &rank.macros_gate,
11245 &rank.macros_up,
11246 &workspace.sel[rank_index],
11247 activation_limit,
11248 &mut workspace.activation_bf16[rank_index],
11249 experts.expert_width,
11250 local_count,
11251 )?;
11252 engine.qmatvec_nvfp4_bf16_sel_down_rows_raw(
11253 &rank.down,
11254 &workspace.sel[rank_index],
11255 &workspace.global_pairs[rank_index],
11256 &workspace.activation_bf16[rank_index],
11257 &rank.macros_down,
11258 workspace.slot_rows_raw,
11259 local_count,
11260 experts.expert_width,
11261 experts.input_width,
11262 experts.down_row_bytes,
11263 rank.down_expert_bytes,
11264 pairs,
11265 )?;
11266 }
11267 workspace.ev_rank[rank_index].record(&engine.stream())?;
11268 }
11269
11270 let output = {
11271 let _main = e.gpu.enter_main()?;
11272 for event in &workspace.ev_rank {
11273 e.stream().wait(event)?;
11274 }
11275 let mut output = e.uninit(tokens * experts.input_width)?;
11276 e.axpy_rows_seq_tokens_into(
11277 &workspace.slot_rows,
11278 &workspace.route_weights,
11279 &mut output,
11280 experts.input_width,
11281 experts_per_token,
11282 tokens,
11283 )?;
11284 output
11285 };
11286 if let Some(started) = started {
11287 use std::sync::atomic::Ordering;
11288 e.stream().synchronize()?;
11289 let elapsed = started.elapsed().as_nanos() as u64;
11290 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11291 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11292 if calls.is_multiple_of(430) {
11293 eprintln!(
11294 "[nvfp4-ep-w4a16-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11295 ns as f64 / 1.0e6,
11296 ns as f64 / calls as f64 / 1.0e3,
11297 );
11298 }
11299 }
11300 Ok(output)
11301 }
11302
11303 #[allow(clippy::too_many_arguments)]
11309 pub fn run_routed_experts_nvfp4_w4a16_device_routed(
11310 &self,
11311 experts: &ResidentNvfp4ExpertParallel,
11312 e: &Engine,
11313 input_dev: &crate::CudaSlice<f32>,
11314 selected_dev: &crate::CudaSlice<i32>,
11315 route_weights_dev: &crate::CudaSlice<f32>,
11316 tokens: usize,
11317 experts_per_token: usize,
11318 activation_limit: Option<f32>,
11319 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11320 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11321 experts,
11322 e,
11323 input_dev,
11324 selected_dev,
11325 route_weights_dev,
11326 tokens,
11327 experts_per_token,
11328 activation_limit,
11329 None,
11330 )
11331 }
11332
11333 #[allow(clippy::too_many_arguments)]
11337 pub fn run_routed_experts_nvfp4_w4a16_device_routed_prejoin(
11338 &self,
11339 experts: &ResidentNvfp4ExpertParallel,
11340 e: &Engine,
11341 input_dev: &crate::CudaSlice<f32>,
11342 selected_dev: &crate::CudaSlice<i32>,
11343 route_weights_dev: &crate::CudaSlice<f32>,
11344 tokens: usize,
11345 experts_per_token: usize,
11346 activation_limit: Option<f32>,
11347 mut pre_join: impl FnMut() -> Result<(), Box<dyn std::error::Error>>,
11348 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11349 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11350 experts,
11351 e,
11352 input_dev,
11353 selected_dev,
11354 route_weights_dev,
11355 tokens,
11356 experts_per_token,
11357 activation_limit,
11358 Some(&mut pre_join),
11359 )
11360 }
11361
11362 #[allow(clippy::too_many_arguments)]
11363 fn run_routed_experts_nvfp4_w4a16_device_routed_inner(
11364 &self,
11365 experts: &ResidentNvfp4ExpertParallel,
11366 e: &Engine,
11367 input_dev: &crate::CudaSlice<f32>,
11368 selected_dev: &crate::CudaSlice<i32>,
11369 route_weights_dev: &crate::CudaSlice<f32>,
11370 tokens: usize,
11371 experts_per_token: usize,
11372 activation_limit: Option<f32>,
11373 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11374 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11375 if !self.native_p2p {
11376 return Err("W4A16 device-routed EP requires native P2P".into());
11377 }
11378 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11379 return Err(format!(
11380 "W4A16 device-routed EP root device {:?} != model engine device {}",
11381 self.devices.first(),
11382 e.ctx().ordinal()
11383 )
11384 .into());
11385 }
11386 let active_input_values =
11387 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11388 let pairs = tokens
11389 .checked_mul(experts_per_token)
11390 .ok_or("W4A16 device-routed EP route count overflow")?;
11391 if selected_dev.len() < pairs || route_weights_dev.len() < pairs {
11392 return Err(format!(
11393 "W4A16 device-routed EP metadata selected={} weights={} < pairs={pairs}",
11394 selected_dev.len(),
11395 route_weights_dev.len(),
11396 )
11397 .into());
11398 }
11399 let world = self.ranks.len();
11400 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11401 return Err(format!(
11402 "W4A16 device-routed EP runtime ranks {world} != bank ranks {}",
11403 experts.ranks.len()
11404 )
11405 .into());
11406 }
11407
11408 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11409 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11410 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11411 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11412 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11413 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11414 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11415 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11416 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11417 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11418 let started = timing.then(std::time::Instant::now);
11419 let graph_enabled = parallel_ep_graph_enabled()?;
11420 let pair_down_enabled = parallel_ep_pair_down_enabled()?;
11421
11422 let mut workspace_guard = experts
11423 .device_workspace
11424 .lock()
11425 .map_err(|_| "W4A16 device-routed EP workspace lock is poisoned")?;
11426 if workspace_guard.is_none() {
11427 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11428 let capacity_pairs = capacity_tokens * experts_per_token;
11429 let mut input = Vec::with_capacity(world);
11430 let mut input_bf16 = Vec::with_capacity(world);
11431 let mut input_q8 = Vec::with_capacity(world);
11432 let mut input_q8_scales = Vec::with_capacity(world);
11433 let mut sel = Vec::with_capacity(world);
11434 let mut token_rows = Vec::with_capacity(world);
11435 let mut global_pairs = Vec::with_capacity(world);
11436 let mut route_w = Vec::with_capacity(world);
11437 let mut gate_out = Vec::with_capacity(world);
11438 let mut up_out = Vec::with_capacity(world);
11439 let mut activation_bf16 = Vec::with_capacity(world);
11440 let mut activation_q8 = Vec::with_capacity(world);
11441 let mut activation_q8_scales = Vec::with_capacity(world);
11442 let mut ev_rank = Vec::with_capacity(world);
11443 let mut phase_head = Vec::with_capacity(world);
11444 let mut phase_copy_done = Vec::with_capacity(world);
11445 let mut phase_gate_up_done = Vec::with_capacity(world);
11446 let mut phase_activation_done = Vec::with_capacity(world);
11447 let mut phase_down_done = Vec::with_capacity(world);
11448 for engine in &self.ranks {
11449 let _main = engine.gpu.enter_main()?;
11450 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11451 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11452 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11453 input_q8_scales
11454 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11455 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11456 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11457 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11458 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11459 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11460 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11461 activation_bf16
11462 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11463 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11464 activation_q8_scales
11465 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11466 ev_rank.push(engine.ctx().new_event(None)?);
11467 if timing {
11468 phase_head.push(
11469 engine.ctx().new_event(Some(
11470 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11471 ))?,
11472 );
11473 phase_copy_done.push(
11474 engine.ctx().new_event(Some(
11475 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11476 ))?,
11477 );
11478 phase_gate_up_done.push(
11479 engine.ctx().new_event(Some(
11480 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11481 ))?,
11482 );
11483 phase_activation_done.push(
11484 engine.ctx().new_event(Some(
11485 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11486 ))?,
11487 );
11488 phase_down_done.push(
11489 engine.ctx().new_event(Some(
11490 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11491 ))?,
11492 );
11493 }
11494 }
11495 let _main = e.gpu.enter_main()?;
11496 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11497 let slot_rows_raw = {
11498 use cudarc::driver::DevicePtr;
11499 let stream = e.stream();
11500 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11501 pointer
11502 };
11503 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11504 input,
11505 input_bf16,
11506 input_q8,
11507 input_q8_scales,
11508 sel,
11509 token_rows,
11510 global_pairs,
11511 route_w,
11512 gate_out,
11513 up_out,
11514 activation_bf16,
11515 activation_q8,
11516 activation_q8_scales,
11517 slot_rows,
11518 slot_rows_raw,
11519 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11520 graph_input: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11521 graph_output: e.uninit(NVFP4_EP_GRAPH_BATCH_CAP * experts.input_width)?,
11522 graph_routes: None,
11523 graphs: std::iter::repeat_with(|| None)
11524 .take(NVFP4_EP_GRAPH_BATCH_CAP + 1)
11525 .collect(),
11526 ev_entry: e.ctx().new_event(None)?,
11527 ev_entry_device: e.ctx().ordinal(),
11528 ev_rank,
11529 phase_events: timing.then_some(Nvfp4EpPhaseEvents {
11530 head: phase_head,
11531 copy_done: phase_copy_done,
11532 gate_up_done: phase_gate_up_done,
11533 activation_done: phase_activation_done,
11534 down_done: phase_down_done,
11535 }),
11536 capacity_tokens,
11537 experts_per_token,
11538 });
11539 }
11540 let workspace = workspace_guard
11541 .as_mut()
11542 .expect("W4A16 device-routed EP workspace initialized above");
11543 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11544 return Err(format!(
11545 "W4A16 device-routed EP workspace tokens={} experts/token={} cannot serve \
11546 tokens={tokens} experts/token={experts_per_token}",
11547 workspace.capacity_tokens, workspace.experts_per_token,
11548 )
11549 .into());
11550 }
11551
11552 if tokens <= NVFP4_EP_Q8_BATCH_CAP && parallel_ep_q8_act_enabled()? {
11553 if graph_enabled {
11554 return Err("MEMRA_PARALLEL_EP_GRAPH=1 is exact W4A16-only; disable \
11555 MEMRA_PARALLEL_EP_Q8_ACT or the graph door"
11556 .into());
11557 }
11558 return self.run_routed_experts_nvfp4_w4a8_device_routed(
11559 experts,
11560 e,
11561 input_dev,
11562 selected_dev,
11563 route_weights_dev,
11564 workspace,
11565 tokens,
11566 experts_per_token,
11567 activation_limit,
11568 pre_join,
11569 );
11570 }
11571
11572 if graph_enabled && !timing && pre_join.is_none() && tokens <= NVFP4_EP_GRAPH_BATCH_CAP {
11573 use cudarc::driver::DevicePtr;
11574 let route_ptrs = {
11575 let stream = e.stream();
11576 let (sel_ptr, _sel_guard) = selected_dev.device_ptr(&stream);
11577 let (weight_ptr, _weight_guard) = route_weights_dev.device_ptr(&stream);
11578 (sel_ptr, weight_ptr)
11579 };
11580 if let Some(graph_exec) = workspace.graphs[tokens].as_ref().map(|graph| graph.exec) {
11581 if workspace.graph_routes != Some(route_ptrs) {
11582 return Err(format!(
11583 "W4A16 EP graph route buffers moved: built={:?} current={route_ptrs:?}",
11584 workspace.graph_routes,
11585 )
11586 .into());
11587 }
11588 let _main = e.gpu.enter_main()?;
11589 e.stream().memcpy_dtod(
11590 &input_dev.slice(0..active_input_values),
11591 &mut workspace.graph_input.slice_mut(0..active_input_values),
11592 )?;
11593 e.memset_zeros_view(
11594 &mut workspace
11595 .slot_rows
11596 .slice_mut(0..pairs * experts.input_width),
11597 )?;
11598 unsafe {
11599 let result = cudarc::driver::sys::cuGraphLaunch(
11600 graph_exec,
11601 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11602 );
11603 if result != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11604 return Err(format!("W4A16 EP graph launch: {result:?}").into());
11605 }
11606 }
11607 let mut output = e.uninit(active_input_values)?;
11608 e.stream().memcpy_dtod(
11609 &workspace.graph_output.slice(0..active_input_values),
11610 &mut output.slice_mut(0..active_input_values),
11611 )?;
11612 return Ok(output);
11613 }
11614 }
11615
11616 {
11617 let _main = e.gpu.enter_main()?;
11618 e.memset_zeros_view(
11619 &mut workspace
11620 .slot_rows
11621 .slice_mut(0..pairs * experts.input_width),
11622 )?;
11623 workspace.ev_entry.record(&e.stream())?;
11624 }
11625
11626 for (rank_index, engine) in self.ranks.iter().enumerate() {
11627 let _main = engine.gpu.enter_main()?;
11628 if let Some(events) = workspace.phase_events.as_ref() {
11629 events.head[rank_index].record(&engine.stream())?;
11630 }
11631 engine.stream().wait(&workspace.ev_entry)?;
11632 let Nvfp4EpDeviceWorkspace {
11633 input_bf16,
11634 sel,
11635 route_w,
11636 ..
11637 } = &mut *workspace;
11638 engine.nvfp4_ep_stage_inputs(
11639 input_dev,
11640 selected_dev,
11641 route_weights_dev,
11642 &mut input_bf16[rank_index],
11643 &mut sel[rank_index],
11644 &mut route_w[rank_index],
11645 active_input_values,
11646 pairs,
11647 false,
11648 )?;
11649 if let Some(events) = workspace.phase_events.as_ref() {
11650 events.copy_done[rank_index].record(&engine.stream())?;
11651 }
11652 let rank = &experts.ranks[rank_index];
11653 let owner_start = rank.expert_range.start;
11654 let owner_end = rank.expert_range.end;
11655 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
11656 &rank.gate,
11657 &rank.up,
11658 &workspace.sel[rank_index],
11659 &workspace.input_bf16[rank_index],
11660 &mut workspace.gate_out[rank_index],
11661 &mut workspace.up_out[rank_index],
11662 pairs,
11663 experts_per_token,
11664 experts.input_width,
11665 experts.expert_width,
11666 owner_start,
11667 owner_end,
11668 experts.gate_row_bytes,
11669 rank.gate_expert_bytes,
11670 )?;
11671 if let Some(events) = workspace.phase_events.as_ref() {
11672 events.gate_up_done[rank_index].record(&engine.stream())?;
11673 }
11674 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
11675 &workspace.gate_out[rank_index],
11676 &workspace.up_out[rank_index],
11677 &rank.macros_gate,
11678 &rank.macros_up,
11679 &workspace.sel[rank_index],
11680 owner_start,
11681 owner_end,
11682 activation_limit,
11683 &mut workspace.activation_bf16[rank_index],
11684 experts.expert_width,
11685 pairs,
11686 )?;
11687 if let Some(events) = workspace.phase_events.as_ref() {
11688 events.activation_done[rank_index].record(&engine.stream())?;
11689 }
11690 if tokens > 1 && pair_down_enabled {
11691 engine.qmatvec_nvfp4_bf16_ep_down_pairs_raw(
11692 &rank.down,
11693 &workspace.sel[rank_index],
11694 &workspace.activation_bf16[rank_index],
11695 &rank.macros_down,
11696 workspace.slot_rows_raw,
11697 pairs,
11698 experts.expert_width,
11699 experts.input_width,
11700 owner_start,
11701 owner_end,
11702 experts.down_row_bytes,
11703 rank.down_expert_bytes,
11704 )?;
11705 } else {
11706 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
11707 &rank.down,
11708 &workspace.sel[rank_index],
11709 &workspace.activation_bf16[rank_index],
11710 &rank.macros_down,
11711 workspace.slot_rows_raw,
11712 pairs,
11713 experts.expert_width,
11714 experts.input_width,
11715 owner_start,
11716 owner_end,
11717 experts.down_row_bytes,
11718 rank.down_expert_bytes,
11719 )?;
11720 }
11721 if let Some(events) = workspace.phase_events.as_ref() {
11722 events.down_done[rank_index].record(&engine.stream())?;
11723 }
11724 workspace.ev_rank[rank_index].record(&engine.stream())?;
11725 }
11726
11727 if let Some(pre_join) = pre_join.as_mut() {
11728 pre_join()?;
11729 }
11730 let issue_ns_this = started
11731 .as_ref()
11732 .map(|started| started.elapsed().as_nanos() as u64);
11733 let join_started = timing.then(std::time::Instant::now);
11734 let output = {
11735 let _main = e.gpu.enter_main()?;
11736 for event in &workspace.ev_rank {
11737 e.stream().wait(event)?;
11738 }
11739 let mut output = e.uninit(tokens * experts.input_width)?;
11740 e.axpy_rows_seq_tokens_into(
11741 &workspace.slot_rows,
11742 route_weights_dev,
11743 &mut output,
11744 experts.input_width,
11745 experts_per_token,
11746 tokens,
11747 )?;
11748 output
11749 };
11750
11751 if let Some(started) = started {
11752 use std::sync::atomic::Ordering;
11753 e.stream().synchronize()?;
11754 let elapsed = started.elapsed().as_nanos() as u64;
11755 let join_ns_this = join_started
11756 .expect("timing join starts with total timing")
11757 .elapsed()
11758 .as_nanos() as u64;
11759 let mut phase_max_ms = [0.0f32; 5];
11760 if let Some(events) = workspace.phase_events.as_ref() {
11761 for rank_index in 0..world {
11762 let engine = &self.ranks[rank_index];
11763 let _main = engine.gpu.enter_main()?;
11764 phase_max_ms[0] = phase_max_ms[0]
11765 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
11766 phase_max_ms[1] = phase_max_ms[1].max(
11767 events.copy_done[rank_index]
11768 .elapsed_ms(&events.gate_up_done[rank_index])?,
11769 );
11770 phase_max_ms[2] = phase_max_ms[2].max(
11771 events.gate_up_done[rank_index]
11772 .elapsed_ms(&events.activation_done[rank_index])?,
11773 );
11774 phase_max_ms[3] = phase_max_ms[3].max(
11775 events.activation_done[rank_index]
11776 .elapsed_ms(&events.down_done[rank_index])?,
11777 );
11778 phase_max_ms[4] = phase_max_ms[4]
11779 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
11780 }
11781 }
11782 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
11783 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11784 let issue_ns = ISSUE_NS.fetch_add(
11785 issue_ns_this.expect("timing issue starts with total timing"),
11786 Ordering::Relaxed,
11787 ) + issue_ns_this.expect("timing issue starts with total timing");
11788 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
11789 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
11790 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
11791 let activation_ns =
11792 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
11793 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
11794 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
11795 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11796 if calls.is_multiple_of(430) {
11797 eprintln!(
11798 "[nvfp4-ep-device-router-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11799 ns as f64 / 1.0e6,
11800 ns as f64 / calls as f64 / 1.0e3,
11801 );
11802 eprintln!(
11803 "[nvfp4-ep-device-router-phases] calls={calls} issue_us={:.1} \
11804 join_us={:.1} rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
11805 activation_us={:.1} down_us={:.1}",
11806 issue_ns as f64 / calls as f64 / 1.0e3,
11807 join_ns as f64 / calls as f64 / 1.0e3,
11808 rank_span_ns as f64 / calls as f64 / 1.0e3,
11809 copy_ns as f64 / calls as f64 / 1.0e3,
11810 gate_up_ns as f64 / calls as f64 / 1.0e3,
11811 activation_ns as f64 / calls as f64 / 1.0e3,
11812 down_ns as f64 / calls as f64 / 1.0e3,
11813 );
11814 }
11815 }
11816 if graph_enabled
11817 && !timing
11818 && pre_join.is_none()
11819 && tokens <= NVFP4_EP_GRAPH_BATCH_CAP
11820 && workspace.graphs[tokens].is_none()
11821 {
11822 e.stream().synchronize()?;
11823 let graph = self.build_nvfp4_ep_routes_graph(
11824 experts,
11825 e,
11826 workspace,
11827 selected_dev,
11828 route_weights_dev,
11829 tokens,
11830 experts_per_token,
11831 activation_limit,
11832 )?;
11833 workspace.graphs[tokens] = Some(graph);
11834 eprintln!(
11835 "[parallel-ep-graph] captured devices={:?} tokens={tokens} \
11836 experts/token={experts_per_token} input=staged routes=fixed \
11837 device_arithmetic=unchanged performance_claim=false",
11838 self.devices,
11839 );
11840 }
11841 Ok(output)
11842 }
11843
11844 #[allow(clippy::too_many_arguments)]
11845 fn run_routed_experts_nvfp4_w4a8_device_routed(
11846 &self,
11847 experts: &ResidentNvfp4ExpertParallel,
11848 e: &Engine,
11849 input_dev: &crate::CudaSlice<f32>,
11850 selected_dev: &crate::CudaSlice<i32>,
11851 route_weights_dev: &crate::CudaSlice<f32>,
11852 workspace: &mut Nvfp4EpDeviceWorkspace,
11853 tokens: usize,
11854 experts_per_token: usize,
11855 activation_limit: Option<f32>,
11856 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11857 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11858 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11859 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11860 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11861 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11862 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11863 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11864 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11865 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11866 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11867 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11868 let started = timing.then(std::time::Instant::now);
11869 let pairs = tokens
11870 .checked_mul(experts_per_token)
11871 .ok_or("W4A8 device-routed EP route count overflow")?;
11872 let input_values = tokens
11873 .checked_mul(experts.input_width)
11874 .ok_or("W4A8 device-routed EP input size overflow")?;
11875 let scope = parallel_ep_q8_scope()?.unwrap_or(ParallelEpQ8Scope::All);
11876 let gate_up_paired = parallel_ep_q8_gu_paired_enabled(true, Some(scope))?;
11877
11878 {
11879 let _main = e.gpu.enter_main()?;
11880 e.memset_zeros_view(
11881 &mut workspace
11882 .slot_rows
11883 .slice_mut(0..pairs * experts.input_width),
11884 )?;
11885 workspace.ev_entry.record(&e.stream())?;
11886 }
11887 for (rank_index, engine) in self.ranks.iter().enumerate() {
11888 let _main = engine.gpu.enter_main()?;
11889 if let Some(events) = workspace.phase_events.as_ref() {
11890 events.head[rank_index].record(&engine.stream())?;
11891 }
11892 engine.stream().wait(&workspace.ev_entry)?;
11893 let rank = &experts.ranks[rank_index];
11894 let owner_start = rank.expert_range.start;
11895 let owner_end = rank.expert_range.end;
11896 match scope {
11897 ParallelEpQ8Scope::All | ParallelEpQ8Scope::GateUp => {
11898 engine.quantize_q8_1_into(
11899 input_dev,
11900 tokens,
11901 experts.input_width,
11902 &mut workspace.input_q8[rank_index],
11903 &mut workspace.input_q8_scales[rank_index],
11904 )?;
11905 engine.moe_sel_w_mirror(
11906 selected_dev,
11907 route_weights_dev,
11908 &mut workspace.sel[rank_index],
11909 &mut workspace.route_w[rank_index],
11910 pairs,
11911 )?;
11912 if let Some(events) = workspace.phase_events.as_ref() {
11913 events.copy_done[rank_index].record(&engine.stream())?;
11914 }
11915 if gate_up_paired {
11916 engine.qmatvec_nvfp4_q8_ep_paired_slots_into(
11917 &rank.gate,
11918 &rank.up,
11919 &workspace.sel[rank_index],
11920 &workspace.input_q8[rank_index],
11921 &workspace.input_q8_scales[rank_index],
11922 &mut workspace.gate_out[rank_index],
11923 &mut workspace.up_out[rank_index],
11924 pairs,
11925 experts_per_token,
11926 experts.input_width,
11927 experts.expert_width,
11928 owner_start,
11929 owner_end,
11930 experts.gate_row_bytes,
11931 rank.gate_expert_bytes,
11932 )?;
11933 } else {
11934 engine.qmatvec_nvfp4_q8_ep_dual_slots_into(
11935 &rank.gate,
11936 &rank.up,
11937 &workspace.sel[rank_index],
11938 &workspace.input_q8[rank_index],
11939 &workspace.input_q8_scales[rank_index],
11940 &mut workspace.gate_out[rank_index],
11941 &mut workspace.up_out[rank_index],
11942 pairs,
11943 experts_per_token,
11944 experts.input_width,
11945 experts.expert_width,
11946 owner_start,
11947 owner_end,
11948 experts.gate_row_bytes,
11949 rank.gate_expert_bytes,
11950 )?;
11951 }
11952 }
11953 ParallelEpQ8Scope::Down => {
11954 engine.nvfp4_ep_stage_inputs(
11955 input_dev,
11956 selected_dev,
11957 route_weights_dev,
11958 &mut workspace.input_bf16[rank_index],
11959 &mut workspace.sel[rank_index],
11960 &mut workspace.route_w[rank_index],
11961 input_values,
11962 pairs,
11963 false,
11964 )?;
11965 if let Some(events) = workspace.phase_events.as_ref() {
11966 events.copy_done[rank_index].record(&engine.stream())?;
11967 }
11968 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
11969 &rank.gate,
11970 &rank.up,
11971 &workspace.sel[rank_index],
11972 &workspace.input_bf16[rank_index],
11973 &mut workspace.gate_out[rank_index],
11974 &mut workspace.up_out[rank_index],
11975 pairs,
11976 experts_per_token,
11977 experts.input_width,
11978 experts.expert_width,
11979 owner_start,
11980 owner_end,
11981 experts.gate_row_bytes,
11982 rank.gate_expert_bytes,
11983 )?;
11984 }
11985 }
11986 if let Some(events) = workspace.phase_events.as_ref() {
11987 events.gate_up_done[rank_index].record(&engine.stream())?;
11988 }
11989 match scope {
11990 ParallelEpQ8Scope::All | ParallelEpQ8Scope::Down => {
11991 engine.silu_mul_scaled_host_expf_q8_ep_slots_into(
11992 &workspace.gate_out[rank_index],
11993 &workspace.up_out[rank_index],
11994 &rank.macros_gate,
11995 &rank.macros_up,
11996 &workspace.sel[rank_index],
11997 owner_start,
11998 owner_end,
11999 activation_limit,
12000 &mut workspace.activation_q8[rank_index],
12001 &mut workspace.activation_q8_scales[rank_index],
12002 experts.expert_width,
12003 pairs,
12004 )?;
12005 if let Some(events) = workspace.phase_events.as_ref() {
12006 events.activation_done[rank_index].record(&engine.stream())?;
12007 }
12008 engine.qmatvec_nvfp4_q8_ep_down_slots_raw(
12009 &rank.down,
12010 &workspace.sel[rank_index],
12011 &workspace.activation_q8[rank_index],
12012 &workspace.activation_q8_scales[rank_index],
12013 &rank.macros_down,
12014 workspace.slot_rows_raw,
12015 pairs,
12016 experts.expert_width,
12017 experts.input_width,
12018 owner_start,
12019 owner_end,
12020 experts.down_row_bytes,
12021 rank.down_expert_bytes,
12022 )?;
12023 }
12024 ParallelEpQ8Scope::GateUp => {
12025 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12026 &workspace.gate_out[rank_index],
12027 &workspace.up_out[rank_index],
12028 &rank.macros_gate,
12029 &rank.macros_up,
12030 &workspace.sel[rank_index],
12031 owner_start,
12032 owner_end,
12033 activation_limit,
12034 &mut workspace.activation_bf16[rank_index],
12035 experts.expert_width,
12036 pairs,
12037 )?;
12038 if let Some(events) = workspace.phase_events.as_ref() {
12039 events.activation_done[rank_index].record(&engine.stream())?;
12040 }
12041 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12042 &rank.down,
12043 &workspace.sel[rank_index],
12044 &workspace.activation_bf16[rank_index],
12045 &rank.macros_down,
12046 workspace.slot_rows_raw,
12047 pairs,
12048 experts.expert_width,
12049 experts.input_width,
12050 owner_start,
12051 owner_end,
12052 experts.down_row_bytes,
12053 rank.down_expert_bytes,
12054 )?;
12055 }
12056 }
12057 if let Some(events) = workspace.phase_events.as_ref() {
12058 events.down_done[rank_index].record(&engine.stream())?;
12059 }
12060 workspace.ev_rank[rank_index].record(&engine.stream())?;
12061 }
12062
12063 if let Some(pre_join) = pre_join.as_mut() {
12064 pre_join()?;
12065 }
12066 let issue_ns_this = started
12067 .as_ref()
12068 .map(|started| started.elapsed().as_nanos() as u64);
12069 let join_started = timing.then(std::time::Instant::now);
12070 let output = {
12071 let _main = e.gpu.enter_main()?;
12072 for event in &workspace.ev_rank {
12073 e.stream().wait(event)?;
12074 }
12075 let mut output = e.uninit(input_values)?;
12076 e.axpy_rows_seq_tokens_into(
12077 &workspace.slot_rows,
12078 route_weights_dev,
12079 &mut output,
12080 experts.input_width,
12081 experts_per_token,
12082 tokens,
12083 )?;
12084 output
12085 };
12086 if let Some(started) = started {
12087 use std::sync::atomic::Ordering;
12088 e.stream().synchronize()?;
12089 let elapsed = started.elapsed().as_nanos() as u64;
12090 let join_ns_this = join_started
12091 .expect("timing join starts with total timing")
12092 .elapsed()
12093 .as_nanos() as u64;
12094 let mut phase_max_ms = [0.0f32; 5];
12095 if let Some(events) = workspace.phase_events.as_ref() {
12096 for rank_index in 0..self.ranks.len() {
12097 let engine = &self.ranks[rank_index];
12098 let _main = engine.gpu.enter_main()?;
12099 phase_max_ms[0] = phase_max_ms[0]
12100 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12101 phase_max_ms[1] = phase_max_ms[1].max(
12102 events.copy_done[rank_index]
12103 .elapsed_ms(&events.gate_up_done[rank_index])?,
12104 );
12105 phase_max_ms[2] = phase_max_ms[2].max(
12106 events.gate_up_done[rank_index]
12107 .elapsed_ms(&events.activation_done[rank_index])?,
12108 );
12109 phase_max_ms[3] = phase_max_ms[3].max(
12110 events.activation_done[rank_index]
12111 .elapsed_ms(&events.down_done[rank_index])?,
12112 );
12113 phase_max_ms[4] = phase_max_ms[4]
12114 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12115 }
12116 }
12117 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12118 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12119 let issue_ns = ISSUE_NS.fetch_add(
12120 issue_ns_this.expect("timing issue starts with total timing"),
12121 Ordering::Relaxed,
12122 ) + issue_ns_this.expect("timing issue starts with total timing");
12123 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12124 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12125 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12126 let activation_ns =
12127 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12128 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12129 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12130 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12131 if calls.is_multiple_of(430) {
12132 eprintln!(
12133 "[nvfp4-ep-q8-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12134 ns as f64 / 1.0e6,
12135 ns as f64 / calls as f64 / 1.0e3,
12136 );
12137 eprintln!(
12138 "[nvfp4-ep-q8-phases] calls={calls} issue_us={:.1} join_us={:.1} \
12139 rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12140 activation_us={:.1} down_us={:.1}",
12141 issue_ns as f64 / calls as f64 / 1.0e3,
12142 join_ns as f64 / calls as f64 / 1.0e3,
12143 rank_span_ns as f64 / calls as f64 / 1.0e3,
12144 copy_ns as f64 / calls as f64 / 1.0e3,
12145 gate_up_ns as f64 / calls as f64 / 1.0e3,
12146 activation_ns as f64 / calls as f64 / 1.0e3,
12147 down_ns as f64 / calls as f64 / 1.0e3,
12148 );
12149 }
12150 }
12151 static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
12152 if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
12153 let (expert_input, post_activation, numeric_class) = match scope {
12154 ParallelEpQ8Scope::All => ("q8_1", "q8_1", "w4a8-internal"),
12155 ParallelEpQ8Scope::GateUp => ("q8_1", "bf16", "w4a8-gate-up-internal"),
12156 ParallelEpQ8Scope::Down => ("bf16", "q8_1", "w4a8-down-internal"),
12157 };
12158 eprintln!(
12159 "[parallel-ep-q8] devices={:?} tokens={tokens} scope={} \
12160 expert_input={expert_input} post_activation={post_activation} \
12161 gate_up_schedule={} \
12162 external_boundary=bf16 numeric_class={numeric_class} \
12163 host_expf=true accumulation=token-slot-order performance_claim=false",
12164 self.devices,
12165 scope.label(),
12166 if gate_up_paired {
12167 "paired-cta"
12168 } else {
12169 "separate-cta"
12170 },
12171 );
12172 }
12173 Ok(output)
12174 }
12175
12176 #[allow(clippy::too_many_arguments)]
12177 fn build_nvfp4_ep_routes_graph(
12178 &self,
12179 experts: &ResidentNvfp4ExpertParallel,
12180 e: &Engine,
12181 workspace: &mut Nvfp4EpDeviceWorkspace,
12182 selected_dev: &crate::CudaSlice<i32>,
12183 route_weights_dev: &crate::CudaSlice<f32>,
12184 tokens: usize,
12185 experts_per_token: usize,
12186 activation_limit: Option<f32>,
12187 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12188 use cudarc::driver::DevicePtr;
12189 use cudarc::driver::sys;
12190
12191 fn cu_try(result: sys::CUresult, context: &str) -> Result<(), Box<dyn std::error::Error>> {
12192 if result == sys::CUresult::CUDA_SUCCESS {
12193 Ok(())
12194 } else {
12195 Err(format!("{context}: {result:?}").into())
12196 }
12197 }
12198
12199 let world = self.ranks.len();
12200 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
12201 return Err(format!(
12202 "W4A16 EP graph world {world} != expert ranks {}",
12203 experts.ranks.len()
12204 )
12205 .into());
12206 }
12207 let width = experts.input_width;
12208 if !(1..=NVFP4_EP_GRAPH_BATCH_CAP).contains(&tokens) {
12209 return Err(format!(
12210 "W4A16 EP graph tokens {tokens} outside 1..={NVFP4_EP_GRAPH_BATCH_CAP}"
12211 )
12212 .into());
12213 }
12214 let pairs = tokens
12215 .checked_mul(experts_per_token)
12216 .ok_or("W4A16 EP graph pair count overflow")?;
12217 let input_values = tokens
12218 .checked_mul(width)
12219 .ok_or("W4A16 EP graph input size overflow")?;
12220 let root_stream = e.stream();
12221 let (input_ptr, _input_guard) = workspace.graph_input.device_ptr(&root_stream);
12222 let (selected_ptr, _selected_guard) = selected_dev.device_ptr(&root_stream);
12223 let (weights_ptr, _weights_guard) = route_weights_dev.device_ptr(&root_stream);
12224 let route_ptrs = (selected_ptr, weights_ptr);
12225
12226 let mut children = Vec::with_capacity(world + 1);
12227 for rank_index in 0..world {
12228 let engine = &self.ranks[rank_index];
12229 let rank = &experts.ranks[rank_index];
12230 let owner_start = rank.expert_range.start;
12231 let owner_end = rank.expert_range.end;
12232 let _main = engine.gpu.enter_main()?;
12233 let (child, _retained) = engine.capture_graph_retained(|_| {
12234 engine.nvfp4_ep_stage_inputs_raw(
12235 input_ptr,
12236 selected_ptr,
12237 weights_ptr,
12238 &mut workspace.input_bf16[rank_index],
12239 &mut workspace.sel[rank_index],
12240 &mut workspace.route_w[rank_index],
12241 input_values,
12242 pairs,
12243 false,
12244 )?;
12245 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
12246 &rank.gate,
12247 &rank.up,
12248 &workspace.sel[rank_index],
12249 &workspace.input_bf16[rank_index],
12250 &mut workspace.gate_out[rank_index],
12251 &mut workspace.up_out[rank_index],
12252 pairs,
12253 experts_per_token,
12254 width,
12255 experts.expert_width,
12256 owner_start,
12257 owner_end,
12258 experts.gate_row_bytes,
12259 rank.gate_expert_bytes,
12260 )?;
12261 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12262 &workspace.gate_out[rank_index],
12263 &workspace.up_out[rank_index],
12264 &rank.macros_gate,
12265 &rank.macros_up,
12266 &workspace.sel[rank_index],
12267 owner_start,
12268 owner_end,
12269 activation_limit,
12270 &mut workspace.activation_bf16[rank_index],
12271 experts.expert_width,
12272 pairs,
12273 )?;
12274 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12275 &rank.down,
12276 &workspace.sel[rank_index],
12277 &workspace.activation_bf16[rank_index],
12278 &rank.macros_down,
12279 workspace.slot_rows_raw,
12280 pairs,
12281 experts.expert_width,
12282 width,
12283 owner_start,
12284 owner_end,
12285 experts.down_row_bytes,
12286 rank.down_expert_bytes,
12287 )?;
12288 Ok(())
12289 })?;
12290 children.push(child);
12291 }
12292
12293 {
12294 let _main = e.gpu.enter_main()?;
12295 let (child, _retained) = e.capture_graph_retained(|_| {
12296 e.axpy_rows_seq_tokens_into(
12297 &workspace.slot_rows,
12298 route_weights_dev,
12299 &mut workspace.graph_output,
12300 width,
12301 experts_per_token,
12302 tokens,
12303 )
12304 })?;
12305 children.push(child);
12306 }
12307
12308 let mut parent: sys::CUgraph = std::ptr::null_mut();
12309 unsafe {
12310 cu_try(sys::cuGraphCreate(&mut parent, 0), "W4A16 EP cuGraphCreate")?;
12311 }
12312 let mut rank_nodes = Vec::with_capacity(world);
12313 for (rank_index, child) in children.iter().take(world).enumerate() {
12314 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12315 unsafe {
12316 cu_try(
12317 sys::cuGraphAddChildGraphNode(
12318 &mut node,
12319 parent,
12320 std::ptr::null(),
12321 0,
12322 child.cu_graph(),
12323 ),
12324 &format!("W4A16 EP graph rank {rank_index}"),
12325 )?;
12326 }
12327 rank_nodes.push(node);
12328 }
12329 let mut combine_node: sys::CUgraphNode = std::ptr::null_mut();
12330 unsafe {
12331 cu_try(
12332 sys::cuGraphAddChildGraphNode(
12333 &mut combine_node,
12334 parent,
12335 rank_nodes.as_ptr(),
12336 rank_nodes.len(),
12337 children[world].cu_graph(),
12338 ),
12339 "W4A16 EP graph combine",
12340 )?;
12341 }
12342 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12343 unsafe {
12344 cu_try(
12345 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12346 "W4A16 EP graph instantiate",
12347 )?;
12348 }
12349 workspace.graph_routes = Some(route_ptrs);
12350 Ok(RoutesGraph {
12351 exec,
12352 parent,
12353 _children: children,
12354 })
12355 }
12356
12357 pub fn run_tensor_parallel_routes_nvfp4_device(
12371 &self,
12372 experts: &ResidentNvfp4TensorParallel,
12373 input: &[f32],
12374 selected: &[usize],
12375 route_weights: &[f32],
12376 experts_per_token: usize,
12377 activation_limit: Option<f32>,
12378 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12379 validate_activations(input, 1, experts.input_width)?;
12380 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12381 return Err(format!(
12382 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
12383 selected.len(),
12384 route_weights.len(),
12385 )
12386 .into());
12387 }
12388 if !route_weights.iter().all(|weight| weight.is_finite()) {
12389 return Err("NVFP4 device route weights contain a non-finite value".into());
12390 }
12391 let world = self.ranks.len();
12392 if world != NVFP4_CANONICAL_ROW_SHARDS {
12393 return Err(format!(
12394 "NVFP4 device routes require world == canonical shard grid \
12395 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12396 )
12397 .into());
12398 }
12399 let local_out = if experts.ep2 {
12400 experts.expert_width
12401 } else {
12402 experts.expert_width / world
12403 };
12404
12405 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12409 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12410 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12411 let started = timing.then(std::time::Instant::now);
12412
12413 let n_sel = experts_per_token;
12414 let mut workspace_guard = experts
12415 .device_workspace
12416 .lock()
12417 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12418 if workspace_guard.is_none() {
12419 let mut gate_out = Vec::with_capacity(world);
12420 let mut up_out = Vec::with_capacity(world);
12421 let mut act_q = Vec::with_capacity(world);
12422 let mut act_d = Vec::with_capacity(world);
12423 let mut sel = Vec::with_capacity(world);
12424 let mut partial = Vec::with_capacity(world);
12425 let mut accumulator = Vec::with_capacity(world);
12426 let mut combine_w = Vec::with_capacity(world);
12427 let mut route_w = Vec::with_capacity(world);
12428 let mut in_q = Vec::with_capacity(world);
12429 let mut in_d = Vec::with_capacity(world);
12430 let mut input = Vec::with_capacity(world);
12431 let mut ev_rank = Vec::with_capacity(world);
12432 let moe_direct = moe_direct_on();
12433 for (rank, engine) in self.ranks.iter().enumerate() {
12434 let _main = engine.gpu.enter_main()?;
12435 gate_out.push(engine.uninit(n_sel * local_out)?);
12436 up_out.push(engine.uninit(n_sel * local_out)?);
12437 act_q.push(engine.uninit_i8(n_sel * local_out)?);
12438 act_d.push(engine.uninit(n_sel * local_out / 32)?);
12439 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
12440 partial.push(engine.uninit(n_sel * experts.input_width)?);
12441 if moe_direct && rank != 0 {
12443 let root = &self.ranks[0];
12444 let _root_main = root.gpu.enter_main()?;
12445 accumulator.push(root.zeros(experts.input_width)?);
12446 } else {
12447 accumulator.push(engine.zeros(experts.input_width)?);
12448 }
12449 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12450 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12451 in_q.push(engine.uninit_i8(experts.input_width)?);
12452 in_d.push(engine.uninit(experts.input_width / 32)?);
12453 input.push(engine.uninit(experts.input_width)?);
12454 ev_rank.push(engine.ctx().new_event(None)?);
12455 }
12456 let root = &self.ranks[0];
12457 let _main = root.gpu.enter_main()?;
12458 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
12459 prestaged: false,
12460 rank1_routed: false,
12461 ev_input: None,
12462 fence_flags_raw: 0,
12463 fence_ticket: 0,
12464 gate_out,
12465 up_out,
12466 act_q,
12467 act_d,
12468 sel,
12469 partial,
12470 accumulator,
12471 combine_w,
12472 route_w,
12473 in_q,
12474 in_d,
12475 dev_route_e: None,
12476 in_stage_e: None,
12477 out_stage_e: None,
12478 routes_graph: None,
12479 raw_dev_route_e: None,
12480 raw_combine: None,
12481 raw_input: Vec::new(),
12482 raw_sel: Vec::new(),
12483 raw_route_w: Vec::new(),
12484 remote: root.uninit(experts.input_width)?,
12485 combined: root.uninit(experts.input_width)?,
12486 n_sel,
12487 input,
12488 ev_rank,
12489 ev_done: Some(root.ctx().new_event(None)?),
12490 ev_entry: None,
12491 });
12492 }
12493 let workspace = workspace_guard
12494 .as_mut()
12495 .expect("NVFP4 device routes workspace initialized above");
12496 if experts.ep2 {
12499 return Ok(vec![0.0f32; experts.input_width]);
12500 }
12501 if workspace.n_sel != n_sel {
12502 return Err(format!(
12503 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12504 workspace.n_sel
12505 )
12506 .into());
12507 }
12508 for &expert in selected {
12509 if expert >= experts.expert_count {
12510 return Err(format!(
12511 "NVFP4 device selected expert {expert} outside 0..{}",
12512 experts.expert_count
12513 )
12514 .into());
12515 }
12516 }
12517 let sel_i32 = selected
12518 .iter()
12519 .map(|&expert| expert as i32)
12520 .collect::<Vec<_>>();
12521
12522 for (rank_index, engine) in self.ranks.iter().enumerate() {
12529 let _main = engine.gpu.enter_main()?;
12530 let device_input = engine.htod(input)?;
12531 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
12532 engine.quantize_q8_1_into(
12533 &device_input,
12534 1,
12535 experts.input_width,
12536 &mut in_q[rank_index],
12537 &mut in_d[rank_index],
12538 )?;
12539 }
12541 self.nvfp4_routes_batched_sweeps(
12542 experts,
12543 workspace,
12544 selected,
12545 route_weights,
12546 &sel_i32,
12547 local_out,
12548 n_sel,
12549 activation_limit,
12550 false,
12551 )?;
12552
12553 let root = &self.ranks[0];
12556 for engine in &self.ranks[1..] {
12557 let _main = engine.gpu.enter_main()?;
12558 engine.stream().synchronize()?;
12559 }
12560 let _main = root.gpu.enter_main()?;
12561 root.stream()
12562 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12563 root.add(
12564 &workspace.accumulator[0],
12565 &workspace.remote,
12566 &mut workspace.combined,
12567 experts.input_width,
12568 )?;
12569 let output = root.dtoh(&workspace.combined)?;
12570 if let Some(started) = started {
12571 use std::sync::atomic::Ordering;
12572 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12573 + started.elapsed().as_nanos() as u64;
12574 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12575 if calls.is_multiple_of(430) {
12576 eprintln!(
12577 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12578 ns as f64 / 1.0e6,
12579 ns as f64 / calls as f64 / 1.0e3,
12580 );
12581 }
12582 }
12583 Ok(output)
12584 }
12585
12586 #[allow(clippy::too_many_arguments)]
12591 fn nvfp4_routes_batched_sweeps(
12592 &self,
12593 experts: &ResidentNvfp4TensorParallel,
12594 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12595 selected: &[usize],
12596 route_weights: &[f32],
12597 sel_i32: &[i32],
12598 local_out: usize,
12599 n_sel: usize,
12600 activation_limit: Option<f32>,
12601 device_routed: bool,
12602 ) -> Result<(), Box<dyn std::error::Error>> {
12603 for rank_index in 0..self.ranks.len() {
12604 self.nvfp4_routes_batched_sweeps_rank(
12605 experts,
12606 workspace,
12607 selected,
12608 route_weights,
12609 sel_i32,
12610 local_out,
12611 n_sel,
12612 activation_limit,
12613 device_routed,
12614 rank_index,
12615 )?;
12616 }
12617 Ok(())
12618 }
12619
12620 #[allow(clippy::too_many_arguments)]
12623 fn nvfp4_routes_batched_sweeps_rank(
12624 &self,
12625 experts: &ResidentNvfp4TensorParallel,
12626 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12627 selected: &[usize],
12628 route_weights: &[f32],
12629 sel_i32: &[i32],
12630 local_out: usize,
12631 n_sel: usize,
12632 activation_limit: Option<f32>,
12633 device_routed: bool,
12634 rank_index: usize,
12635 ) -> Result<(), Box<dyn std::error::Error>> {
12636 {
12637 let engine = &self.ranks[rank_index];
12638 let _main = engine.gpu.enter_main()?;
12639 if experts.ep2 {
12644 if !device_routed {
12645 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
12646 }
12647 let gate_bank = &experts.gate[rank_index];
12648 let up_bank = &experts.up[rank_index];
12649 if gate_bank.local_out != experts.expert_width
12650 || gate_bank.expert_bytes != up_bank.expert_bytes
12651 {
12652 return Err("NVFP4 EP2 bank geometry drifted".into());
12653 }
12654 {
12655 let Nvfp4DeviceRoutesWorkspace {
12656 sel,
12657 gate_out,
12658 up_out,
12659 in_q,
12660 in_d,
12661 ..
12662 } = &mut *workspace;
12663 engine.qmatvec_nvfp4_sel_gu_ep_into(
12664 &gate_bank.bank,
12665 &up_bank.bank,
12666 &sel[rank_index],
12667 &in_q[rank_index],
12668 &in_d[rank_index],
12669 &mut gate_out[rank_index],
12670 &mut up_out[rank_index],
12671 n_sel,
12672 gate_bank.in_features,
12673 gate_bank.local_out,
12674 gate_bank.row_bytes,
12675 gate_bank.expert_bytes,
12676 rank_index,
12677 )?;
12678 }
12679 {
12680 let Nvfp4DeviceRoutesWorkspace {
12681 gate_out,
12682 up_out,
12683 sel,
12684 act_q,
12685 act_d,
12686 ..
12687 } = &mut *workspace;
12688 engine.silu_mul_scaled_q8_1_sel_ep_into(
12689 &gate_out[rank_index],
12690 &up_out[rank_index],
12691 &experts.macros_gate_dev[rank_index],
12692 &experts.macros_up_dev[rank_index],
12693 &sel[rank_index],
12694 activation_limit,
12695 &mut act_q[rank_index],
12696 &mut act_d[rank_index],
12697 local_out,
12698 n_sel,
12699 rank_index,
12700 )?;
12701 }
12702 let shard = &experts.down[rank_index];
12703 if shard.device_rank != rank_index || shard.local_in != local_out {
12704 return Err("NVFP4 EP2 down bank placement drifted".into());
12705 }
12706 {
12707 let Nvfp4DeviceRoutesWorkspace {
12708 sel,
12709 act_q,
12710 act_d,
12711 route_w,
12712 accumulator,
12713 ..
12714 } = &mut *workspace;
12715 engine.qmatvec_nvfp4_sel_down8_ep_into(
12716 &shard.bank,
12717 &sel[rank_index],
12718 &act_q[rank_index],
12719 &act_d[rank_index],
12720 &route_w[rank_index],
12721 &experts.macros_down_dev[rank_index],
12722 &mut accumulator[rank_index],
12723 n_sel,
12724 shard.local_in,
12725 shard.out_features,
12726 shard.row_bytes,
12727 shard.expert_bytes,
12728 local_out,
12729 local_out / 32,
12730 rank_index,
12731 )?;
12732 }
12733 return Ok(());
12734 }
12735 if !device_routed {
12736 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
12737 let folded = (0..n_sel)
12740 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
12741 .collect::<Vec<_>>();
12742 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
12743 engine.stream().memcpy_htod(&folded, &mut view)?;
12744 }
12745 let gate_bank = &experts.gate[rank_index];
12746 let up_bank = &experts.up[rank_index];
12747 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
12748 let gu_fused = sel_gu_fused_on()
12756 && gate_bank.slot_major
12757 && up_bank.slot_major
12758 && gate_bank.in_features == up_bank.in_features
12759 && gate_bank.local_out == up_bank.local_out
12760 && gate_bank.row_bytes == up_bank.row_bytes
12761 && gate_bank.expert_bytes == up_bank.expert_bytes;
12762 {
12768 static SEEN_GU: std::sync::Mutex<Vec<(bool, bool, bool)>> =
12769 std::sync::Mutex::new(Vec::new());
12770 let combo = (gu_fused, sel_gu_fused_on(), gate_bank.slot_major);
12771 let mut seen = SEEN_GU.lock().unwrap();
12772 if !seen.contains(&combo) {
12773 seen.push(combo);
12774 eprintln!(
12775 "[nvfp4-sweep] gu_fused={} door={} slot_major={} geometry_match={} \
12776 in_f={} out_f={} n_sel={n_sel}",
12777 gu_fused,
12778 sel_gu_fused_on(),
12779 gate_bank.slot_major,
12780 gate_bank.in_features == up_bank.in_features
12781 && gate_bank.local_out == up_bank.local_out
12782 && gate_bank.row_bytes == up_bank.row_bytes
12783 && gate_bank.expert_bytes == up_bank.expert_bytes,
12784 gate_bank.in_features,
12785 gate_bank.local_out
12786 );
12787 }
12788 }
12789 if gu_fused {
12790 let Nvfp4DeviceRoutesWorkspace {
12791 sel,
12792 gate_out,
12793 up_out,
12794 in_q,
12795 in_d,
12796 ..
12797 } = &mut *workspace;
12798 engine.qmatvec_nvfp4_sel_gu_into(
12799 &gate_bank.bank,
12800 &up_bank.bank,
12801 &sel[rank_index],
12802 &in_q[rank_index],
12803 &in_d[rank_index],
12804 &mut gate_out[rank_index],
12805 &mut up_out[rank_index],
12806 n_sel,
12807 gate_bank.in_features,
12808 gate_bank.local_out,
12809 gate_bank.row_bytes,
12810 gate_bank.expert_bytes,
12811 gate_bank.slot_major,
12812 )?;
12813 } else {
12814 engine.qmatvec_nvfp4_sel_into(
12815 &gate_bank.bank,
12816 &workspace.sel[rank_index],
12817 aq,
12818 ad,
12819 &mut workspace.gate_out[rank_index],
12820 n_sel,
12821 gate_bank.in_features,
12822 gate_bank.local_out,
12823 gate_bank.row_bytes,
12824 gate_bank.expert_bytes,
12825 0,
12826 0,
12827 gate_bank.slot_major,
12828 )?;
12829 engine.qmatvec_nvfp4_sel_into(
12830 &up_bank.bank,
12831 &workspace.sel[rank_index],
12832 aq,
12833 ad,
12834 &mut workspace.up_out[rank_index],
12835 n_sel,
12836 up_bank.in_features,
12837 up_bank.local_out,
12838 up_bank.row_bytes,
12839 up_bank.expert_bytes,
12840 0,
12841 0,
12842 up_bank.slot_major,
12843 )?;
12844 }
12845 {
12849 let Nvfp4DeviceRoutesWorkspace {
12850 gate_out,
12851 up_out,
12852 sel,
12853 act_q,
12854 act_d,
12855 ..
12856 } = &mut *workspace;
12857 engine.silu_mul_scaled_q8_1_sel_into(
12858 &gate_out[rank_index],
12859 &up_out[rank_index],
12860 &experts.macros_gate_dev[rank_index],
12861 &experts.macros_up_dev[rank_index],
12862 &sel[rank_index],
12863 activation_limit,
12864 &mut act_q[rank_index],
12865 &mut act_d[rank_index],
12866 local_out,
12867 n_sel,
12868 )?;
12869 }
12870 let shard = &experts.down[rank_index];
12871 if shard.device_rank != rank_index || shard.local_in != local_out {
12872 return Err(
12873 "NVFP4 device routes: down canonical shard placement drifted from \
12874 the gate/up column split"
12875 .into(),
12876 );
12877 }
12878 let down8 =
12886 device_routed && sel_down8_on() && shard.slot_major && (shard.local_in >> 5) <= 32;
12887 {
12892 static SEEN_D8: std::sync::Mutex<Vec<(bool, bool, bool, bool)>> =
12893 std::sync::Mutex::new(Vec::new());
12894 let combo = (down8, sel_down8_on(), device_routed, shard.slot_major);
12895 let mut seen = SEEN_D8.lock().unwrap();
12896 if !seen.contains(&combo) {
12897 seen.push(combo);
12898 eprintln!(
12904 "[nvfp4-sweep] down8={} door={} door_source={} device_routed={} \
12905 slot_major={} nsb={} in_class={} n_sel={n_sel}",
12906 down8,
12907 sel_down8_on(),
12908 sel_down8_source().1,
12909 device_routed,
12910 shard.slot_major,
12911 shard.local_in >> 5,
12912 (shard.local_in >> 5) <= 32
12913 );
12914 }
12915 }
12916 if down8 {
12917 let Nvfp4DeviceRoutesWorkspace {
12918 sel,
12919 act_q,
12920 act_d,
12921 route_w,
12922 accumulator,
12923 ..
12924 } = &mut *workspace;
12925 engine.qmatvec_nvfp4_sel_down8_into(
12926 &shard.bank,
12927 &sel[rank_index],
12928 &act_q[rank_index],
12929 &act_d[rank_index],
12930 &route_w[rank_index],
12931 &experts.macros_down_dev[rank_index],
12932 &mut accumulator[rank_index],
12933 n_sel,
12934 shard.local_in,
12935 shard.out_features,
12936 shard.row_bytes,
12937 shard.expert_bytes,
12938 local_out,
12939 local_out / 32,
12940 shard.slot_major,
12941 )?;
12942 } else {
12943 let Nvfp4DeviceRoutesWorkspace {
12944 sel,
12945 act_q,
12946 act_d,
12947 partial,
12948 ..
12949 } = &mut *workspace;
12950 engine.qmatvec_nvfp4_sel_into(
12951 &shard.bank,
12952 &sel[rank_index],
12953 &act_q[rank_index],
12954 &act_d[rank_index],
12955 &mut partial[rank_index],
12956 n_sel,
12957 shard.local_in,
12958 shard.out_features,
12959 shard.row_bytes,
12960 shard.expert_bytes,
12961 local_out,
12962 local_out / 32,
12963 shard.slot_major,
12964 )?;
12965 }
12966 if !down8 {
12971 let Nvfp4DeviceRoutesWorkspace {
12972 partial,
12973 combine_w,
12974 route_w,
12975 sel,
12976 accumulator,
12977 ..
12978 } = &mut *workspace;
12979 if device_routed {
12980 engine.axpy_rows_seq_md_into(
12981 &partial[rank_index],
12982 &route_w[rank_index],
12983 &experts.macros_down_dev[rank_index],
12984 &sel[rank_index],
12985 &mut accumulator[rank_index],
12986 experts.input_width,
12987 n_sel,
12988 )?;
12989 } else {
12990 engine.axpy_rows_seq_into(
12991 &partial[rank_index],
12992 &combine_w[rank_index],
12993 &mut accumulator[rank_index],
12994 experts.input_width,
12995 n_sel,
12996 )?;
12997 }
12998 }
12999 }
13000 Ok(())
13001 }
13002
13003 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_io(
13012 &self,
13013 experts: &ResidentNvfp4TensorParallel,
13014 e: &Engine,
13015 input_dev: &crate::CudaSlice<f32>,
13016 selected: &[usize],
13017 route_weights: &[f32],
13018 experts_per_token: usize,
13019 activation_limit: Option<f32>,
13020 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13021 if input_dev.len() != experts.input_width {
13022 return Err(format!(
13023 "NVFP4 device-io routes input {} != width {}",
13024 input_dev.len(),
13025 experts.input_width
13026 )
13027 .into());
13028 }
13029 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
13030 return Err(format!(
13031 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
13032 selected.len(),
13033 route_weights.len(),
13034 )
13035 .into());
13036 }
13037 if !route_weights.iter().all(|weight| weight.is_finite()) {
13038 return Err("NVFP4 device route weights contain a non-finite value".into());
13039 }
13040 let world = self.ranks.len();
13041 if world != NVFP4_CANONICAL_ROW_SHARDS {
13042 return Err(format!(
13043 "NVFP4 device routes require world == canonical shard grid \
13044 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
13045 )
13046 .into());
13047 }
13048 let local_out = experts.expert_width / world;
13049 let n_sel = experts_per_token;
13050
13051 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13052 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13053 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
13054 let started = timing.then(std::time::Instant::now);
13055
13056 let mut workspace_guard = experts
13057 .device_workspace
13058 .lock()
13059 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13060 if workspace_guard.is_none() {
13061 drop(workspace_guard);
13062 let zero = vec![0.0f32; experts.input_width];
13065 let zero_sel = vec![0usize; n_sel];
13066 let zero_w = vec![0.0f32; n_sel];
13067 let _ = self.run_tensor_parallel_routes_nvfp4_device(
13068 experts,
13069 &zero,
13070 &zero_sel,
13071 &zero_w,
13072 n_sel,
13073 activation_limit,
13074 )?;
13075 workspace_guard = experts
13076 .device_workspace
13077 .lock()
13078 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13079 }
13080 let workspace = workspace_guard
13081 .as_mut()
13082 .expect("NVFP4 device routes workspace initialized above");
13083 if workspace.n_sel != n_sel {
13084 return Err(format!(
13085 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
13086 workspace.n_sel
13087 )
13088 .into());
13089 }
13090 for &expert in selected {
13091 if expert >= experts.expert_count {
13092 return Err(format!(
13093 "NVFP4 device selected expert {expert} outside 0..{}",
13094 experts.expert_count
13095 )
13096 .into());
13097 }
13098 }
13099 let sel_i32 = selected
13100 .iter()
13101 .map(|&expert| expert as i32)
13102 .collect::<Vec<_>>();
13103
13104 if let Some((_, device)) = workspace.ev_entry.as_ref() {
13108 if *device != e.ctx().ordinal() {
13109 return Err("NVFP4 device-io routes engine changed".into());
13110 }
13111 } else {
13112 let _main = e.gpu.enter_main()?;
13113 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13114 }
13115 {
13116 let _main = e.gpu.enter_main()?;
13117 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13118 ev_entry.record(&e.stream())?;
13119 }
13120 for (rank_index, engine) in self.ranks.iter().enumerate() {
13121 let _main = engine.gpu.enter_main()?;
13122 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13123 engine.stream().wait(ev_entry)?;
13124 {
13125 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13126 engine
13127 .stream()
13128 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13129 }
13130 {
13131 let Nvfp4DeviceRoutesWorkspace {
13132 input, in_q, in_d, ..
13133 } = &mut *workspace;
13134 engine.quantize_q8_1_into(
13135 &input[rank_index],
13136 1,
13137 experts.input_width,
13138 &mut in_q[rank_index],
13139 &mut in_d[rank_index],
13140 )?;
13141 }
13142 }
13143 self.nvfp4_routes_batched_sweeps(
13144 experts,
13145 workspace,
13146 selected,
13147 route_weights,
13148 &sel_i32,
13149 local_out,
13150 n_sel,
13151 activation_limit,
13152 false,
13153 )?;
13154
13155 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
13161 let _main = engine.gpu.enter_main()?;
13162 workspace.ev_rank[rank_index].record(&engine.stream())?;
13163 }
13164 if moe_direct_on() && self.ranks.len() == 2 {
13165 {
13172 let root = &self.ranks[0];
13173 let _main = root.gpu.enter_main()?;
13174 workspace
13175 .ev_done
13176 .as_ref()
13177 .expect("device routes done event")
13178 .record(&root.stream())?;
13179 }
13180 let _main = e.gpu.enter_main()?;
13181 e.stream().wait(
13182 workspace
13183 .ev_done
13184 .as_ref()
13185 .expect("device routes done event"),
13186 )?;
13187 for ev in workspace.ev_rank.iter().skip(1) {
13188 e.stream().wait(ev)?;
13189 }
13190 let mut output = e.uninit(experts.input_width)?;
13191 e.add(
13192 &workspace.accumulator[0],
13193 &workspace.accumulator[1],
13194 &mut output,
13195 experts.input_width,
13196 )?;
13197 let output = output;
13198 if let Some(started) = started {
13199 use std::sync::atomic::Ordering;
13200 let ns = TIMING_NS
13201 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13202 + started.elapsed().as_nanos() as u64;
13203 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13204 if calls.is_multiple_of(430) {
13205 eprintln!(
13206 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13207 ns as f64 / 1.0e6,
13208 ns as f64 / calls as f64 / 1.0e3,
13209 );
13210 }
13211 }
13212 return Ok(output);
13213 }
13214 {
13215 let root = &self.ranks[0];
13216 let _main = root.gpu.enter_main()?;
13217 for ev in workspace.ev_rank.iter().skip(1) {
13218 root.stream().wait(ev)?;
13219 }
13220 root.stream()
13221 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
13222 {
13223 let Nvfp4DeviceRoutesWorkspace {
13224 accumulator,
13225 remote,
13226 combined,
13227 ..
13228 } = &mut *workspace;
13229 root.add(&accumulator[0], remote, combined, experts.input_width)?;
13230 }
13231 workspace
13232 .ev_done
13233 .as_ref()
13234 .expect("device routes done event")
13235 .record(&root.stream())?;
13236 }
13237 let output = {
13238 let _main = e.gpu.enter_main()?;
13239 e.stream().wait(
13240 workspace
13241 .ev_done
13242 .as_ref()
13243 .expect("device routes done event"),
13244 )?;
13245 let mut output = e.uninit(experts.input_width)?;
13248 e.stream().memcpy_dtod(
13249 &workspace.combined.slice(0..experts.input_width),
13250 &mut output.slice_mut(0..experts.input_width),
13251 )?;
13252 output
13253 };
13254 if let Some(started) = started {
13255 use std::sync::atomic::Ordering;
13256 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13257 + started.elapsed().as_nanos() as u64;
13258 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13259 if calls.is_multiple_of(430) {
13260 eprintln!(
13261 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13262 ns as f64 / 1.0e6,
13263 ns as f64 / calls as f64 / 1.0e3,
13264 );
13265 }
13266 }
13267 Ok(output)
13268 }
13269
13270 #[allow(clippy::too_many_arguments)]
13276 pub fn nvfp4_routes_prestage(
13281 &self,
13282 experts: &ResidentNvfp4TensorParallel,
13283 e: &Engine,
13284 input_dev: &crate::CudaSlice<f32>,
13285 ) -> Result<bool, Box<dyn std::error::Error>> {
13286 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
13287 }
13288
13289 pub fn nvfp4_routes_prestage_with(
13295 &self,
13296 experts: &ResidentNvfp4TensorParallel,
13297 e: &Engine,
13298 input_dev: &crate::CudaSlice<f32>,
13299 rank1_router: impl FnOnce(
13300 &Engine,
13301 &crate::CudaSlice<f32>,
13302 &mut crate::CudaSlice<i32>,
13303 &mut crate::CudaSlice<f32>,
13304 ) -> Result<bool, Box<dyn std::error::Error>>,
13305 ) -> Result<bool, Box<dyn std::error::Error>> {
13306 if !routes_prestage_on() || step_tp_graph_enabled()? {
13307 return Ok(false);
13308 }
13309 if input_dev.len() != experts.input_width {
13310 return Err("NVFP4 prestage input width mismatch".into());
13311 }
13312 let mut workspace_guard = experts
13313 .device_workspace
13314 .lock()
13315 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13316 let Some(workspace) = workspace_guard.as_mut() else {
13317 return Ok(false);
13318 };
13319 if workspace.ev_input.is_none() {
13320 let _main = e.gpu.enter_main()?;
13321 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13322 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
13323 return Err("NVFP4 prestage engine changed".into());
13324 }
13325 {
13326 let _main = e.gpu.enter_main()?;
13327 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13328 ev.record(&e.stream())?;
13329 }
13330 for (rank_index, engine) in self.ranks.iter().enumerate() {
13331 let _main = engine.gpu.enter_main()?;
13332 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
13333 engine.stream().wait(ev)?;
13334 {
13335 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13336 engine
13337 .stream()
13338 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13339 }
13340 {
13341 let Nvfp4DeviceRoutesWorkspace {
13342 input, in_q, in_d, ..
13343 } = &mut *workspace;
13344 engine.quantize_q8_1_into(
13345 &input[rank_index],
13346 1,
13347 experts.input_width,
13348 &mut in_q[rank_index],
13349 &mut in_d[rank_index],
13350 )?;
13351 }
13352 }
13353 if self.ranks.len() == 2 {
13354 let rank1 = &self.ranks[1];
13355 let _r1 = rank1.gpu.enter_main()?;
13356 let Nvfp4DeviceRoutesWorkspace {
13357 input,
13358 sel,
13359 route_w,
13360 ..
13361 } = &mut *workspace;
13362 let (in1, rest_sel) = (&input[1], &mut sel[1]);
13363 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
13364 workspace.rank1_routed = true;
13365 }
13366 }
13367 workspace.prestaged = true;
13368 Ok(true)
13369 }
13370
13371 #[allow(clippy::too_many_arguments)]
13388 fn determ_stage_bytes(v: &[u8]) -> u64 {
13397 v.iter().fold(0u64, |a, b| {
13398 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
13399 })
13400 }
13401
13402 fn determ_stage_i32(v: &[i32]) -> u64 {
13407 v.iter().fold(0u64, |a, b| {
13408 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
13409 })
13410 }
13411
13412 fn determ_stage_sum(v: &[f32]) -> u64 {
13413 v.iter().fold(0u64, |a, x| {
13414 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
13415 })
13416 }
13417
13418 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
13420 &self,
13421 experts: &ResidentNvfp4TensorParallel,
13422 e: &Engine,
13423 z_t: &crate::CudaSlice<f32>,
13424 t: usize,
13425 sel: &[i32],
13426 w: &[f32],
13427 n_used: usize,
13428 activation_limit: Option<f32>,
13429 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13430 let world = self.ranks.len();
13431 if world != NVFP4_CANONICAL_ROW_SHARDS {
13432 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
13433 }
13434 let slot_major = experts.gate.iter().all(|b| b.slot_major)
13448 && experts.up.iter().all(|b| b.slot_major)
13449 && experts.down.iter().all(|b| b.slot_major);
13450 let any_slot_major = experts.gate.iter().any(|b| b.slot_major)
13451 || experts.up.iter().any(|b| b.slot_major)
13452 || experts.down.iter().any(|b| b.slot_major);
13453 if any_slot_major != slot_major {
13454 return Err(
13455 "NVFP4 grouped prime: gate/up/down banks disagree on the row layout — \
13456 one grouped GEMM cannot serve two byte maps"
13457 .into(),
13458 );
13459 }
13460 let bank_qt = if slot_major {
13461 crate::QT_NVFP4_V2
13462 } else {
13463 crate::QT_NVFP4
13464 };
13465 let width = experts.input_width;
13466 let n_expert = experts.expert_count;
13467 let n_pairs = t * n_used;
13468 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
13469 return Err("NVFP4 grouped prime geometry".into());
13470 }
13471 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
13480 let g_t0 = std::time::Instant::now();
13481 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
13484 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
13485 let s_id = s_id as usize;
13486 if s_id >= n_expert {
13487 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
13488 }
13489 buckets[s_id].push(p as i32);
13490 }
13491 let mut ex_ids: Vec<i32> = Vec::new();
13492 let mut ex_off: Vec<i32> = vec![0];
13493 let mut ex_pairs: Vec<i32> = Vec::new();
13494 for (e_id, b) in buckets.iter().enumerate() {
13495 if !b.is_empty() {
13496 ex_ids.push(e_id as i32);
13497 ex_pairs.extend_from_slice(b);
13498 ex_off.push(ex_pairs.len() as i32);
13499 }
13500 }
13501 let n_active = ex_ids.len();
13502 if n_active == 0 {
13503 return e.zeros(t * width);
13504 }
13505 if n_active > 512 {
13506 return Err("grouped prime n_active > 512 (direct lane cap)".into());
13507 }
13508 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
13509 let mut inv = vec![0i32; n_pairs];
13512 for (row, &pair) in ex_pairs.iter().enumerate() {
13513 inv[pair as usize] = row as i32;
13514 }
13515 let mg: Vec<f32> = ex_pairs
13517 .iter()
13518 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
13519 .collect();
13520 let mu: Vec<f32> = ex_pairs
13521 .iter()
13522 .map(|&p| experts.macros_up[sel[p as usize] as usize])
13523 .collect();
13524 let wd: Vec<f32> = (0..n_pairs)
13525 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
13526 .collect();
13527 {
13531 let mut tabs = experts
13532 .prime_tables
13533 .lock()
13534 .map_err(|_| "grouped prime table cache is poisoned")?;
13535 if tabs.len() != world {
13536 tabs.clear();
13537 for rank in 0..world {
13538 let engine = &self.ranks[rank];
13539 let _main = engine.gpu.enter_main()?;
13540 let (gb, ub, db) =
13541 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
13542 let mut tab = vec![0u64; 3 * n_expert];
13543 {
13544 use cudarc::driver::DevicePtr;
13545 let stream = engine.stream();
13546 let (pg, _g0) = gb.bank.device_ptr(&stream);
13547 let (pu, _g1) = ub.bank.device_ptr(&stream);
13548 let (pd, _g2) = db.bank.device_ptr(&stream);
13549 for ex in 0..n_expert {
13550 tab[ex] = pg + (ex * gb.expert_bytes) as u64;
13551 tab[n_expert + ex] = pu + (ex * ub.expert_bytes) as u64;
13552 tab[2 * n_expert + ex] = pd + (ex * db.expert_bytes) as u64;
13553 }
13554 }
13555 tabs.push(engine.htod_u64(&tab)?);
13556 }
13557 }
13558 }
13559 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
13560 let g_t1 = std::time::Instant::now();
13561 {
13568 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
13569 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13570 for rank in 0..world {
13571 let e_r = &self.ranks[rank];
13572 let _m = e_r.gpu.enter_main();
13573 eprintln!(
13574 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
13575 root_stream={:?}",
13576 e_r.ctx().ordinal(),
13577 std::sync::Arc::as_ptr(e_r.ctx()),
13578 e_r.stream().cu_stream(),
13579 e.ctx().ordinal(),
13580 e.stream().cu_stream(),
13581 );
13582 }
13583 }
13584 }
13585
13586 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
13587 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
13588 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
13589 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
13590 for rank in 0..world {
13591 let engine = &self.ranks[rank];
13592 let _main = engine.gpu.enter_main()?;
13593 if gprof {
13594 let h = engine
13599 .ctx()
13600 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13601 h.record(&engine.stream())?;
13602 ev_head.push(h);
13603 }
13604 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
13607 let gb = &experts.gate[rank];
13608 let ub = &experts.up[rank];
13609 let db = &experts.down[rank];
13610 if db.device_rank != rank {
13611 return Err("grouped prime: down shard placement drifted".into());
13612 }
13613 let local_ff = gb.local_out;
13614 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
13615 return Err("grouped prime: bank width mismatch".into());
13616 }
13617 let csr_tok_d = engine.htod_i32(&csr_tok)?;
13620 let exi_d = engine.htod_i32(&ex_ids)?;
13621 let exoff_d = engine.htod_i32(&ex_off)?;
13622 let mg_d = engine.htod(&mg)?;
13623 let mu_d = engine.htod(&mu)?;
13624 let tabs_guard = experts
13626 .prime_tables
13627 .lock()
13628 .map_err(|_| "grouped prime table cache is poisoned")?;
13629 let tab_d = &tabs_guard[rank];
13630 let mut z_r = engine.uninit(t * width)?;
13631 {
13632 let mut dst = z_r.slice_mut(0..t * width);
13633 engine
13634 .stream()
13635 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
13636 }
13637 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
13638 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
13639 if dstage {
13640 let zr = engine.dtoh(&z_r)?;
13644 let zsv = engine.dtoh(&zs)?;
13645 let z16v = engine.dtoh_u8(&z16)?;
13646 eprintln!(
13647 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
13648 Self::determ_stage_sum(&zr),
13649 Self::determ_stage_sum(&zsv),
13650 Self::determ_stage_bytes(&z16v)
13651 );
13652 }
13653 if dstage {
13654 engine.stream().synchronize()?;
13662 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
13663 let exi_v = engine.dtoh_i32(&exi_d)?;
13664 let exo_v = engine.dtoh_i32(&exoff_d)?;
13665 let mg_v = engine.dtoh(&mg_d)?;
13666 let mu_v = engine.dtoh(&mu_d)?;
13667 let tab_v = engine.dtoh_u64(tab_d)?;
13668 eprintln!(
13669 "[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={}",
13670 Self::determ_stage_i32(&csr_v),
13671 Self::determ_stage_i32(&exi_v),
13672 Self::determ_stage_i32(&exo_v),
13673 Self::determ_stage_i32(&ex_off),
13674 Self::determ_stage_sum(&mg_v),
13675 Self::determ_stage_sum(&mu_v),
13676 tab_v
13677 .iter()
13678 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
13679 gb.row_bytes
13680 );
13681 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
13684 let bank_v = engine.dtoh_u8(&gb.bank)?;
13685 eprintln!(
13686 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
13687 Self::determ_stage_bytes(&bank_v),
13688 bank_v.len()
13689 );
13690 }
13691 }
13692 let mut g = engine.moe_f16_grouped(
13693 tab_d,
13694 0,
13695 n_expert,
13696 &exi_d,
13697 &ex_off,
13698 &exoff_d,
13699 &z16,
13700 &zs,
13701 width,
13702 local_ff,
13703 n_active,
13704 n_pairs,
13705 bank_qt,
13706 gb.row_bytes,
13707 )?;
13708 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
13709 let mut u = engine.moe_f16_grouped(
13710 tab_d,
13711 1,
13712 n_expert,
13713 &exi_d,
13714 &ex_off,
13715 &exoff_d,
13716 &z16,
13717 &zs,
13718 width,
13719 local_ff,
13720 n_active,
13721 n_pairs,
13722 bank_qt,
13723 ub.row_bytes,
13724 )?;
13725 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
13726 let act = match activation_limit.filter(|l| *l > 1e-6) {
13730 Some(lim) => {
13731 let mut a = engine.uninit(n_pairs * local_ff)?;
13732 engine.swiglu_clamped_mul_scaled(
13733 &g,
13734 &u,
13735 1.0,
13736 1.0,
13737 lim,
13738 &mut a,
13739 n_pairs * local_ff,
13740 )?;
13741 a
13742 }
13743 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
13744 };
13745 if dstage {
13746 let gv = engine.dtoh(&g)?;
13747 let uv = engine.dtoh(&u)?;
13748 let av = engine.dtoh(&act)?;
13749 let key = (rank, t);
13754 let mut prev_map = DETERM_PREV
13755 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
13756 .lock()
13757 .map_err(|_| "determ prev map poisoned")?;
13758 let shape = match prev_map.get(&key) {
13759 Some(prev) if prev.len() == gv.len() => {
13760 let mut md = 0.0f32;
13761 let mut n_diff = 0usize;
13762 let mut n_big = 0usize;
13763 for (a, b) in prev.iter().zip(gv.iter()) {
13764 let d = (a - b).abs();
13765 if d > 0.0 {
13766 n_diff += 1;
13767 }
13768 if d > 1e-3 {
13769 n_big += 1;
13770 }
13771 if d > md {
13772 md = d;
13773 }
13774 }
13775 format!(
13776 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
13777 gv.len()
13778 )
13779 }
13780 _ => String::new(),
13781 };
13782 prev_map.insert(key, gv.clone());
13783 drop(prev_map);
13784 eprintln!(
13785 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
13786 Self::determ_stage_sum(&gv),
13787 Self::determ_stage_sum(&uv),
13788 Self::determ_stage_sum(&av)
13789 );
13790 }
13791 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
13792 let d_csr = engine.moe_f16_grouped(
13793 tab_d,
13794 2,
13795 n_expert,
13796 &exi_d,
13797 &ex_off,
13798 &exoff_d,
13799 &a16,
13800 &a_s,
13801 local_ff,
13802 width,
13803 n_active,
13804 n_pairs,
13805 bank_qt,
13806 db.row_bytes,
13807 )?;
13808
13809 if dstage {
13812 engine.stream().synchronize()?;
13813 let a16v = engine.dtoh_u8(&a16)?;
13814 let dv = engine.dtoh(&d_csr)?;
13815 eprintln!(
13816 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
13817 Self::determ_stage_bytes(&a16v),
13818 Self::determ_stage_sum(&dv)
13819 );
13820 }
13821 let ev = engine.ctx().new_event(None)?;
13822 ev.record(&engine.stream())?;
13823 if gprof {
13824 let tp = engine
13833 .ctx()
13834 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13835 tp.record(&engine.stream())?;
13836 ev_tail_prof.push(tp);
13837 }
13838 ev_rank.push(ev);
13839 partials.push(d_csr);
13840 }
13841 let _main = e.gpu.enter_main()?;
13842 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
13843 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
13845 let g_t2 = std::time::Instant::now();
13846 for ev in &ev_rank {
13847 e.stream().wait(ev)?;
13848 }
13849 let mut y0 = e.uninit(n_pairs * width)?;
13852 {
13853 let mut dst = y0.slice_mut(0..n_pairs * width);
13854 e.stream()
13855 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
13856 }
13857 let mut y1 = e.uninit(n_pairs * width)?;
13858 {
13859 let mut dst = y1.slice_mut(0..n_pairs * width);
13860 e.stream()
13861 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
13862 }
13863 let inv_d = e.htod_i32(&inv)?;
13864 let wd_d = e.htod(&wd)?;
13865 let mut out = e.uninit(t * width)?;
13866 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
13867 if gprof {
13868 let _ = e.stream().synchronize();
13869 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
13870 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
13878 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
13879 let guard = self.ranks[rank].gpu.enter_main();
13880 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
13881 Ok(v) => span_ms.push(v),
13882 Err(err) => {
13883 static SAID: std::sync::atomic::AtomicBool =
13884 std::sync::atomic::AtomicBool::new(false);
13885 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13886 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
13887 }
13888 span_ms.push(-1.0);
13889 }
13890 }
13891 }
13892 eprintln!(
13893 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
13894 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
13895 span_ms.iter().sum::<f32>(),
13896 span_ms.iter().cloned().fold(0.0f32, f32::max)
13897 );
13898 }
13899 Ok(out)
13900 }
13901
13902 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_routed(
13904 &self,
13905 experts: &ResidentNvfp4TensorParallel,
13906 e: &Engine,
13907 input_dev: &crate::CudaSlice<f32>,
13908 sel_d: &crate::CudaSlice<i32>,
13909 w_d: &crate::CudaSlice<f32>,
13910 experts_per_token: usize,
13911 activation_limit: Option<f32>,
13912 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13913 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
13914 experts,
13915 e,
13916 input_dev,
13917 sel_d,
13918 w_d,
13919 experts_per_token,
13920 activation_limit,
13921 || Ok(()),
13922 )
13923 }
13924
13925 #[allow(clippy::too_many_arguments)]
13931 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
13932 &self,
13933 experts: &ResidentNvfp4TensorParallel,
13934 e: &Engine,
13935 input_dev: &crate::CudaSlice<f32>,
13936 sel_d: &crate::CudaSlice<i32>,
13937 w_d: &crate::CudaSlice<f32>,
13938 experts_per_token: usize,
13939 activation_limit: Option<f32>,
13940 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
13941 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13942 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
13943 experts,
13944 e,
13945 input_dev,
13946 sel_d,
13947 w_d,
13948 experts_per_token,
13949 activation_limit,
13950 pre_join,
13951 None,
13952 )
13953 }
13954
13955 #[allow(clippy::too_many_arguments)]
13960 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
13961 &self,
13962 experts: &ResidentNvfp4TensorParallel,
13963 e: &Engine,
13964 input_dev: &crate::CudaSlice<f32>,
13965 sel_d: &crate::CudaSlice<i32>,
13966 w_d: &crate::CudaSlice<f32>,
13967 experts_per_token: usize,
13968 activation_limit: Option<f32>,
13969 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
13970 post_add: Option<(u64, u64)>,
13971 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13972 if input_dev.len() != experts.input_width {
13973 return Err(format!(
13974 "NVFP4 device-routed input {} != width {}",
13975 input_dev.len(),
13976 experts.input_width
13977 )
13978 .into());
13979 }
13980 let n_sel = experts_per_token;
13981 if sel_d.len() < n_sel || w_d.len() < n_sel {
13982 return Err(format!(
13983 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
13984 sel_d.len(),
13985 w_d.len()
13986 )
13987 .into());
13988 }
13989 let world = self.ranks.len();
13990 if world != NVFP4_CANONICAL_ROW_SHARDS {
13991 return Err(format!(
13992 "NVFP4 device routes require world == canonical shard grid \
13993 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
13994 )
13995 .into());
13996 }
13997 let local_out = if experts.ep2 {
13998 experts.expert_width
13999 } else {
14000 experts.expert_width / world
14001 };
14002
14003 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
14004 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
14005 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
14006 let started = timing.then(std::time::Instant::now);
14007
14008 let mut workspace_guard = experts
14009 .device_workspace
14010 .lock()
14011 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
14012 if workspace_guard.is_none() {
14013 drop(workspace_guard);
14014 let zero = vec![0.0f32; experts.input_width];
14015 let zero_sel = vec![0usize; n_sel];
14016 let zero_w = vec![0.0f32; n_sel];
14017 let _ = self.run_tensor_parallel_routes_nvfp4_device(
14018 experts,
14019 &zero,
14020 &zero_sel,
14021 &zero_w,
14022 n_sel,
14023 activation_limit,
14024 )?;
14025 workspace_guard = experts
14026 .device_workspace
14027 .lock()
14028 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
14029 }
14030 let workspace = workspace_guard
14031 .as_mut()
14032 .expect("NVFP4 device routes workspace initialized above");
14033 if workspace.n_sel != n_sel {
14034 return Err(format!(
14035 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
14036 workspace.n_sel
14037 )
14038 .into());
14039 }
14040
14041 if step_tp_graph_enabled()? && step_tp_graph_headroom_ok(e) {
14052 if experts.ep2 {
14053 return Err(
14054 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
14055 co-gated; unset one"
14056 .into(),
14057 );
14058 }
14059 if workspace.dev_route_e.is_none() {
14060 let _main = e.gpu.enter_main()?;
14061 workspace.dev_route_e = Some((
14062 e.htod_i32(&vec![0i32; n_sel])?,
14063 e.htod(&vec![0.0f32; n_sel])?,
14064 ));
14065 }
14066 if workspace.in_stage_e.is_none() {
14067 let _main = e.gpu.enter_main()?;
14068 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14069 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
14070 }
14071 if workspace.routes_graph.is_none() {
14072 let graph = self.nvfp4_routes_build_graph(
14073 experts,
14074 workspace,
14075 local_out,
14076 n_sel,
14077 activation_limit,
14078 )?;
14079 workspace.routes_graph = Some(graph);
14080 eprintln!(
14081 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
14082 children=3 updates=none performance_claim=false"
14083 );
14084 }
14085 let output = {
14086 let _main = e.gpu.enter_main()?;
14087 {
14088 let (sel_e, w_e) = workspace
14089 .dev_route_e
14090 .as_mut()
14091 .expect("device route staging set above");
14092 {
14093 let mut dst = sel_e.slice_mut(0..n_sel);
14094 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14095 }
14096 {
14097 let mut dst = w_e.slice_mut(0..n_sel);
14098 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14099 }
14100 }
14101 {
14102 let in_stage = workspace
14103 .in_stage_e
14104 .as_mut()
14105 .expect("graph staging set above");
14106 let mut dst = in_stage.slice_mut(0..experts.input_width);
14107 e.stream()
14108 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
14109 }
14110 unsafe {
14111 let r = cudarc::driver::sys::cuGraphLaunch(
14112 workspace
14113 .routes_graph
14114 .as_ref()
14115 .expect("routes graph built above")
14116 .exec,
14117 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
14118 );
14119 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
14120 return Err(format!("routes graph launch: {r:?}").into());
14121 }
14122 }
14123 let mut output = e.uninit(experts.input_width)?;
14124 {
14125 let out_stage = workspace
14126 .out_stage_e
14127 .as_ref()
14128 .expect("graph staging set above");
14129 e.stream().memcpy_dtod(
14130 &out_stage.slice(0..experts.input_width),
14131 &mut output.slice_mut(0..experts.input_width),
14132 )?;
14133 }
14134 output
14135 };
14136 if let Some(started) = started {
14137 use std::sync::atomic::Ordering;
14138 let ns = TIMING_NS
14139 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14140 + started.elapsed().as_nanos() as u64;
14141 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14142 if calls.is_multiple_of(430) {
14143 eprintln!(
14144 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14145 ns as f64 / 1.0e6,
14146 ns as f64 / calls as f64 / 1.0e3,
14147 );
14148 }
14149 }
14150 return Ok(output);
14151 }
14152
14153 if let Some((_, device)) = workspace.ev_entry.as_ref() {
14157 if *device != e.ctx().ordinal() {
14158 return Err("NVFP4 device-routed routes engine changed".into());
14159 }
14160 } else {
14161 let _main = e.gpu.enter_main()?;
14162 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
14163 }
14164 if workspace.dev_route_e.is_none() {
14165 let _main = e.gpu.enter_main()?;
14166 workspace.dev_route_e = Some((
14167 e.htod_i32(&vec![0i32; n_sel])?,
14168 e.htod(&vec![0.0f32; n_sel])?,
14169 ));
14170 }
14171 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
14177 let e_device = e.ctx().ordinal();
14178 let rank1_routed_peek = workspace.rank1_routed;
14180 let stage_needed = !mirror
14181 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
14182 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
14183 });
14184 {
14185 let _main = e.gpu.enter_main()?;
14186 if stage_needed {
14187 let (sel_e, w_e) = workspace
14188 .dev_route_e
14189 .as_mut()
14190 .expect("device route staging set above");
14191 {
14192 let mut dst = sel_e.slice_mut(0..n_sel);
14193 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
14194 }
14195 {
14196 let mut dst = w_e.slice_mut(0..n_sel);
14197 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
14198 }
14199 }
14200 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14201 ev_entry.record(&e.stream())?;
14202 }
14203 let prestaged = std::mem::take(&mut workspace.prestaged);
14206 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
14207 for (rank_index, engine) in self.ranks.iter().enumerate() {
14208 let _main = engine.gpu.enter_main()?;
14209 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
14210 engine.stream().wait(ev_entry)?;
14211 if !prestaged {
14212 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
14213 engine
14214 .stream()
14215 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
14216 }
14217 if !(rank1_routed && rank_index == 1) {
14218 let same_dev = engine.ctx().ordinal() == e_device;
14222 if mirror {
14223 let Nvfp4DeviceRoutesWorkspace {
14226 sel,
14227 route_w,
14228 dev_route_e,
14229 ..
14230 } = &mut *workspace;
14231 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
14232 if same_dev {
14233 (sel_d, w_d)
14234 } else {
14235 let (sel_e, w_e) = dev_route_e
14236 .as_ref()
14237 .expect("device route staging set above");
14238 (sel_e, w_e)
14239 };
14240 engine.moe_sel_w_mirror(
14241 src_sel,
14242 src_w,
14243 &mut sel[rank_index],
14244 &mut route_w[rank_index],
14245 n_sel,
14246 )?;
14247 } else {
14248 let (sel_e, w_e) = workspace
14249 .dev_route_e
14250 .as_ref()
14251 .expect("device route staging set above");
14252 {
14253 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
14254 engine
14255 .stream()
14256 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
14257 }
14258 {
14259 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
14260 engine
14261 .stream()
14262 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
14263 }
14264 }
14265 }
14266 if !prestaged {
14267 let Nvfp4DeviceRoutesWorkspace {
14268 input, in_q, in_d, ..
14269 } = &mut *workspace;
14270 engine.quantize_q8_1_into(
14271 &input[rank_index],
14272 1,
14273 experts.input_width,
14274 &mut in_q[rank_index],
14275 &mut in_d[rank_index],
14276 )?;
14277 }
14278 }
14279 self.nvfp4_routes_batched_sweeps(
14280 experts,
14281 workspace,
14282 &[],
14283 &[],
14284 &[],
14285 local_out,
14286 n_sel,
14287 activation_limit,
14288 true,
14289 )?;
14290
14291 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
14294 let _main = engine.gpu.enter_main()?;
14295 workspace.ev_rank[rank_index].record(&engine.stream())?;
14296 }
14297 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
14300 let mut ticket = 0u32;
14301 if memops {
14302 use cudarc::driver::sys;
14303 if workspace.fence_flags_raw == 0 {
14304 let root = &self.ranks[0];
14305 let _main = root.gpu.enter_main()?;
14306 let mut ptr: sys::CUdeviceptr = 0;
14307 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
14308 if r != sys::CUresult::CUDA_SUCCESS {
14309 return Err(format!("fence flag alloc: {r:?}").into());
14310 }
14311 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
14312 if r != sys::CUresult::CUDA_SUCCESS {
14313 return Err(format!("fence flag memset: {r:?}").into());
14314 }
14315 workspace.fence_flags_raw = ptr as u64;
14316 }
14317 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
14318 ticket = workspace.fence_ticket;
14319 let base = workspace.fence_flags_raw;
14320 if fence_rank1_on() {
14326 let peer = &self.ranks[1];
14327 let _pmain = peer.gpu.enter_main()?;
14328 peer.ring_flag_raw(base, ticket)?;
14329 }
14330 {
14331 let root = &self.ranks[0];
14332 let _main = root.gpu.enter_main()?;
14333 let r = unsafe {
14334 sys::cuStreamWriteValue32_v2(
14335 root.stream().cu_stream() as sys::CUstream,
14336 (base + 4) as sys::CUdeviceptr,
14337 ticket,
14338 0,
14339 )
14340 };
14341 if r != sys::CUresult::CUDA_SUCCESS {
14342 return Err(format!("fence write root: {r:?}").into());
14343 }
14344 }
14345 }
14346 pre_join()?;
14349
14350 if moe_direct_on() && self.ranks.len() == 2 {
14351 let _main = e.gpu.enter_main()?;
14358 if memops {
14359 use cudarc::driver::sys;
14360 let base = workspace.fence_flags_raw;
14361 let r = unsafe {
14362 sys::cuStreamWaitValue32_v2(
14363 e.stream().cu_stream() as sys::CUstream,
14364 (base + 4) as sys::CUdeviceptr,
14365 ticket,
14366 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14367 )
14368 };
14369 if r != sys::CUresult::CUDA_SUCCESS {
14370 return Err(format!("fence wait: {r:?}").into());
14371 }
14372 if fence_rank1_on() {
14373 let r = unsafe {
14375 sys::cuStreamWaitValue32_v2(
14376 e.stream().cu_stream() as sys::CUstream,
14377 base as sys::CUdeviceptr,
14378 ticket,
14379 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
14380 )
14381 };
14382 if r != sys::CUresult::CUDA_SUCCESS {
14383 return Err(format!("fence wait rank1: {r:?}").into());
14384 }
14385 } else {
14386 for ev in workspace.ev_rank.iter().skip(1) {
14387 e.stream().wait(ev)?;
14388 }
14389 }
14390 } else {
14391 {
14392 let root = &self.ranks[0];
14393 let _rmain = root.gpu.enter_main()?;
14394 workspace
14395 .ev_done
14396 .as_ref()
14397 .expect("device routes done event")
14398 .record(&root.stream())?;
14399 }
14400 e.stream().wait(
14401 workspace
14402 .ev_done
14403 .as_ref()
14404 .expect("device routes done event"),
14405 )?;
14406 for ev in workspace.ev_rank.iter().skip(1) {
14407 e.stream().wait(ev)?;
14408 }
14409 }
14410 let mut output = e.uninit(experts.input_width)?;
14411 if let Some((sh_raw, scale_raw)) = post_add {
14412 e.add3_raw(
14415 &workspace.accumulator[0],
14416 &workspace.accumulator[1],
14417 sh_raw,
14418 scale_raw,
14419 &mut output,
14420 experts.input_width,
14421 )?;
14422 } else {
14423 e.add(
14424 &workspace.accumulator[0],
14425 &workspace.accumulator[1],
14426 &mut output,
14427 experts.input_width,
14428 )?;
14429 }
14430 let output = output;
14431 if let Some(started) = started {
14432 use std::sync::atomic::Ordering;
14433 let ns = TIMING_NS
14434 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14435 + started.elapsed().as_nanos() as u64;
14436 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14437 if calls.is_multiple_of(430) {
14438 eprintln!(
14439 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14440 ns as f64 / 1.0e6,
14441 ns as f64 / calls as f64 / 1.0e3,
14442 );
14443 }
14444 }
14445 return Ok(output);
14446 }
14447 {
14448 let root = &self.ranks[0];
14449 let _main = root.gpu.enter_main()?;
14450 for ev in workspace.ev_rank.iter().skip(1) {
14451 root.stream().wait(ev)?;
14452 }
14453 root.stream()
14454 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
14455 {
14456 let Nvfp4DeviceRoutesWorkspace {
14457 accumulator,
14458 remote,
14459 combined,
14460 ..
14461 } = &mut *workspace;
14462 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14463 }
14464 workspace
14465 .ev_done
14466 .as_ref()
14467 .expect("device routes done event")
14468 .record(&root.stream())?;
14469 }
14470 let output = {
14471 let _main = e.gpu.enter_main()?;
14472 e.stream().wait(
14473 workspace
14474 .ev_done
14475 .as_ref()
14476 .expect("device routes done event"),
14477 )?;
14478 let mut output = e.uninit(experts.input_width)?;
14481 e.stream().memcpy_dtod(
14482 &workspace.combined.slice(0..experts.input_width),
14483 &mut output.slice_mut(0..experts.input_width),
14484 )?;
14485 output
14486 };
14487 if let Some(started) = started {
14488 use std::sync::atomic::Ordering;
14489 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14490 + started.elapsed().as_nanos() as u64;
14491 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14492 if calls.is_multiple_of(430) {
14493 eprintln!(
14494 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14495 ns as f64 / 1.0e6,
14496 ns as f64 / calls as f64 / 1.0e3,
14497 );
14498 }
14499 }
14500 Ok(output)
14501 }
14502
14503 pub(crate) fn decode_v2_finish_root_fused(
14507 &self,
14508 ws: &mut StepTpDecodeV2Ws,
14509 ) -> Result<(), Box<dyn std::error::Error>> {
14510 let root = &self.ranks[0];
14511 let _main = root.gpu.enter_main()?;
14512 if ws.raw_peer_partial != 0 {
14513 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
14515 } else {
14516 root.stream()
14517 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
14518 }
14519 {
14520 let StepTpDecodeV2Ws {
14521 o_partials,
14522 peer_partial,
14523 reduce_a,
14524 o_out,
14525 ..
14526 } = &mut *ws;
14527 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
14528 }
14529 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
14530 if shadows {
14531 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
14534 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
14535 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
14536 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
14537 }
14538 if shadows && ws.raw_peer_partial != 0 {
14539 raw_copy_bytes(
14540 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
14541 ws.raw_k1,
14542 ws.local_kv_dim * 4,
14543 root,
14544 )?;
14545 raw_copy_bytes(
14546 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
14547 ws.raw_v1,
14548 ws.local_kv_dim * 4,
14549 root,
14550 )?;
14551 } else if shadows {
14552 let start = ws.local_kv_dim;
14553 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
14554 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
14555 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
14556 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
14557 }
14558 if ws.raw_mixed_stage_e != 0 {
14559 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
14562 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
14563 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
14564 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
14565 }
14566 Ok(())
14567 }
14568
14569 pub(crate) fn decode_v2_arm_token_mirrors(
14572 &self,
14573 ws: &mut StepTpDecodeV2Ws,
14574 mixed_stage_e: u64,
14575 shadow_stage_e: (u64, u64),
14576 ) -> Result<(), Box<dyn std::error::Error>> {
14577 use cudarc::driver::DevicePtr;
14578 let root = &self.ranks[0];
14579 let _main = root.gpu.enter_main()?;
14580 let stream = root.stream();
14581 let (a, _g) = ws.reduce_a.device_ptr(&stream);
14582 ws.raw_reduce_a = a;
14583 ws.raw_mixed_stage_e = mixed_stage_e;
14584 ws.raw_shadow_stage_e = shadow_stage_e;
14585 Ok(())
14586 }
14587
14588 fn nvfp4_routes_build_graph(
14594 &self,
14595 experts: &ResidentNvfp4TensorParallel,
14596 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14597 local_out: usize,
14598 n_sel: usize,
14599 activation_limit: Option<f32>,
14600 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
14601 use cudarc::driver::DevicePtr;
14602 use cudarc::driver::sys;
14603 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14604 if r == sys::CUresult::CUDA_SUCCESS {
14605 Ok(())
14606 } else {
14607 Err(format!("{what}: {r:?}").into())
14608 }
14609 }
14610 let world = self.ranks.len();
14611 if world != 2 {
14612 return Err("routes graph door is built for the TP2 pair".into());
14613 }
14614 let width = experts.input_width;
14615
14616 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
14618 let stream = engine.stream();
14619 let (ptr, _g) = buf.device_ptr(&stream);
14620 ptr
14621 };
14622 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
14623 let stream = engine.stream();
14624 let (ptr, _g) = buf.device_ptr(&stream);
14625 ptr
14626 };
14627 let (sel_e, w_e) = workspace
14628 .dev_route_e
14629 .as_ref()
14630 .expect("device route staging set before graph build");
14631 let root_engine = &self.ranks[0];
14632 let p_in_stage = ptr_f32(
14633 workspace.in_stage_e.as_ref().expect("graph staging"),
14634 root_engine,
14635 );
14636 let p_out_stage = ptr_f32(
14637 workspace.out_stage_e.as_ref().expect("graph staging"),
14638 root_engine,
14639 );
14640 let p_sel_e = ptr_i32(sel_e, root_engine);
14641 let p_w_e = ptr_f32(w_e, root_engine);
14642 let p_input: Vec<u64> = (0..world)
14643 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
14644 .collect();
14645 let p_sel: Vec<u64> = (0..world)
14646 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
14647 .collect();
14648 let p_route_w: Vec<u64> = (0..world)
14649 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
14650 .collect();
14651 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
14652 let p_remote = ptr_f32(&workspace.remote, root_engine);
14653 let p_combined = ptr_f32(&workspace.combined, root_engine);
14654
14655 let raw_copy = |dst: u64,
14656 src: u64,
14657 bytes: usize,
14658 engine: &Engine|
14659 -> Result<(), Box<dyn std::error::Error>> {
14660 unsafe {
14661 cu_try(
14662 sys::cuMemcpyAsync(
14663 dst as sys::CUdeviceptr,
14664 src as sys::CUdeviceptr,
14665 bytes,
14666 engine.stream().cu_stream() as sys::CUstream,
14667 ),
14668 "routes graph cuMemcpyAsync",
14669 )
14670 }
14671 };
14672
14673 let mut children = Vec::with_capacity(3);
14674 for rank in 0..world {
14675 let engine = &self.ranks[rank];
14676 let _main = engine.gpu.enter_main()?;
14677 let (child, _retained) = engine.capture_graph_retained(|_| {
14678 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
14679 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
14680 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
14681 {
14682 let Nvfp4DeviceRoutesWorkspace {
14683 input, in_q, in_d, ..
14684 } = &mut *workspace;
14685 engine.quantize_q8_1_into(
14686 &input[rank],
14687 1,
14688 width,
14689 &mut in_q[rank],
14690 &mut in_d[rank],
14691 )?;
14692 }
14693 self.nvfp4_routes_batched_sweeps_rank(
14694 experts,
14695 workspace,
14696 &[],
14697 &[],
14698 &[],
14699 local_out,
14700 n_sel,
14701 activation_limit,
14702 true,
14703 rank,
14704 )?;
14705 Ok(())
14706 })?;
14707 children.push(child);
14708 }
14709 {
14710 let root = &self.ranks[0];
14711 let _main = root.gpu.enter_main()?;
14712 let (child, _retained) = root.capture_graph_retained(|_| {
14713 raw_copy(p_remote, p_acc1, width * 4, root)?;
14714 {
14715 let Nvfp4DeviceRoutesWorkspace {
14716 accumulator,
14717 remote,
14718 combined,
14719 ..
14720 } = &mut *workspace;
14721 root.add(&accumulator[0], remote, combined, width)?;
14722 }
14723 raw_copy(p_out_stage, p_combined, width * 4, root)?;
14724 Ok(())
14725 })?;
14726 children.push(child);
14727 }
14728
14729 let mut parent: sys::CUgraph = std::ptr::null_mut();
14730 unsafe {
14731 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
14732 }
14733 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
14734 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
14735 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
14736 unsafe {
14737 cu_try(
14738 sys::cuGraphAddChildGraphNode(
14739 &mut n0,
14740 parent,
14741 std::ptr::null(),
14742 0,
14743 children[0].cu_graph(),
14744 ),
14745 "routes child r0",
14746 )?;
14747 cu_try(
14748 sys::cuGraphAddChildGraphNode(
14749 &mut n1,
14750 parent,
14751 std::ptr::null(),
14752 0,
14753 children[1].cu_graph(),
14754 ),
14755 "routes child r1",
14756 )?;
14757 let deps = [n0, n1];
14758 cu_try(
14759 sys::cuGraphAddChildGraphNode(
14760 &mut n2,
14761 parent,
14762 deps.as_ptr(),
14763 2,
14764 children[2].cu_graph(),
14765 ),
14766 "routes child root",
14767 )?;
14768 }
14769 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
14770 unsafe {
14771 cu_try(
14772 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
14773 "routes instantiate",
14774 )?;
14775 }
14776 Ok(RoutesGraph {
14777 exec,
14778 parent,
14779 _children: children,
14780 })
14781 }
14782
14783 #[allow(clippy::too_many_arguments)]
14787 pub(crate) fn routes_rank_section(
14788 &self,
14789 experts: &ResidentNvfp4TensorParallel,
14790 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14791 raw_input_src: u64,
14792 local_out: usize,
14793 n_sel: usize,
14794 activation_limit: Option<f32>,
14795 rank_index: usize,
14796 ) -> Result<(), Box<dyn std::error::Error>> {
14797 let engine = &self.ranks[rank_index];
14798 {
14799 let _main = engine.gpu.enter_main()?;
14800 let (sel_e_ptr, w_e_ptr) = workspace
14802 .raw_dev_route_e
14803 .ok_or("routes rank section requires armed staging pointers")?;
14804 raw_copy_bytes(
14805 workspace.raw_input[rank_index],
14806 raw_input_src,
14807 experts.input_width * 4,
14808 engine,
14809 )?;
14810 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
14811 raw_copy_bytes(
14812 workspace.raw_route_w[rank_index],
14813 w_e_ptr,
14814 n_sel * 4,
14815 engine,
14816 )?;
14817 {
14818 let Nvfp4DeviceRoutesWorkspace {
14819 input, in_q, in_d, ..
14820 } = &mut *workspace;
14821 engine.quantize_q8_1_into(
14822 &input[rank_index],
14823 1,
14824 experts.input_width,
14825 &mut in_q[rank_index],
14826 &mut in_d[rank_index],
14827 )?;
14828 }
14829 }
14830 self.nvfp4_routes_batched_sweeps_rank(
14831 experts,
14832 workspace,
14833 &[],
14834 &[],
14835 &[],
14836 local_out,
14837 n_sel,
14838 activation_limit,
14839 true,
14840 rank_index,
14841 )
14842 }
14843
14844 pub(crate) fn routes_root_section(
14847 &self,
14848 experts: &ResidentNvfp4TensorParallel,
14849 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14850 ) -> Result<(), Box<dyn std::error::Error>> {
14851 let root = &self.ranks[0];
14852 let _main = root.gpu.enter_main()?;
14853 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
14854 .raw_combine
14855 .ok_or("routes root section requires armed combine pointers")?;
14856 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
14857 {
14858 let Nvfp4DeviceRoutesWorkspace {
14859 accumulator,
14860 remote,
14861 combined,
14862 ..
14863 } = &mut *workspace;
14864 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14865 }
14866 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
14867 Ok(())
14868 }
14869
14870 pub(crate) fn routes_arm_raw(
14873 &self,
14874 experts: &ResidentNvfp4TensorParallel,
14875 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14876 ) -> Result<(), Box<dyn std::error::Error>> {
14877 use cudarc::driver::DevicePtr;
14878 if workspace.raw_dev_route_e.is_some() {
14879 return Ok(());
14880 }
14881 let _ = experts;
14882 let (sel_e, w_e) = workspace
14883 .dev_route_e
14884 .as_ref()
14885 .ok_or("routes staging not armed")?;
14886 let root = &self.ranks[0];
14887 {
14888 let _main = root.gpu.enter_main()?;
14889 let stream = root.stream();
14890 let (a, _g) = sel_e.device_ptr(&stream);
14891 let (b, _g) = w_e.device_ptr(&stream);
14892 workspace.raw_dev_route_e = Some((a, b));
14893 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
14894 let (d, _g) = workspace.remote.device_ptr(&stream);
14895 let (f, _g) = workspace.combined.device_ptr(&stream);
14896 let out_stage = workspace
14897 .out_stage_e
14898 .as_ref()
14899 .ok_or("routes out stage not armed")?;
14900 let (g_, _g) = out_stage.device_ptr(&stream);
14901 workspace.raw_combine = Some((c, d, f, g_));
14902 }
14903 for rank in 0..self.ranks.len() {
14904 let engine = &self.ranks[rank];
14905 let _main = engine.gpu.enter_main()?;
14906 let stream = engine.stream();
14907 let (a, _g) = workspace.input[rank].device_ptr(&stream);
14908 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
14909 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
14910 workspace.raw_input.push(a);
14911 workspace.raw_sel.push(b);
14912 workspace.raw_route_w.push(c);
14913 }
14914 Ok(())
14915 }
14916
14917 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4(
14922 &self,
14923 experts: &ResidentNvfp4TensorParallel,
14924 input: &[f32],
14925 tokens: usize,
14926 selected: &[usize],
14927 route_weights: &[f32],
14928 experts_per_token: usize,
14929 activation_limit: Option<f32>,
14930 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
14931 validate_activations(input, tokens, experts.input_width)?;
14932 let pairs = tokens
14933 .checked_mul(experts_per_token)
14934 .ok_or("NVFP4 TP route count overflow")?;
14935 if selected.len() != pairs || route_weights.len() != pairs {
14936 return Err(format!(
14937 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
14938 {experts_per_token} ({pairs})",
14939 selected.len(),
14940 route_weights.len(),
14941 )
14942 .into());
14943 }
14944 if !route_weights.iter().all(|weight| weight.is_finite()) {
14945 return Err("NVFP4 TP route weights contain a non-finite value".into());
14946 }
14947
14948 let mut output = vec![0.0f32; tokens * experts.input_width];
14949 for token in 0..tokens {
14950 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
14951 for slot in 0..experts_per_token {
14952 let pair = token * experts_per_token + slot;
14953 let expert = selected[pair];
14954 if expert >= experts.expert_count {
14955 return Err(format!(
14956 "NVFP4 TP selected expert {expert} outside 0..{}",
14957 experts.expert_count
14958 )
14959 .into());
14960 }
14961 let gate = if experts.ep2 {
14967 self.run_full_bank_expert_nvfp4(
14968 &experts.gate,
14969 &experts.macros_gate,
14970 expert,
14971 input_row,
14972 )?
14973 } else {
14974 self.run_column_bank_expert_nvfp4(
14975 &experts.gate,
14976 &experts.macros_gate,
14977 expert,
14978 input_row,
14979 )?
14980 };
14981 let up = if experts.ep2 {
14982 self.run_full_bank_expert_nvfp4(
14983 &experts.up,
14984 &experts.macros_up,
14985 expert,
14986 input_row,
14987 )?
14988 } else {
14989 self.run_column_bank_expert_nvfp4(
14990 &experts.up,
14991 &experts.macros_up,
14992 expert,
14993 input_row,
14994 )?
14995 };
14996 let activated: Vec<f32> = gate
14997 .iter()
14998 .zip(&up)
14999 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
15000 .collect();
15001 debug_assert_eq!(activated.len(), experts.expert_width);
15002 let down = if experts.ep2 {
15003 self.run_full_down_expert_nvfp4(
15004 &experts.down,
15005 &experts.macros_down,
15006 expert,
15007 &activated,
15008 )?
15009 } else {
15010 self.run_row_bank_expert_nvfp4(
15011 &experts.down,
15012 &experts.macros_down,
15013 expert,
15014 &activated,
15015 )?
15016 };
15017 let weight = route_weights[pair];
15018 for (sum, value) in output
15019 [token * experts.input_width..(token + 1) * experts.input_width]
15020 .iter_mut()
15021 .zip(down)
15022 {
15023 *sum += weight * value;
15024 }
15025 }
15026 }
15027 Ok(output)
15028 }
15029}
15030
15031#[cfg(test)]
15032mod default_on_door_tests {
15033 use super::door_default_on_value;
15034
15035 #[test]
15046 fn the_default_on_door_parses_every_state_and_names_its_source() {
15047 assert_eq!(
15050 door_default_on_value("MEMRA_TEST_DOOR", None),
15051 (true, "default-on")
15052 );
15053 assert_eq!(
15055 door_default_on_value("MEMRA_TEST_DOOR", Some("1")),
15056 (true, "env=1")
15057 );
15058 assert_eq!(
15060 door_default_on_value("MEMRA_TEST_DOOR", Some("0")),
15061 (false, "env=0 (rollback seam)")
15062 );
15063 for bad in [
15068 "false", "off", "no", "", " 0", "0 ", "00", "true", "2", "-1",
15069 ] {
15070 let (on, source) = door_default_on_value("MEMRA_TEST_DOOR", Some(bad));
15071 assert!(on, "value {bad:?} must NOT disarm a default-ON door");
15072 assert!(
15073 source.contains("default-on") && source.contains("unrecognized"),
15074 "value {bad:?} gave source {source:?}, which does not announce itself as an \
15075 ignored value — a receipt reader would take it for a clean default"
15076 );
15077 }
15078 }
15079}
15080
15081#[cfg(test)]
15082mod bank_v2_layout_tests {
15083 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
15084
15085 #[test]
15095 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
15096 let (out_f, in_f) = (2usize, 128usize);
15098 let row_bytes = nvfp4_row_bytes(in_f);
15099 assert_eq!(row_bytes, 72);
15100 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
15101 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
15102 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
15103 let n_slots = in_f / 32;
15104 for row in 0..out_f {
15105 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
15106 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
15107 for g in 0..n_slots {
15108 let (sblk, h) = (g / 2, g % 2);
15109 let sb = &src[sblk * 36..sblk * 36 + 36];
15110 assert_eq!(
15111 &dst[g * 16..g * 16 + 16],
15112 &sb[4 + 16 * h..4 + 16 * h + 16],
15113 "row {row} slot {g} codes"
15114 );
15115 assert_eq!(
15116 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
15117 &sb[2 * h..2 * h + 2],
15118 "row {row} slot {g} scales"
15119 );
15120 }
15121 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
15123 a.sort_unstable();
15124 b.sort_unstable();
15125 assert_eq!(a, b, "row {row} is not a byte permutation");
15126 }
15127 }
15128}
15129
15130#[cfg(test)]
15131mod tests {
15132
15133 #[test]
15134 fn door_composition_refuses_first_armed_flag_by_name() {
15135 let table: [(&str, &str); 2] = [
15136 ("MEMRA_DOOR_A", "gated on the unsharded walk only"),
15137 ("MEMRA_DOOR_B", "no sharded branches"),
15138 ];
15139 super::refuse_door_composition("MEMRA_X_TP", &table, |_| false).expect("cold doors pass");
15141 let err = super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_B")
15143 .expect_err("armed door must refuse");
15144 assert_eq!(
15145 err,
15146 "MEMRA_X_TP + MEMRA_DOOR_B: unproven composition, refused (no sharded branches)"
15147 );
15148 super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_C")
15150 .expect("foreign flags are not the matrix");
15151 }
15152
15153 #[test]
15160 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
15161 let (kp, bp) = (0xdead_0000u64, 0u64);
15162 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
15163 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
15164 assert_eq!(
15165 super::retired_rows_tab_key(kp, bp, 20, 2),
15166 super::retired_rows_tab_key(kp, bp, 20, 2),
15167 "same layer and t must hash the same, or the test proves nothing"
15168 );
15169 let a = super::rows_tab_host(&live, 0x9000, true, 1);
15170 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
15171 assert_ne!(a, b, "the two generations write DIFFERENT tables");
15172 assert_eq!(
15174 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
15175 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
15176 "the retired key collides across allocation generations"
15177 );
15178 }
15179
15180 #[test]
15183 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
15184 let parts = [
15185 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
15186 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
15187 ];
15188 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
15189 assert_eq!(
15190 same,
15191 vec![
15192 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
15193 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
15196 "same-session rows share one counter cell and step back t-1-r"
15197 );
15198 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
15199 assert_eq!(
15200 cross,
15201 vec![
15202 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
15203 0x00c1u64, 0x00d1u64, 0x7004, 0,
15204 ],
15205 "cross-session rows get their own counter cell and no step back"
15206 );
15207 }
15208 use super::*;
15209
15210 #[test]
15211 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
15212 let limit = Some(7.0);
15213 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
15214 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
15215 assert!(
15216 step_expert_activation_host(-20.0, 9.0, limit).abs()
15217 < step_expert_activation_host(-20.0, 9.0, None).abs()
15218 );
15219 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
15220 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
15221 assert!(validate_step_expert_activation_limit(limit).is_ok());
15222 }
15223
15224 #[test]
15225 fn moe_residual_host_preserves_official_add_order() {
15226 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
15227 assert_eq!(output, [0.0]);
15228 assert_eq!(
15229 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
15230 "MoE residual lengths residual=1 routed=2 shared=1"
15231 );
15232 }
15233
15234 #[test]
15235 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
15236 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
15237 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
15238 assert_eq!(owners.len(), 4);
15239 for (rank, owner) in owners.iter().enumerate() {
15240 assert_eq!(owner.rank, rank);
15241 assert_eq!(owner.selected, vec![0, 36]);
15242 assert_eq!(owner.token_rows, vec![0, 0]);
15243 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
15244 }
15245 }
15246
15247 #[test]
15248 fn expert_owner_routes_validate_geometry_and_selected_experts() {
15249 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
15250 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
15251 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
15252 assert!(error.contains("outside 0..288"));
15253 }
15254
15255 #[test]
15256 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
15257 let selected = [
15258 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
15259 ];
15260 assert_eq!(
15261 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
15262 16
15263 );
15264 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
15265 assert_eq!(
15266 owners
15267 .iter()
15268 .map(|owner| owner.selected.len())
15269 .collect::<Vec<_>>(),
15270 vec![2, 4, 6, 4]
15271 );
15272 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
15273 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
15274 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
15275 }
15276
15277 #[test]
15278 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
15279 let owner0 = [0usize, 3];
15280 let owner1 = [1usize, 2];
15281 let owners = [owner0.as_slice(), owner1.as_slice()];
15282 assert_eq!(
15283 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
15284 .unwrap(),
15285 WeightedRouteCombineShape {
15286 pairs: 4,
15287 max_pairs: 12,
15288 }
15289 );
15290 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
15291 assert!(
15292 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
15293 .is_err()
15294 );
15295 assert!(
15296 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
15297 .is_err()
15298 );
15299 assert!(
15300 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
15301 .is_err()
15302 );
15303 }
15304
15305 #[test]
15306 fn native_p2p_door_is_strict_and_default_off() {
15307 assert!(!parse_step_tp_native_p2p(None).unwrap());
15308 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
15309 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
15310 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
15311 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
15312 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
15313 }
15314
15315 #[test]
15316 fn bulk_p2p_door_is_strict_and_default_off() {
15317 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
15318 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
15319 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
15320 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
15321 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
15322 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
15323 }
15324
15325 #[test]
15326 fn ep_device_arithmetic_door_is_strict_and_default_off() {
15327 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
15328 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
15329 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
15330 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
15331 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
15332 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
15333 }
15334
15335 #[test]
15336 fn f32_mirror_door_is_strict_and_default_off() {
15337 assert!(!parse_step_tp_f32_mirror(None).unwrap());
15338 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
15339 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
15340 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
15341 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
15342 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
15343 }
15344
15345 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
15346 let codes = (0..out_features * in_features)
15347 .map(|index| (index % 251) as u8)
15348 .collect();
15349 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
15350 .map(|index| index as f32 + 1.0)
15351 .collect();
15352 (codes, scales)
15353 }
15354
15355 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
15356 (0..out_features * in_features)
15357 .flat_map(|value| (value as u16).to_le_bytes())
15358 .collect()
15359 }
15360
15361 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
15362 bytes
15363 .chunks_exact(2)
15364 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
15365 .collect()
15366 }
15367
15368 #[test]
15369 fn bf16_matrix_rejects_wrong_byte_count() {
15370 let bytes = vec![0u8; 4 * 4 * 2 - 1];
15371 let matrix = Bf16Matrix {
15372 bytes: &bytes,
15373 out_features: 4,
15374 in_features: 4,
15375 };
15376 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
15377 }
15378
15379 #[test]
15380 fn replicated_device_rows_require_exact_rank_local_shapes() {
15381 assert_eq!(
15382 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
15383 12_288
15384 );
15385 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
15386 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
15387 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
15388 assert!(
15389 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
15390 );
15391 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
15392 }
15393
15394 #[test]
15395 fn replicated_device_row_refresh_requires_exact_root_source() {
15396 assert_eq!(
15397 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
15398 12_288
15399 );
15400 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
15401 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
15402 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
15403 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
15404 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
15405 }
15406
15407 #[test]
15408 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
15409 for tp in [1, 2, 4, 8] {
15410 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
15411 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
15412 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
15413 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
15414 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
15415 }
15416 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
15417 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
15418 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
15419 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
15420 }
15421
15422 #[test]
15423 fn cache_rows_split_by_token_then_rank() {
15424 let rows = (0u8..24).collect::<Vec<_>>();
15425 assert_eq!(
15426 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
15427 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
15428 );
15429 assert_eq!(
15430 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
15431 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
15432 );
15433 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
15434 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
15435 }
15436
15437 #[test]
15438 fn bf16_column_shard_preserves_contiguous_output_rows() {
15439 let bytes = bf16_matrix_bytes(4, 4);
15440 let matrix = Bf16Matrix {
15441 bytes: &bytes,
15442 out_features: 4,
15443 in_features: 4,
15444 };
15445 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
15446 assert_eq!(shard.out_features, 2);
15447 assert_eq!(shard.in_features, 4);
15448 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
15449 }
15450
15451 #[test]
15452 fn bf16_row_shard_preserves_each_input_column_window() {
15453 let bytes = bf16_matrix_bytes(3, 4);
15454 let matrix = Bf16Matrix {
15455 bytes: &bytes,
15456 out_features: 3,
15457 in_features: 4,
15458 };
15459 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
15460 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
15461 }
15462
15463 #[test]
15464 fn bf16_row_block_preserves_global_column_order() {
15465 let bytes = bf16_matrix_bytes(3, 8);
15466 let matrix = Bf16Matrix {
15467 bytes: &bytes,
15468 out_features: 3,
15469 in_features: 8,
15470 };
15471 let block = bf16_row_block(matrix, 2, 3).unwrap();
15472 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
15473 }
15474
15475 #[test]
15476 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
15477 let (codes, scales) = matrix(1280, 4096);
15478 let matrix = E4m3BlockMatrix {
15479 codes: &codes,
15480 scales: &scales,
15481 out_features: 1280,
15482 in_features: 4096,
15483 };
15484 let shard = column_shard(matrix, 2, 1).unwrap();
15485 assert_eq!(shard.out_features, 640);
15486 assert_eq!(shard.codes, &codes[640 * 4096..]);
15487 assert_eq!(shard.scales, &scales[5 * 32..]);
15488 }
15489
15490 #[test]
15491 fn row_shard_preserves_each_weight_and_scale_column_window() {
15492 let (codes, scales) = matrix(4096, 1280);
15493 let matrix = E4m3BlockMatrix {
15494 codes: &codes,
15495 scales: &scales,
15496 out_features: 4096,
15497 in_features: 1280,
15498 };
15499 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
15500 assert_eq!(shard_codes.len(), 4096 * 640);
15501 assert_eq!(&shard_codes[..640], &codes[640..1280]);
15502 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
15503 assert_eq!(shard_scales.len(), 32 * 5);
15504 assert_eq!(&shard_scales[..5], &scales[5..10]);
15505 assert_eq!(&shard_scales[5..10], &scales[15..20]);
15506 }
15507
15508 #[test]
15509 fn activation_shards_keep_token_rows_separate() {
15510 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
15511 assert_eq!(
15512 activation_shard(&activations, 2, 8, 2, 1),
15513 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
15514 );
15515 }
15516
15517 #[test]
15518 fn expert_bank_selects_expert_major_code_and_scale_planes() {
15519 let expert_count = 2;
15520 let out_features = 128;
15521 let in_features = 128;
15522 let code_stride = out_features * in_features;
15523 let codes: Vec<u8> = (0..expert_count * code_stride)
15524 .map(|index| (index % 251) as u8)
15525 .collect();
15526 let scales = vec![1.0f32, 2.0];
15527 let bank = E4m3ExpertBank {
15528 codes: &codes,
15529 scales: &scales,
15530 expert_count,
15531 out_features,
15532 in_features,
15533 };
15534 bank.validate().unwrap();
15535 let expert = bank.expert(1).unwrap();
15536 assert_eq!(expert.codes, &codes[code_stride..]);
15537 assert_eq!(expert.scales, &[2.0]);
15538 }
15539
15540 #[test]
15541 fn expert_bank_rejects_non_positive_scale() {
15542 let codes = vec![0u8; 128 * 128];
15543 let scales = vec![0.0f32];
15544 let bank = E4m3ExpertBank {
15545 codes: &codes,
15546 scales: &scales,
15547 expert_count: 1,
15548 out_features: 128,
15549 in_features: 128,
15550 };
15551 assert!(bank.validate().unwrap_err().contains("non-positive"));
15552 }
15553
15554 #[test]
15555 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
15556 let expert_count = 2;
15557 let out_features = 256;
15558 let in_features = 128;
15559 let code_stride = out_features * in_features;
15560 let scale_stride = 2;
15561 let codes = (0..expert_count * code_stride)
15562 .map(|index| (index % 251) as u8)
15563 .collect::<Vec<_>>();
15564 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15565 let bank = E4m3ExpertBank {
15566 codes: &codes,
15567 scales: &scales,
15568 expert_count,
15569 out_features,
15570 in_features,
15571 };
15572
15573 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
15574 assert_eq!(rank.out_features, 128);
15575 assert_eq!(rank.in_features, 128);
15576 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15577 assert_eq!(rank.scales, vec![11.0, 21.0]);
15578 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
15579 assert_eq!(
15580 &rank.codes[128 * 128..],
15581 &codes[code_stride + 128 * 128..2 * code_stride]
15582 );
15583 assert_eq!(scale_stride, scales.len() / expert_count);
15584 }
15585
15586 #[test]
15587 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
15588 let expert_count = 2;
15589 let out_features = 128;
15590 let in_features = 256;
15591 let code_stride = out_features * in_features;
15592 let codes = (0..expert_count * code_stride)
15593 .map(|index| (index % 251) as u8)
15594 .collect::<Vec<_>>();
15595 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15596 let bank = E4m3ExpertBank {
15597 codes: &codes,
15598 scales: &scales,
15599 expert_count,
15600 out_features,
15601 in_features,
15602 };
15603
15604 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15605 assert_eq!(rank.out_features, 128);
15606 assert_eq!(rank.in_features, 128);
15607 assert_eq!(rank.k_blocks, Some(1));
15608 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15609 assert_eq!(rank.scales, vec![11.0, 21.0]);
15610 assert_eq!(&rank.codes[..128], &codes[128..256]);
15611 assert_eq!(
15612 &rank.codes[128 * 128..128 * 128 + 128],
15613 &codes[code_stride + 128..code_stride + 256]
15614 );
15615 }
15616
15617 #[test]
15618 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
15619 let expert_count = 2;
15620 let out_features = 256;
15621 let in_features = 512;
15622 let code_stride = out_features * in_features;
15623 let mut codes = vec![0u8; expert_count * code_stride];
15624 for expert in 0..expert_count {
15625 for row in 0..out_features {
15626 for block in 0..4 {
15627 let value = (expert * 80 + block * 16 + row % 16) as u8;
15628 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
15629 codes[start..start + FP8_BLOCK].fill(value);
15630 }
15631 }
15632 }
15633 let scales = vec![
15634 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,
15635 112.0, 113.0, 114.0,
15636 ];
15637 let bank = E4m3ExpertBank {
15638 codes: &codes,
15639 scales: &scales,
15640 expert_count,
15641 out_features,
15642 in_features,
15643 };
15644
15645 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15646 assert_eq!(rank.out_features, out_features);
15647 assert_eq!(rank.in_features, 256);
15648 assert_eq!(rank.k_blocks, Some(2));
15649 assert_eq!(rank.code_stride, out_features * 256);
15650 assert_eq!(rank.scale_stride, 4);
15651 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
15652 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
15653
15654 let block_stride = out_features * FP8_BLOCK;
15655 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
15656 assert!(
15657 rank.codes[block_stride..block_stride + FP8_BLOCK]
15658 .iter()
15659 .all(|&code| code == 48)
15660 );
15661 assert!(
15662 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
15663 .iter()
15664 .all(|&code| code == 112)
15665 );
15666 assert!(
15667 rank.codes
15668 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
15669 .iter()
15670 .all(|&code| code == 128)
15671 );
15672 }
15673
15674 #[test]
15675 fn automatic_parallel_policy_needs_only_one_device_set_not_layer_recipes() {
15676 assert_eq!(parse_auto_parallel_devices(None, None).unwrap(), None);
15677 assert_eq!(
15678 parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3")).unwrap(),
15679 Some(vec![0, 1, 2, 3])
15680 );
15681 assert!(parse_auto_parallel_devices(Some("auto"), None).is_err());
15682 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,1")).is_err());
15683 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3,4")).is_err());
15684 assert!(parse_auto_parallel_devices(Some("ep"), Some("0,1")).is_err());
15685 }
15686
15687 #[test]
15688 fn automatic_ep_device_router_flag_is_strict() {
15689 assert!(!parse_parallel_ep_device_router(None).unwrap());
15690 assert!(!parse_parallel_ep_device_router(Some("0")).unwrap());
15691 assert!(parse_parallel_ep_device_router(Some("1")).unwrap());
15692 assert!(parse_parallel_ep_device_router(Some("true")).is_err());
15693 }
15694
15695 #[test]
15696 fn automatic_ep_graph_flag_is_strict_and_defaults_off() {
15697 assert!(!parse_parallel_ep_graph(None).unwrap());
15698 assert!(!parse_parallel_ep_graph(Some("0")).unwrap());
15699 assert!(parse_parallel_ep_graph(Some("1")).unwrap());
15700 assert!(parse_parallel_ep_graph(Some("true")).is_err());
15701 }
15702
15703 #[test]
15704 fn automatic_ep_pair_down_flag_is_strict_and_defaults_off() {
15705 assert!(!parse_parallel_ep_pair_down(None).unwrap());
15706 assert!(!parse_parallel_ep_pair_down(Some("0")).unwrap());
15707 assert!(parse_parallel_ep_pair_down(Some("1")).unwrap());
15708 assert!(parse_parallel_ep_pair_down(Some("true")).is_err());
15709 }
15710
15711 #[test]
15712 fn automatic_ep_q8_activation_flag_is_strict() {
15713 assert!(!parse_parallel_ep_q8_act(None).unwrap());
15714 assert!(!parse_parallel_ep_q8_act(Some("0")).unwrap());
15715 assert!(parse_parallel_ep_q8_act(Some("1")).unwrap());
15716 assert!(parse_parallel_ep_q8_act(Some("true")).is_err());
15717 }
15718
15719 #[test]
15720 fn automatic_ep_q8_scope_is_explicit_and_strict() {
15721 assert_eq!(parse_parallel_ep_q8_scope(None).unwrap(), None);
15722 assert_eq!(
15723 parse_parallel_ep_q8_scope(Some("all")).unwrap(),
15724 Some(ParallelEpQ8Scope::All)
15725 );
15726 assert_eq!(
15727 parse_parallel_ep_q8_scope(Some("gate-up")).unwrap(),
15728 Some(ParallelEpQ8Scope::GateUp)
15729 );
15730 assert_eq!(
15731 parse_parallel_ep_q8_scope(Some("down")).unwrap(),
15732 Some(ParallelEpQ8Scope::Down)
15733 );
15734 assert!(parse_parallel_ep_q8_scope(Some("input")).is_err());
15735 }
15736
15737 #[test]
15738 fn automatic_ep_q8_gate_up_paired_is_parent_scoped_and_strict() {
15739 assert_eq!(parse_parallel_ep_q8_gu_paired(None).unwrap(), None);
15740 assert_eq!(parse_parallel_ep_q8_gu_paired(Some("")).unwrap(), None);
15741 assert_eq!(
15742 parse_parallel_ep_q8_gu_paired(Some("0")).unwrap(),
15743 Some(false)
15744 );
15745 assert_eq!(
15746 parse_parallel_ep_q8_gu_paired(Some("1")).unwrap(),
15747 Some(true)
15748 );
15749 assert!(parse_parallel_ep_q8_gu_paired(Some("paired")).is_err());
15750 assert!(parse_parallel_ep_q8_gu_paired(Some("true")).is_err());
15751
15752 assert!(!resolve_parallel_ep_q8_gu_paired(None, false, None).unwrap());
15753 assert!(resolve_parallel_ep_q8_gu_paired(None, true, None).unwrap());
15754 assert!(
15755 resolve_parallel_ep_q8_gu_paired(None, true, Some(ParallelEpQ8Scope::GateUp)).unwrap()
15756 );
15757 assert!(
15758 !resolve_parallel_ep_q8_gu_paired(None, true, Some(ParallelEpQ8Scope::Down)).unwrap()
15759 );
15760 assert!(!resolve_parallel_ep_q8_gu_paired(Some("0"), false, None).unwrap());
15761 assert!(!resolve_parallel_ep_q8_gu_paired(Some("0"), true, None).unwrap());
15762 assert!(resolve_parallel_ep_q8_gu_paired(Some("1"), false, None).is_err());
15763 assert!(
15764 resolve_parallel_ep_q8_gu_paired(Some("1"), true, Some(ParallelEpQ8Scope::Down))
15765 .is_err()
15766 );
15767 }
15768
15769 #[test]
15770 fn w4a16_device_ep_accepts_a_capacity_backed_active_prefix() {
15771 let width = 4096;
15772 assert_eq!(
15773 nvfp4_ep_active_input_values(160 * width, 44, width).unwrap(),
15774 44 * width
15775 );
15776 assert_eq!(
15777 nvfp4_ep_active_input_values(44 * width, 44, width).unwrap(),
15778 44 * width
15779 );
15780 assert!(nvfp4_ep_active_input_values(43 * width, 44, width).is_err());
15781 assert!(
15782 nvfp4_ep_active_input_values(160 * width, NVFP4_EP_DEVICE_BATCH_CAP + 1, width)
15783 .is_err()
15784 );
15785 }
15786
15787 #[test]
15788 fn step_ep_layer_specs_are_literal_and_fail_closed() {
15789 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
15790 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
15791 assert_eq!(
15792 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
15793 vec![StepEpLayerSpec {
15794 layer: 24,
15795 devices: vec![1, 2],
15796 }]
15797 );
15798 assert_eq!(
15799 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
15800 vec![
15801 StepEpLayerSpec {
15802 layer: 24,
15803 devices: vec![1, 2],
15804 },
15805 StepEpLayerSpec {
15806 layer: 25,
15807 devices: vec![1, 2],
15808 },
15809 StepEpLayerSpec {
15810 layer: 31,
15811 devices: vec![0, 2],
15812 },
15813 ]
15814 );
15815 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
15816 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
15817 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
15818 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
15819 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
15820 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
15821 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
15822 }
15823
15824 #[test]
15825 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
15826 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
15827 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
15828 assert_eq!(
15829 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
15830 vec![
15831 StepTpLayerSpec {
15832 layer: 24,
15833 devices: vec![1, 2],
15834 },
15835 StepTpLayerSpec {
15836 layer: 25,
15837 devices: vec![1, 2],
15838 },
15839 ]
15840 );
15841 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
15842 assert!(error.contains("MEMRA_STEP_TP"));
15843 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
15844 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
15845
15846 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
15847 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
15848 assert_eq!(all.first().unwrap().layer, 0);
15849 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
15850 let devices = (0..8).collect::<Vec<_>>();
15851 assert!(all.iter().all(|spec| spec.devices == devices));
15852 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
15853 }
15854}
15855
15856struct TokenGraphChild {
15868 #[allow(dead_code)]
15869 graph: cudarc::driver::CudaGraph,
15871 node: cudarc::driver::sys::CUgraphNode,
15872 ctx: cudarc::driver::sys::CUcontext,
15873}
15874
15875struct TokenGraphFaSite {
15879 ctx: cudarc::driver::sys::CUcontext,
15880 memset_o: cudarc::driver::sys::CUgraphNode,
15881 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
15882 fa: cudarc::driver::sys::CUgraphNode,
15883 combine: cudarc::driver::sys::CUgraphNode,
15884 window: usize,
15885 n_head: usize,
15886 n_head_kv: usize,
15887 head_dim: usize,
15888}
15889
15890pub struct TokenGraphBuilder {
15891 parent: cudarc::driver::sys::CUgraph,
15892 children: Vec<TokenGraphChild>,
15893 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
15896 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
15899 group: Option<(
15902 u32,
15903 Vec<cudarc::driver::sys::CUgraphNode>,
15904 Vec<cudarc::driver::sys::CUgraphNode>,
15905 )>,
15906}
15907
15908unsafe impl Send for TokenGraphBuilder {}
15910
15911impl TokenGraphBuilder {
15912 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
15913 use cudarc::driver::sys;
15914 let mut parent: sys::CUgraph = std::ptr::null_mut();
15915 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
15916 if r != sys::CUresult::CUDA_SUCCESS {
15917 return Err(format!("token graph create: {r:?}").into());
15918 }
15919 Ok(Self {
15920 parent,
15921 children: Vec::new(),
15922 frontier: Vec::new(),
15923 pending_detached: Vec::new(),
15924 group: None,
15925 })
15926 }
15927
15928 fn push_child(
15929 &mut self,
15930 graph: cudarc::driver::CudaGraph,
15931 parallel_group: Option<u32>,
15932 detached: bool,
15933 absorb: bool,
15934 ctx: cudarc::driver::sys::CUcontext,
15935 ) -> Result<(), Box<dyn std::error::Error>> {
15936 use cudarc::driver::sys;
15937 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
15941 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
15942 (state, Some(group)) => {
15943 if let Some((_, _, members)) = state.take() {
15945 self.frontier = members;
15946 }
15947 let base = self.frontier.clone();
15948 *state = Some((group, base.clone(), Vec::new()));
15949 base
15950 }
15951 (state, None) if detached => match state.as_ref() {
15952 Some((_, base, _)) => base.clone(),
15953 None => self.frontier.clone(),
15954 },
15955 (state, None) => {
15956 if let Some((_, _, members)) = state.take() {
15957 self.frontier = members;
15958 }
15959 let mut deps = self.frontier.clone();
15960 if absorb {
15961 deps.append(&mut self.pending_detached);
15962 }
15963 deps
15964 }
15965 };
15966 let mut node: sys::CUgraphNode = std::ptr::null_mut();
15967 let r = unsafe {
15968 sys::cuGraphAddChildGraphNode(
15969 &mut node,
15970 self.parent,
15971 if deps.is_empty() {
15972 std::ptr::null()
15973 } else {
15974 deps.as_ptr()
15975 },
15976 deps.len(),
15977 graph.cu_graph(),
15978 )
15979 };
15980 if r != sys::CUresult::CUDA_SUCCESS {
15981 return Err(format!("token graph child: {r:?}").into());
15982 }
15983 match (&mut self.group, parallel_group, detached) {
15984 (_, None, true) => self.pending_detached.push(node),
15985 (Some((_, _, members)), Some(_), _) => members.push(node),
15986 _ => self.frontier = vec![node],
15987 }
15988 self.children.push(TokenGraphChild { graph, node, ctx });
15989 Ok(())
15990 }
15991
15992 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
15993 use cudarc::driver::sys;
15994 if let Some((_, _, members)) = self.group.take() {
15995 self.frontier = members;
15996 }
15997 let mut fa_sites = Vec::new();
16000 for child in &self.children {
16001 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
16002 fa_sites.push(site);
16003 }
16004 }
16005 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
16006 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
16007 if r != sys::CUresult::CUDA_SUCCESS {
16008 return Err(format!("token graph instantiate: {r:?}").into());
16009 }
16010 Ok(TokenGraph {
16011 exec,
16012 parent: self.parent,
16013 _children: self.children,
16014 fa_sites,
16015 })
16016 }
16017}
16018
16019fn discover_fa_site(
16022 child_node: cudarc::driver::sys::CUgraphNode,
16023 ctx: cudarc::driver::sys::CUcontext,
16024) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
16025 use cudarc::driver::sys;
16026 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16027 if r == sys::CUresult::CUDA_SUCCESS {
16028 Ok(())
16029 } else {
16030 Err(format!("{what}: {r:?}").into())
16031 }
16032 }
16033 let mut graph: sys::CUgraph = std::ptr::null_mut();
16034 unsafe {
16035 cu_try(
16036 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
16037 "fa-site child GetGraph",
16038 )?;
16039 }
16040 let mut count: usize = 0;
16041 unsafe {
16042 cu_try(
16043 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
16044 "fa-site GetNodes(count)",
16045 )?;
16046 }
16047 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
16048 unsafe {
16049 cu_try(
16050 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
16051 "fa-site GetNodes",
16052 )?;
16053 }
16054 nodes.truncate(count);
16055 let node_type =
16056 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
16057 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
16058 unsafe {
16059 cu_try(
16060 sys::cuGraphNodeGetType(node, &mut ty),
16061 "fa-site NodeGetType",
16062 )?;
16063 }
16064 Ok(ty)
16065 };
16066 let memsets: Vec<sys::CUgraphNode> = {
16067 let mut v = Vec::new();
16068 for &node in &nodes {
16069 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
16070 v.push(node);
16071 }
16072 }
16073 v
16074 };
16075 if memsets.len() != 3 {
16076 return Ok(None);
16077 }
16078 let dependents =
16080 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
16081 let mut n: usize = 0;
16082 unsafe {
16083 cu_try(
16084 sys::cuGraphNodeGetDependentNodes_v2(
16085 node,
16086 std::ptr::null_mut(),
16087 std::ptr::null_mut(),
16088 &mut n,
16089 ),
16090 "fa-site GetDependentNodes(count)",
16091 )?;
16092 }
16093 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
16094 unsafe {
16095 cu_try(
16096 sys::cuGraphNodeGetDependentNodes_v2(
16097 node,
16098 v.as_mut_ptr(),
16099 std::ptr::null_mut(),
16100 &mut n,
16101 ),
16102 "fa-site GetDependentNodes",
16103 )?;
16104 }
16105 v.truncate(n);
16106 Ok(v)
16107 };
16108 let mut fa: Option<sys::CUgraphNode> = None;
16111 let mut last_memset: Option<sys::CUgraphNode> = None;
16112 for &ms in &memsets {
16113 for dep in dependents(ms)? {
16114 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16115 fa = Some(dep);
16116 last_memset = Some(ms);
16117 }
16118 }
16119 }
16120 let (Some(fa), Some(_last)) = (fa, last_memset) else {
16121 return Ok(None);
16122 };
16123 let mut combine: Option<sys::CUgraphNode> = None;
16124 for dep in dependents(fa)? {
16125 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
16126 combine = Some(dep);
16127 }
16128 }
16129 let Some(combine) = combine else {
16130 return Ok(None);
16131 };
16132 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16135 unsafe {
16136 cu_try(
16137 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
16138 "fa-site KernelNodeGetParams",
16139 )?;
16140 }
16141 let arg_i32 =
16142 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
16143 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
16144 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
16146 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16147 unsafe {
16148 cu_try(
16149 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16150 "fa-site MemsetNodeGetParams",
16151 )?;
16152 }
16153 Ok(mp.width)
16154 };
16155 let mut widest = memsets[0];
16156 for &ms in &memsets[1..] {
16157 if width_of(ms)? > width_of(widest)? {
16158 widest = ms;
16159 }
16160 }
16161 let memset_m: Vec<sys::CUgraphNode> =
16162 memsets.iter().copied().filter(|&m| m != widest).collect();
16163 Ok(Some(TokenGraphFaSite {
16164 ctx,
16165 memset_o: widest,
16166 memset_m: [memset_m[0], memset_m[1]],
16167 fa,
16168 combine,
16169 window: win as usize,
16170 n_head: nh as usize,
16171 n_head_kv: nhkv as usize,
16172 head_dim: hd as usize,
16173 }))
16174}
16175
16176pub struct TokenGraph {
16177 exec: cudarc::driver::sys::CUgraphExec,
16178 parent: cudarc::driver::sys::CUgraph,
16179 _children: Vec<TokenGraphChild>,
16180 fa_sites: Vec<TokenGraphFaSite>,
16181}
16182
16183unsafe impl Send for TokenGraph {}
16184
16185impl TokenGraph {
16186 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
16191 use cudarc::driver::sys;
16192 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
16193 if r == sys::CUresult::CUDA_SUCCESS {
16194 Ok(())
16195 } else {
16196 Err(format!("{what}: {r:?}").into())
16197 }
16198 }
16199 for site in &self.fa_sites {
16200 let layer_bucket = if site.window > 0 {
16201 bucket.min(site.window)
16202 } else {
16203 bucket
16204 };
16205 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
16206 let nsp = layer_bucket.div_ceil(sp).max(1);
16207 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16209 unsafe {
16210 cu_try(
16211 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
16212 "retarget fa GetParams",
16213 )?;
16214 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
16215 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
16216 params.gridDimY = nsp as u32;
16217 cu_try(
16218 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
16219 "retarget fa SetParams",
16220 )?;
16221 }
16222 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
16224 unsafe {
16225 cu_try(
16226 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
16227 "retarget combine GetParams",
16228 )?;
16229 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
16230 cu_try(
16231 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
16232 "retarget combine SetParams",
16233 )?;
16234 }
16235 let set_width =
16237 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
16238 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
16239 unsafe {
16240 cu_try(
16241 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
16242 "retarget memset GetParams",
16243 )?;
16244 }
16245 mp.width = width;
16246 unsafe {
16247 cu_try(
16248 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
16249 "retarget memset SetParams",
16250 )?;
16251 }
16252 Ok(())
16253 };
16254 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
16255 set_width(site.memset_m[0], site.n_head * nsp)?;
16256 set_width(site.memset_m[1], site.n_head * nsp)?;
16257 }
16258 Ok(())
16259 }
16260
16261 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
16262 use cudarc::driver::sys;
16263 let _main = e.gpu.enter_main()?;
16264 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
16265 if r != sys::CUresult::CUDA_SUCCESS {
16266 return Err(format!("token graph launch: {r:?}").into());
16267 }
16268 Ok(())
16269 }
16270}
16271
16272impl Drop for TokenGraph {
16273 fn drop(&mut self) {
16274 unsafe {
16275 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
16276 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
16277 }
16278 }
16279}
16280
16281std::thread_local! {
16282 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
16283 const { std::cell::RefCell::new(None) };
16284}
16285
16286pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
16288 let builder = TokenGraphBuilder::new()?;
16289 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
16290 Ok(())
16291}
16292
16293pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
16295 let builder = TOKEN_GRAPH_BUILDER
16296 .with(|cell| cell.borrow_mut().take())
16297 .ok_or("token graph build was not begun")?;
16298 builder.finish()
16299}
16300
16301pub fn token_graph_building() -> bool {
16303 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
16304}
16305
16306pub fn graph_section<F>(
16311 engine: &Engine,
16312 parallel_group: Option<u32>,
16313 f: F,
16314) -> Result<(), Box<dyn std::error::Error>>
16315where
16316 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16317{
16318 graph_section_opts(engine, parallel_group, false, false, f)
16319}
16320
16321pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16323where
16324 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16325{
16326 graph_section_opts(engine, None, false, true, f)
16327}
16328
16329pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
16332where
16333 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16334{
16335 graph_section_opts(engine, None, true, false, f)
16336}
16337
16338pub fn graph_section_opts<F>(
16339 engine: &Engine,
16340 parallel_group: Option<u32>,
16341 detached: bool,
16342 absorb: bool,
16343 f: F,
16344) -> Result<(), Box<dyn std::error::Error>>
16345where
16346 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
16347{
16348 let building = token_graph_building();
16349 if !building {
16350 let mut f = f;
16351 return f();
16352 }
16353 let (child, ctx) = {
16354 let _main = engine.gpu.enter_main()?;
16355 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
16356 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
16357 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
16358 return Err(format!("graph section ctx query: {r:?}").into());
16359 }
16360 let mut f = f;
16361 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
16364 (child, ctx)
16365 };
16366 TOKEN_GRAPH_BUILDER.with(|cell| {
16367 cell.borrow_mut()
16368 .as_mut()
16369 .expect("builder checked above")
16370 .push_child(child, parallel_group, detached, absorb, ctx)
16371 })
16372}