1use crate::Engine;
9use crate::mmq_ffi::{DeviceExpertCsr, ExpertCsr, Fp8GroupedWorkspace};
10use crate::parallel::{PRODUCT_MAX_CARDS, STEP37_TRUNK_LAYERS};
11use cudarc::driver::{CudaEvent, CudaSlice, DevicePtr, DeviceSlice, LaunchConfig, PushKernelArg};
12use std::ops::Range;
13
14#[allow(clippy::type_complexity)] static DETERM_PREV: std::sync::OnceLock<
18 std::sync::Mutex<std::collections::HashMap<(usize, usize), Vec<f32>>>,
19> = std::sync::OnceLock::new();
20
21const FP8_BLOCK: usize = 128;
22const NATIVE_P2P_PROBE_WORDS: &[usize] = &[4096, 16_384, 262_144, 16_777_216];
23const STEP_GROUPED_FP8_EXPERTS: usize = 288;
24const STEP_GROUPED_FP8_TOP_K: usize = 8;
25const STEP_GROUPED_FP8_WIDTH: usize = 1280;
26
27fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
28 if let Some(limit) = limit
29 && (!limit.is_finite() || limit <= 0.0)
30 {
31 return Err(format!(
32 "Step routed-expert activation limit must be positive and finite, got {limit}"
33 ));
34 }
35 Ok(())
36}
37
38pub(crate) fn routes_prestage_on() -> bool {
61 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
62 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
63}
64
65pub(crate) fn oproj_tail_on() -> bool {
83 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
84 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
85}
86thread_local! {
87 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
88 const { std::cell::Cell::new(None) };
89}
90thread_local! {
91 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
96}
97pub(crate) struct OprojTailScope(());
99pub(crate) fn oproj_tail_scope() -> OprojTailScope {
100 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
101 OprojTailScope(())
102}
103impl Drop for OprojTailScope {
104 fn drop(&mut self) {
105 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
106 OPROJ_TAIL_PENDING.with(|c| c.set(None));
108 }
109}
110thread_local! {
111 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
114}
115pub(crate) fn set_verify_tcol(c: Option<usize>) {
116 VERIFY_TCOL.with(|x| x.set(c));
117}
118pub(crate) fn take_verify_tcol() -> Option<usize> {
119 VERIFY_TCOL.with(|x| x.take())
120}
121
122pub(crate) fn tcol_oproj_on() -> bool {
129 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
130 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
131}
132thread_local! {
133 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
137 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
138}
139pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
140 TCOL_OPROJ_DEFER.with(|x| x.set(c));
141}
142pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
143 TCOL_OPROJ_DEFER.with(|x| x.take())
144}
145pub(crate) fn set_tcol_oproj_stashed() {
146 TCOL_OPROJ_STASHED.with(|x| x.set(true));
147}
148pub(crate) fn take_tcol_oproj_stashed() -> bool {
149 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
150}
151
152pub(crate) fn oproj_tail_eligible() -> bool {
153 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
154}
155pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
156 OPROJ_TAIL_PENDING.with(|c| c.take())
157}
158pub(crate) fn set_oproj_tail(v: (u64, u64)) {
159 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
160}
161
162pub(crate) fn rank0_merge_on() -> bool {
163 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
164 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
165}
166
167pub(crate) fn len_mirror_lazy_on() -> bool {
168 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
169 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
170}
171
172pub(crate) fn fence_memops_on() -> bool {
173 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
174 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
175}
176
177pub(crate) fn moe_direct_on() -> bool {
178 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
179 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
180}
181
182pub(crate) fn rows_tab_host(
212 parts_rank: &[[u64; 4]],
213 ctr_base: u64,
214 same_session: bool,
215 t: usize,
216) -> Vec<u64> {
217 let mut host = Vec::with_capacity(t * 6);
218 for (r, parts) in parts_rank.iter().enumerate().take(t) {
219 host.extend_from_slice(&[
220 parts[0],
221 parts[1],
222 parts[2],
223 parts[3],
224 if same_session {
225 ctr_base
226 } else {
227 ctr_base + (r as u64) * 4
228 },
229 if same_session {
230 (t - 1 - r) as u64
231 } else {
232 0u64
233 },
234 ]);
235 }
236 host
237}
238
239#[cfg(test)]
243pub(crate) fn retired_rows_tab_key(kp: u64, bp: u64, il: usize, t: usize) -> u64 {
244 kp.rotate_left(17)
245 .wrapping_add(bp)
246 .wrapping_add((il as u64) << 32)
247 .wrapping_add(t as u64)
248 .wrapping_add(1 << 63)
249}
250
251pub(crate) fn rows_tab_restage_on() -> bool {
252 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
253 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_RESTAGE").as_deref() != Ok("0"))
254}
255
256pub(crate) fn rows_tab_stale_scan() -> bool {
265 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
266 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_STALE_SCAN").as_deref() == Ok("1"))
267}
268
269pub(crate) static ROWS_TAB_ENGAGED: std::sync::atomic::AtomicU64 =
270 std::sync::atomic::AtomicU64::new(0);
271pub(crate) static ROWS_TAB_STALE: std::sync::atomic::AtomicU64 =
272 std::sync::atomic::AtomicU64::new(0);
273
274pub(crate) fn spec_fa2_on() -> bool {
275 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
276 crate::step37_door(&ENV, "MEMRA_SPEC_FA2")
277}
278thread_local! {
279 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
282 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
283}
284pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
285 SPEC_FA2_DEFER.with(|x| x.set(c));
286}
287pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
288 SPEC_FA2_DEFER.with(|x| x.take())
289}
290pub(crate) fn set_spec_fa2_stashed() {
291 SPEC_FA2_STASHED.with(|x| x.set(true));
292}
293pub(crate) fn take_spec_fa2_stashed() -> bool {
294 SPEC_FA2_STASHED.with(|x| x.replace(false))
295}
296
297pub(crate) fn sel_mirror_on() -> bool {
298 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
299 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
300}
301
302pub(crate) fn oproj_direct_on() -> bool {
303 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
304 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
305}
306
307fn door_default_on(name: &'static str) -> (bool, &'static str) {
346 let raw = std::env::var(name).ok();
347 door_default_on_value(name, raw.as_deref())
348}
349
350fn door_default_on_value(name: &str, value: Option<&str>) -> (bool, &'static str) {
355 match value {
356 Some("0") => (false, "env=0 (rollback seam)"),
357 Some("1") => (true, "env=1"),
358 None => (true, "default-on"),
359 Some(_) => {
360 eprintln!(
361 "[nvfp4-door] WARN {name} has an unrecognized value; only `0` and `1` are \
362 accepted and the DEFAULT-ON answer is kept. To roll back, set {name}=0."
363 );
364 (true, "default-on (unrecognized value ignored)")
365 }
366 }
367}
368
369pub(crate) fn bank_slot_major_on() -> bool {
389 bank_slot_major_source().0
390}
391
392pub(crate) fn bank_slot_major_source() -> (bool, &'static str) {
394 static ON: std::sync::OnceLock<(bool, &'static str)> = std::sync::OnceLock::new();
395 *ON.get_or_init(|| door_default_on("MEMRA_NVFP4_BANK_SM"))
396}
397
398pub(crate) fn sel_down8_on() -> bool {
422 sel_down8_source().0
423}
424
425pub(crate) fn sel_down8_source() -> (bool, &'static str) {
427 static ON: std::sync::OnceLock<(bool, &'static str)> = std::sync::OnceLock::new();
428 *ON.get_or_init(|| door_default_on("MEMRA_NVFP4_SEL_DOWN8"))
429}
430
431pub(crate) fn raw_copy_bytes(
432 dst: u64,
433 src: u64,
434 bytes: usize,
435 engine: &Engine,
436) -> Result<(), Box<dyn std::error::Error>> {
437 use cudarc::driver::sys;
438 let r = unsafe {
439 sys::cuMemcpyAsync(
440 dst as sys::CUdeviceptr,
441 src as sys::CUdeviceptr,
442 bytes,
443 engine.stream().cu_stream() as sys::CUstream,
444 )
445 };
446 if r == sys::CUresult::CUDA_SUCCESS {
447 Ok(())
448 } else {
449 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
452 eprintln!(
453 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
454 std::backtrace::Backtrace::force_capture()
455 );
456 }
457 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
458 }
459}
460
461pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
462 let silu = gate / (1.0 + (-gate).exp());
463 match limit {
464 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
465 None => silu * up,
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, Eq)]
470struct ExpertOwnerRoutes {
471 rank: usize,
472 selected: Vec<usize>,
473 token_rows: Vec<usize>,
474 global_pairs: Vec<usize>,
475}
476
477#[allow(clippy::manual_is_multiple_of)] fn partition_expert_owner_routes(
479 expert_count: usize,
480 ranks: usize,
481 tokens: usize,
482 experts_per_token: usize,
483 selected: &[usize],
484) -> Result<Vec<ExpertOwnerRoutes>, String> {
485 if expert_count == 0
486 || ranks == 0
487 || tokens == 0
488 || experts_per_token == 0
489 || expert_count % ranks != 0
490 {
491 return Err(format!(
492 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
493 tokens={tokens} experts_per_token={experts_per_token}"
494 ));
495 }
496 let pairs = tokens
497 .checked_mul(experts_per_token)
498 .ok_or("expert-owner route count overflow")?;
499 if selected.len() != pairs {
500 return Err(format!(
501 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
502 selected.len()
503 ));
504 }
505 let per_rank = expert_count / ranks;
506 let mut owners = (0..ranks)
507 .map(|rank| ExpertOwnerRoutes {
508 rank,
509 selected: Vec::new(),
510 token_rows: Vec::new(),
511 global_pairs: Vec::new(),
512 })
513 .collect::<Vec<_>>();
514 for (pair, &expert) in selected.iter().enumerate() {
515 if expert >= expert_count {
516 return Err(format!(
517 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
518 ));
519 }
520 let rank = expert / per_rank;
521 owners[rank].selected.push(expert - rank * per_rank);
522 owners[rank].token_rows.push(pair / experts_per_token);
523 owners[rank].global_pairs.push(pair);
524 }
525 Ok(owners)
526}
527
528fn validate_step_grouped_owner_routes(
529 expert_count: usize,
530 tokens: usize,
531 selected: &[usize],
532) -> Result<usize, String> {
533 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
534 return Err(format!(
535 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
536 experts={expert_count} tokens={tokens}",
537 STEP_GROUPED_FP8_EXPERTS
538 ));
539 }
540 let pairs = tokens
541 .checked_mul(STEP_GROUPED_FP8_TOP_K)
542 .ok_or("official Step owner-grouped FP8 route count overflow")?;
543 if selected.len() != pairs {
544 return Err(format!(
545 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
546 selected.len(),
547 STEP_GROUPED_FP8_TOP_K,
548 ));
549 }
550 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
551 let mut unique = routes.to_vec();
552 unique.sort_unstable();
553 unique.dedup();
554 if unique.len() != STEP_GROUPED_FP8_TOP_K {
555 return Err(format!(
556 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
557 {routes:?}"
558 ));
559 }
560 }
561 Ok(pairs)
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565struct WeightedRouteCombineShape {
566 pairs: usize,
567 max_pairs: usize,
568}
569
570fn validate_weighted_route_combine(
571 width: usize,
572 experts_per_token: usize,
573 max_tokens: usize,
574 tokens: usize,
575 owner_global_pairs: &[&[usize]],
576 route_weights: &[f32],
577) -> Result<WeightedRouteCombineShape, String> {
578 if width == 0
579 || experts_per_token == 0
580 || max_tokens == 0
581 || tokens == 0
582 || tokens > max_tokens
583 || width > i32::MAX as usize
584 || experts_per_token > i32::MAX as usize
585 || tokens > i32::MAX as usize
586 {
587 return Err(format!(
588 "invalid weighted route combine geometry width={width} experts_per_token=\
589 {experts_per_token} tokens={tokens}/{max_tokens}"
590 ));
591 }
592 let pairs = tokens
593 .checked_mul(experts_per_token)
594 .ok_or("weighted route combine pair count overflow")?;
595 let max_pairs = max_tokens
596 .checked_mul(experts_per_token)
597 .ok_or("weighted route combine capacity overflow")?;
598 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
599 return Err(format!(
600 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
601 route_weights.len()
602 ));
603 }
604 let mut seen = vec![false; pairs];
605 let mut observed = 0usize;
606 for pairs_for_owner in owner_global_pairs {
607 observed = observed
608 .checked_add(pairs_for_owner.len())
609 .ok_or("weighted route combine observed pair count overflow")?;
610 for &pair in *pairs_for_owner {
611 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
612 return Err(format!(
613 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
614 ));
615 }
616 }
617 }
618 if observed != pairs || seen.iter().any(|present| !present) {
619 return Err(format!(
620 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
621 ));
622 }
623 Ok(WeightedRouteCombineShape { pairs, max_pairs })
624}
625
626fn cache_rank_rows(
627 rows: &[u8],
628 tokens: usize,
629 local_token_bytes: usize,
630 ranks: usize,
631 rank: usize,
632) -> Result<Vec<u8>, String> {
633 if ranks == 0 || rank >= ranks {
634 return Err(format!(
635 "TP cache rank {rank} is outside a {ranks}-rank layout"
636 ));
637 }
638 let global_token_bytes = local_token_bytes
639 .checked_mul(ranks)
640 .ok_or("TP cache global token-byte overflow")?;
641 let expected = tokens
642 .checked_mul(global_token_bytes)
643 .ok_or("TP cache row-byte overflow")?;
644 if rows.len() != expected {
645 return Err(format!(
646 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
647 rows.len()
648 ));
649 }
650 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
651 for token in 0..tokens {
652 let start = token * global_token_bytes + rank * local_token_bytes;
653 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
654 }
655 Ok(shard)
656}
657
658fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
659 match value {
660 None | Some("") | Some("0") => Ok(false),
661 Some("1") => Ok(true),
662 Some(value) => Err(format!(
663 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
664 )),
665 }
666}
667
668pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
669 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
670}
671
672fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
673 match value {
674 None | Some("") | Some("0") => Ok(false),
675 Some("1") => Ok(true),
676 Some(value) => Err(format!(
677 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
678 )),
679 }
680}
681
682pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
683 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
684}
685
686fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
687 match value {
688 None | Some("") | Some("0") => Ok(false),
689 Some("1") => Ok(true),
690 Some(value) => Err(format!(
691 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
692 )),
693 }
694}
695
696fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
697 match value {
698 None | Some("") | Some("0") => Ok(false),
699 Some("1") => Ok(true),
700 Some(value) => Err(format!(
701 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
702 )),
703 }
704}
705
706pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
709 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
710}
711
712pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
713 parse_step_ep_device_arithmetic(
714 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
715 .ok()
716 .as_deref(),
717 )
718}
719
720fn parse_step_tp_f32_mirror(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_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
726 )),
727 }
728}
729
730pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
731 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
732}
733
734fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
735 match value {
736 None | Some("") | Some("0") => Ok(false),
737 Some("1") => Ok(true),
738 Some(value) => Err(format!(
739 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
740 )),
741 }
742}
743
744pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
749 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
750}
751
752fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
753 match value {
754 None | Some("") | Some("0") => Ok(false),
755 Some("1") => Ok(true),
756 Some(value) => Err(format!(
757 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
758 )),
759 }
760}
761
762fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
763 match value {
764 None | Some("") | Some("0") => Ok(false),
765 Some("1") => Ok(true),
766 Some(value) => Err(format!(
767 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
768 )),
769 }
770}
771
772pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
776 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
777}
778
779fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
780 match value {
781 None | Some("") | Some("0") => Ok(false),
782 Some("1") => Ok(true),
783 Some(value) => Err(format!(
784 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
785 )),
786 }
787}
788
789fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
790 match value {
791 None | Some("") | Some("0") => Ok(false),
792 Some("1") => Ok(true),
793 Some(value) => Err(format!(
794 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
795 )),
796 }
797}
798
799pub fn step_tp_dcw_enabled() -> Result<bool, String> {
804 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
805}
806
807pub fn step_tp_graph_enabled() -> Result<bool, String> {
812 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
813}
814
815fn step_tp_graph_headroom_ok(e: &Engine) -> bool {
820 let ok = crate::spec::graph_launch_headroom_ok(e);
821 if !ok {
822 static NOTED: std::sync::Once = std::sync::Once::new();
823 NOTED.call_once(|| crate::spec::graph_replay_suspended_note("step-tp-routes"));
824 }
825 ok
826}
827
828pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
832 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
833}
834
835#[derive(Debug, Clone, PartialEq, Eq)]
836pub struct StepEpLayerSpec {
837 pub layer: usize,
838 pub devices: Vec<usize>,
839}
840
841pub type StepTpLayerSpec = StepEpLayerSpec;
842
843fn parse_auto_parallel_devices(
847 mode: Option<&str>,
848 raw_devices: Option<&str>,
849) -> Result<Option<Vec<usize>>, String> {
850 let mode = match mode {
851 None | Some("") | Some("0") | Some("off") => return Ok(None),
852 Some("auto") => "auto",
853 Some(value) => {
854 return Err(format!(
855 "MEMRA_PARALLEL={value:?} is invalid; expected off or auto"
856 ));
857 }
858 };
859 let raw = raw_devices.ok_or_else(|| {
860 format!("{mode} parallel placement requires MEMRA_PARALLEL_DEVICES=DEVICE,DEVICE[...]")
861 })?;
862 let devices =
863 raw.split(',')
864 .map(|device| {
865 device.trim().parse::<usize>().map_err(|_| {
866 format!("MEMRA_PARALLEL_DEVICES entry {device:?} is not an integer")
867 })
868 })
869 .collect::<Result<Vec<_>, _>>()?;
870 if !(2..=crate::parallel::AUTO_PARALLEL_MAX_CARDS).contains(&devices.len()) {
871 return Err(format!(
872 "MEMRA_PARALLEL=auto requires 2..={} devices, got {}",
873 crate::parallel::AUTO_PARALLEL_MAX_CARDS,
874 devices.len()
875 ));
876 }
877 let mut unique = devices.clone();
878 unique.sort_unstable();
879 unique.dedup();
880 if unique.len() != devices.len() {
881 return Err(format!(
882 "MEMRA_PARALLEL_DEVICES must be distinct, got {devices:?}"
883 ));
884 }
885 Ok(Some(devices))
886}
887
888pub fn auto_parallel_devices() -> Result<Option<Vec<usize>>, String> {
889 parse_auto_parallel_devices(
890 std::env::var("MEMRA_PARALLEL").ok().as_deref(),
891 std::env::var("MEMRA_PARALLEL_DEVICES").ok().as_deref(),
892 )
893}
894
895fn parse_parallel_ep_device_router(value: Option<&str>) -> Result<bool, String> {
896 match value {
897 None | Some("") | Some("0") => Ok(false),
898 Some("1") => Ok(true),
899 Some(value) => Err(format!(
900 "MEMRA_PARALLEL_EP_DEVICE_ROUTER={value:?} is invalid; expected 0 or 1"
901 )),
902 }
903}
904
905pub fn parallel_ep_device_router_enabled() -> Result<bool, String> {
906 parse_parallel_ep_device_router(
907 std::env::var("MEMRA_PARALLEL_EP_DEVICE_ROUTER")
908 .ok()
909 .as_deref(),
910 )
911}
912
913fn parse_parallel_ep_pair_down(value: Option<&str>) -> Result<bool, String> {
914 match value {
915 None | Some("") | Some("0") => Ok(false),
916 Some("1") => Ok(true),
917 Some(value) => Err(format!(
918 "MEMRA_PARALLEL_EP_PAIR_DOWN={value:?} is invalid; expected 0 or 1"
919 )),
920 }
921}
922
923pub fn parallel_ep_pair_down_enabled() -> Result<bool, String> {
924 parse_parallel_ep_pair_down(std::env::var("MEMRA_PARALLEL_EP_PAIR_DOWN").ok().as_deref())
925}
926
927fn parse_parallel_ep_q8_act(value: Option<&str>) -> Result<bool, String> {
928 match value {
929 None | Some("") | Some("0") => Ok(false),
930 Some("1") => Ok(true),
931 Some(value) => Err(format!(
932 "MEMRA_PARALLEL_EP_Q8_ACT={value:?} is invalid; expected 0 or 1"
933 )),
934 }
935}
936
937pub fn parallel_ep_q8_act_enabled() -> Result<bool, String> {
938 parse_parallel_ep_q8_act(std::env::var("MEMRA_PARALLEL_EP_Q8_ACT").ok().as_deref())
939}
940
941#[derive(Clone, Copy, Debug, PartialEq, Eq)]
942pub(crate) enum ParallelEpQ8Scope {
943 All,
944 GateUp,
945 Down,
946}
947
948impl ParallelEpQ8Scope {
949 fn label(self) -> &'static str {
950 match self {
951 Self::All => "all",
952 Self::GateUp => "gate-up",
953 Self::Down => "down",
954 }
955 }
956}
957
958fn parse_parallel_ep_q8_scope(value: Option<&str>) -> Result<Option<ParallelEpQ8Scope>, String> {
959 match value {
960 None | Some("") => Ok(None),
961 Some("all") => Ok(Some(ParallelEpQ8Scope::All)),
962 Some("gate-up") => Ok(Some(ParallelEpQ8Scope::GateUp)),
963 Some("down") => Ok(Some(ParallelEpQ8Scope::Down)),
964 Some(value) => Err(format!(
965 "MEMRA_PARALLEL_EP_Q8_SCOPE={value:?} is invalid; expected all, gate-up, or down"
966 )),
967 }
968}
969
970pub(crate) fn parallel_ep_q8_scope() -> Result<Option<ParallelEpQ8Scope>, String> {
971 parse_parallel_ep_q8_scope(std::env::var("MEMRA_PARALLEL_EP_Q8_SCOPE").ok().as_deref())
972}
973
974pub(crate) fn parallel_ep_q8_gu_paired_enabled(
977 q8_active: bool,
978 scope: Option<ParallelEpQ8Scope>,
979) -> bool {
980 q8_active && scope != Some(ParallelEpQ8Scope::Down)
981}
982
983fn parse_step_layer_specs(
984 flag: &str,
985 value: Option<&str>,
986 allow_full_model: bool,
987) -> Result<Vec<StepEpLayerSpec>, String> {
988 let trunk = allow_full_model.then_some(STEP37_TRUNK_LAYERS);
989 parse_layer_specs_for_trunk(flag, value, trunk)
990}
991
992pub(crate) fn refuse_door_composition(
1000 primary: &str,
1001 table: &[(&str, &str)],
1002 armed: impl Fn(&str) -> bool,
1003) -> Result<(), String> {
1004 for (flag, why) in table {
1005 if armed(flag) {
1006 return Err(format!(
1007 "{primary} + {flag}: unproven composition, refused ({why})"
1008 ));
1009 }
1010 }
1011 Ok(())
1012}
1013
1014pub(crate) fn parse_layer_specs_for_trunk(
1019 flag: &str,
1020 value: Option<&str>,
1021 full_model_trunk: Option<usize>,
1022) -> Result<Vec<StepEpLayerSpec>, String> {
1023 let Some(value) = value else {
1024 return Ok(Vec::new());
1025 };
1026 if value.is_empty() || value == "0" {
1027 return Ok(Vec::new());
1028 }
1029
1030 let mut specs = Vec::new();
1031 for item in value.split(';') {
1032 let (layers, devices) = item.split_once('@').ok_or_else(|| {
1033 let layers = if full_model_trunk.is_some() {
1034 "LAYER[-LAYER] or all"
1035 } else {
1036 "LAYER[-LAYER]"
1037 };
1038 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
1039 })?;
1040 let (first, last) = if layers == "all" {
1041 let Some(trunk) = full_model_trunk else {
1042 return Err(format!(
1043 "{flag} does not support the full-model shorthand; assign routed layers \
1044 explicitly"
1045 ));
1046 };
1047 (0, trunk - 1)
1048 } else {
1049 match layers.split_once('-') {
1050 Some((first, last)) => {
1051 let first = first
1052 .parse::<usize>()
1053 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
1054 let last = last
1055 .parse::<usize>()
1056 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
1057 if first > last {
1058 return Err(format!("{flag} layer range {first}-{last} is reversed"));
1059 }
1060 if last - first + 1 > 128 {
1061 return Err(format!(
1062 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
1063 ));
1064 }
1065 (first, last)
1066 }
1067 None => {
1068 let layer = layers
1069 .parse::<usize>()
1070 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
1071 (layer, layer)
1072 }
1073 }
1074 };
1075 let devices = devices
1076 .split(',')
1077 .map(|device| {
1078 device
1079 .parse::<usize>()
1080 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
1081 })
1082 .collect::<Result<Vec<_>, _>>()?;
1083 if !(2..=8).contains(&devices.len()) {
1084 return Err(format!(
1085 "{flag} requires 2..=8 devices, got {}",
1086 devices.len()
1087 ));
1088 }
1089 let mut unique = devices.clone();
1090 unique.sort_unstable();
1091 unique.dedup();
1092 if unique.len() != devices.len() {
1093 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
1094 }
1095 for layer in first..=last {
1096 if specs
1097 .iter()
1098 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
1099 {
1100 return Err(format!("{flag} assigns layer {layer} more than once"));
1101 }
1102 specs.push(StepEpLayerSpec {
1103 layer,
1104 devices: devices.clone(),
1105 });
1106 }
1107 }
1108 Ok(specs)
1109}
1110
1111pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
1112 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
1113}
1114
1115pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
1116 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
1117}
1118
1119pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
1120 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
1121}
1122
1123pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
1124 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
1125}
1126
1127#[derive(Clone, Copy)]
1128pub struct E4m3BlockMatrix<'a> {
1129 pub codes: &'a [u8],
1130 pub scales: &'a [f32],
1131 pub out_features: usize,
1132 pub in_features: usize,
1133}
1134
1135impl E4m3BlockMatrix<'_> {
1136 fn validate(&self) -> Result<(), String> {
1137 let code_count = self
1138 .out_features
1139 .checked_mul(self.in_features)
1140 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
1141 if self.codes.len() != code_count {
1142 return Err(format!(
1143 "E4M3 code count {} != {}x{} ({code_count})",
1144 self.codes.len(),
1145 self.out_features,
1146 self.in_features,
1147 ));
1148 }
1149 let scale_count =
1150 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1151 if self.scales.len() != scale_count {
1152 return Err(format!(
1153 "E4M3 scale count {} != {scale_count} for {}x{}",
1154 self.scales.len(),
1155 self.out_features,
1156 self.in_features,
1157 ));
1158 }
1159 if !self
1160 .scales
1161 .iter()
1162 .all(|scale| scale.is_finite() && *scale > 0.0)
1163 {
1164 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
1165 }
1166 Ok(())
1167 }
1168}
1169
1170#[derive(Clone, Copy)]
1171pub struct E4m3ExpertBank<'a> {
1172 pub codes: &'a [u8],
1173 pub scales: &'a [f32],
1174 pub expert_count: usize,
1175 pub out_features: usize,
1176 pub in_features: usize,
1177}
1178
1179impl E4m3ExpertBank<'_> {
1180 fn validate(&self) -> Result<(), String> {
1181 if self.expert_count == 0 {
1182 return Err("E4M3 expert bank is empty".to_string());
1183 }
1184 let code_stride = self
1185 .out_features
1186 .checked_mul(self.in_features)
1187 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
1188 let code_count = self
1189 .expert_count
1190 .checked_mul(code_stride)
1191 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
1192 if self.codes.len() != code_count {
1193 return Err(format!(
1194 "E4M3 expert code count {} != {}x{} ({code_count})",
1195 self.codes.len(),
1196 self.expert_count,
1197 code_stride,
1198 ));
1199 }
1200 let scale_stride =
1201 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1202 let scale_count = self
1203 .expert_count
1204 .checked_mul(scale_stride)
1205 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
1206 if self.scales.len() != scale_count {
1207 return Err(format!(
1208 "E4M3 expert scale count {} != {}x{} ({scale_count})",
1209 self.scales.len(),
1210 self.expert_count,
1211 scale_stride,
1212 ));
1213 }
1214 if !self
1215 .scales
1216 .iter()
1217 .all(|scale| scale.is_finite() && *scale > 0.0)
1218 {
1219 return Err(
1220 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
1221 );
1222 }
1223 Ok(())
1224 }
1225
1226 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
1227 if expert >= self.expert_count {
1228 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
1229 }
1230 let code_stride = self.out_features * self.in_features;
1231 let scale_stride =
1232 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
1233 Ok(E4m3BlockMatrix {
1234 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
1235 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
1236 out_features: self.out_features,
1237 in_features: self.in_features,
1238 })
1239 }
1240}
1241
1242pub struct ColumnParallelResult {
1243 pub gathered: Vec<f32>,
1244 pub rank_outputs: Vec<Vec<f32>>,
1245}
1246
1247pub struct RowParallelResult {
1248 pub reduced: Vec<f32>,
1249 pub rank_partials: Vec<Vec<f32>>,
1250}
1251
1252#[derive(Clone, Copy)]
1253pub struct Bf16Matrix<'a> {
1254 pub bytes: &'a [u8],
1255 pub out_features: usize,
1256 pub in_features: usize,
1257}
1258
1259impl Bf16Matrix<'_> {
1260 pub fn validate(&self) -> Result<(), String> {
1261 if self.out_features == 0 || self.in_features == 0 {
1262 return Err("BF16 matrix dimensions must be nonzero".into());
1263 }
1264 let expected = self
1265 .out_features
1266 .checked_mul(self.in_features)
1267 .and_then(|values| values.checked_mul(2))
1268 .ok_or("BF16 matrix byte count overflow")?;
1269 if self.bytes.len() != expected {
1270 return Err(format!(
1271 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
1272 self.bytes.len(),
1273 self.out_features,
1274 self.in_features,
1275 ));
1276 }
1277 Ok(())
1278 }
1279}
1280
1281struct ResidentE4m3Rank {
1282 codes: CudaSlice<u8>,
1283 scales: CudaSlice<f32>,
1284 out_features: usize,
1285 in_features: usize,
1286}
1287
1288enum ResidentBf16Weight {
1289 Bf16(CudaSlice<u8>),
1290 F32(CudaSlice<f32>),
1291}
1292
1293impl ResidentBf16Weight {
1294 fn ordinal(&self) -> usize {
1295 match self {
1296 Self::Bf16(bytes) => bytes.ordinal(),
1297 Self::F32(values) => values.ordinal(),
1298 }
1299 }
1300}
1301
1302struct ResidentBf16Rank {
1303 weight: ResidentBf16Weight,
1304 out_features: usize,
1305 in_features: usize,
1306 q8: Option<CudaSlice<u8>>,
1309}
1310
1311pub struct ResidentColumnParallel {
1312 ranks: Vec<ResidentE4m3Rank>,
1313 out_features: usize,
1314 in_features: usize,
1315}
1316
1317pub struct ResidentRowParallel {
1318 ranks: Vec<ResidentE4m3Rank>,
1319 out_features: usize,
1320 in_features: usize,
1321}
1322
1323pub struct ResidentBf16ColumnParallel {
1324 ranks: Vec<ResidentBf16Rank>,
1325 out_features: usize,
1326 in_features: usize,
1327 canonical_chunk_rows: Option<usize>,
1328}
1329
1330pub struct ResidentBf16RowParallel {
1331 ranks: Vec<ResidentBf16Rank>,
1332 out_features: usize,
1333 in_features: usize,
1334}
1335
1336pub struct ResidentStepBf16RowParallel {
1337 ranks: Vec<Vec<ResidentBf16Rank>>,
1338 out_features: usize,
1339 in_features: usize,
1340 canonical_chunk_cols: usize,
1341}
1342
1343pub struct ResidentSigmoidTopKRouter {
1345 weight: CudaSlice<f32>,
1346 correction_bias: CudaSlice<f32>,
1347 active: CudaSlice<u8>,
1348 root_device: usize,
1349 input_width: usize,
1350 expert_count: usize,
1351 experts_per_token: usize,
1352 active_count: usize,
1353 scaling_factor: f32,
1354 route_norm: bool,
1355}
1356
1357pub struct SigmoidTopKHostOutput {
1358 pub logits: Vec<f32>,
1359 pub selected: Vec<u32>,
1360 pub weights: Vec<f32>,
1361}
1362
1363pub struct ResidentReplicatedBf16SwiGlu {
1365 gate: Vec<ResidentBf16Rank>,
1366 up: Vec<ResidentBf16Rank>,
1367 down: Vec<ResidentBf16Rank>,
1368 input_width: usize,
1369 intermediate_width: usize,
1370}
1371
1372pub struct ResidentReplicatedDeviceRows {
1377 ranks: Vec<CudaSlice<f32>>,
1378 tokens: usize,
1379 width: usize,
1380}
1381
1382impl ResidentReplicatedDeviceRows {
1383 pub fn tokens(&self) -> usize {
1384 self.tokens
1385 }
1386
1387 pub fn width(&self) -> usize {
1388 self.width
1389 }
1390
1391 pub fn ranks(&self) -> usize {
1392 self.ranks.len()
1393 }
1394}
1395
1396pub fn moe_residual_host(
1398 residual: &[f32],
1399 routed: &[f32],
1400 shared: &[f32],
1401) -> Result<Vec<f32>, String> {
1402 if residual.len() != routed.len() || residual.len() != shared.len() {
1403 return Err(format!(
1404 "MoE residual lengths residual={} routed={} shared={}",
1405 residual.len(),
1406 routed.len(),
1407 shared.len()
1408 ));
1409 }
1410 let ffn = routed
1411 .iter()
1412 .zip(shared)
1413 .map(|(&routed, &shared)| routed + shared)
1414 .collect::<Vec<_>>();
1415 Ok(residual
1416 .iter()
1417 .zip(ffn)
1418 .map(|(&residual, ffn)| residual + ffn)
1419 .collect())
1420}
1421
1422pub use memra_kv::{
1423 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1424};
1425
1426pub struct ResidentTpExpert {
1432 gate: ResidentColumnParallel,
1433 up: ResidentColumnParallel,
1434 down: ResidentRowParallel,
1435 input_width: usize,
1436 expert_width: usize,
1437}
1438
1439struct ResidentE4m3ExpertBankRank {
1440 codes: CudaSlice<u8>,
1441 scales: CudaSlice<f32>,
1442 expert_range: Range<usize>,
1443 out_features: usize,
1444 in_features: usize,
1445 code_stride: usize,
1446 scale_stride: usize,
1447 k_blocks: Option<usize>,
1450}
1451
1452struct PackedE4m3ExpertBankRank {
1453 codes: Vec<u8>,
1454 scales: Vec<f32>,
1455 expert_range: Range<usize>,
1456 out_features: usize,
1457 in_features: usize,
1458 code_stride: usize,
1459 scale_stride: usize,
1460 k_blocks: Option<usize>,
1461}
1462
1463struct ResidentEpRank {
1464 gate: ResidentE4m3ExpertBankRank,
1465 up: ResidentE4m3ExpertBankRank,
1466 down: ResidentE4m3ExpertBankRank,
1467}
1468
1469pub struct ResidentExpertParallel {
1476 ranks: Vec<ResidentEpRank>,
1477 expert_count: usize,
1478 input_width: usize,
1479 expert_width: usize,
1480}
1481
1482pub struct StepGroupedFp8ProjectionOutput {
1487 pub gate: Vec<f32>,
1488 pub up: Vec<f32>,
1489 pub down: Vec<f32>,
1490}
1491
1492pub struct PreparedStepGroupedFp8Gate {
1497 device: usize,
1498 gate: ResidentE4m3ExpertBankRank,
1499 up: ResidentE4m3ExpertBankRank,
1500 down: ResidentE4m3ExpertBankRank,
1501 input: CudaSlice<f32>,
1502 route_csr: DeviceExpertCsr,
1503 down_csr: DeviceExpertCsr,
1504 gate_workspace: Fp8GroupedWorkspace,
1505 up_workspace: Fp8GroupedWorkspace,
1506 down_workspace: Fp8GroupedWorkspace,
1507 activation: CudaSlice<f32>,
1508 activation_limit: Option<f32>,
1509 tokens: usize,
1510 pairs: usize,
1511}
1512
1513impl PreparedStepGroupedFp8Gate {
1514 pub fn tokens(&self) -> usize {
1515 self.tokens
1516 }
1517
1518 pub fn pairs(&self) -> usize {
1519 self.pairs
1520 }
1521}
1522
1523struct PreparedStepGroupedExpertOwner {
1524 rank: usize,
1525 global_pairs: Vec<usize>,
1526 route_csr: DeviceExpertCsr,
1527 down_csr: DeviceExpertCsr,
1528 gate_workspace: Fp8GroupedWorkspace,
1529 up_workspace: Fp8GroupedWorkspace,
1530 down_workspace: Fp8GroupedWorkspace,
1531 activation: CudaSlice<f32>,
1532}
1533
1534struct StepGroupedExpertOwnerSchedule {
1535 global_pairs: Vec<usize>,
1536 route_csr: ExpertCsr,
1537 down_csr: ExpertCsr,
1538}
1539
1540pub struct PreparedStepGroupedExpertParallelGate {
1546 rank_inputs: Vec<CudaSlice<f32>>,
1547 owners: Vec<PreparedStepGroupedExpertOwner>,
1548 activation_limit: Option<f32>,
1549 tokens: usize,
1550 pairs: usize,
1551 max_tokens: usize,
1552 max_pairs: usize,
1553 input_width: usize,
1554 expert_width: usize,
1555 generation: u64,
1556 executed_generation: Option<u64>,
1557 ready: bool,
1558}
1559
1560impl PreparedStepGroupedExpertParallelGate {
1561 pub fn tokens(&self) -> usize {
1562 self.tokens
1563 }
1564
1565 pub fn pairs(&self) -> usize {
1566 self.pairs
1567 }
1568
1569 pub fn max_tokens(&self) -> usize {
1570 self.max_tokens
1571 }
1572
1573 pub fn input_width(&self) -> usize {
1574 self.input_width
1575 }
1576
1577 pub fn expert_width(&self) -> usize {
1578 self.expert_width
1579 }
1580
1581 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1582 validate_step_expert_activation_limit(limit)?;
1583 self.activation_limit = limit;
1584 self.executed_generation = None;
1585 Ok(())
1586 }
1587
1588 pub fn active_owners(&self) -> usize {
1589 self.owners
1590 .iter()
1591 .filter(|owner| !owner.global_pairs.is_empty())
1592 .count()
1593 }
1594
1595 pub fn owner_pair_counts(&self) -> Vec<usize> {
1596 self.owners
1597 .iter()
1598 .map(|owner| owner.global_pairs.len())
1599 .collect()
1600 }
1601
1602 pub fn generation(&self) -> u64 {
1603 self.generation
1604 }
1605}
1606
1607struct PreparedPeerWeightedRouteOwner {
1608 token_rows: CudaSlice<i32>,
1609 slots: CudaSlice<i32>,
1610 weights: CudaSlice<f32>,
1611 active_pairs: usize,
1612}
1613
1614pub struct PreparedPeerWeightedRouteCombine {
1620 root_device: usize,
1621 owners: Vec<PreparedPeerWeightedRouteOwner>,
1622 peer_staging: CudaSlice<f32>,
1623 slots: CudaSlice<f32>,
1624 weights: CudaSlice<f32>,
1625 output: CudaSlice<f32>,
1626 peer_devices: Vec<usize>,
1627 peer_outputs: Vec<CudaSlice<f32>>,
1628 width: usize,
1629 experts_per_token: usize,
1630 max_tokens: usize,
1631 max_pairs: usize,
1632 tokens: usize,
1633 pairs: usize,
1634 projection_generation: u64,
1635 output_generation: Option<u64>,
1636 broadcast_generation: Option<u64>,
1637 ready: bool,
1638}
1639
1640impl PreparedPeerWeightedRouteCombine {
1641 pub fn tokens(&self) -> usize {
1642 self.tokens
1643 }
1644
1645 pub fn pairs(&self) -> usize {
1646 self.pairs
1647 }
1648
1649 pub fn owner_pair_counts(&self) -> Vec<usize> {
1650 self.owners.iter().map(|owner| owner.active_pairs).collect()
1651 }
1652
1653 pub fn distributed_ranks(&self) -> usize {
1654 1 + self.peer_outputs.len()
1655 }
1656}
1657
1658struct ResidentTpExpertBank {
1659 gate: Vec<ResidentE4m3ExpertBankRank>,
1660 up: Vec<ResidentE4m3ExpertBankRank>,
1661 down: Vec<ResidentE4m3ExpertBankRank>,
1662 expert_count: usize,
1663 input_width: usize,
1664 expert_width: usize,
1665}
1666
1667pub struct ResidentTensorParallel {
1673 bank: ResidentTpExpertBank,
1674}
1675
1676pub struct TpE4m3HostBounce {
1682 devices: Vec<usize>,
1683 ranks: Vec<Engine>,
1684 native_p2p: bool,
1685 ep_device_arithmetic: bool,
1686 bulk_p2p: bool,
1687 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1690}
1691
1692pub struct TpKvVerifiedLayer<'a> {
1693 pub cache: &'a mut ResidentTpKvCache,
1694 pub start: usize,
1695 pub logical_len: usize,
1696 pub source_k_raw: u64,
1697 pub source_v_raw: u64,
1698 pub source_k_tok_bytes: usize,
1699 pub source_v_tok_bytes: usize,
1700}
1701
1702pub struct Tp2ReplicatedRowJoin {
1710 width: usize,
1711 parity: usize,
1712 stage0: [CudaSlice<f32>; 2],
1713 stage1: [CudaSlice<f32>; 2],
1714 stage0_raw: [u64; 2],
1715 stage1_raw: [u64; 2],
1716 event0: [CudaEvent; 2],
1717 event1: [CudaEvent; 2],
1718}
1719
1720fn validate_tp2_replicated_row_join(
1721 ranks: usize,
1722 native_p2p: bool,
1723 width: usize,
1724) -> Result<(), String> {
1725 if ranks != 2 {
1726 return Err(format!(
1727 "replicated-row join requires exactly two ranks, got {ranks}"
1728 ));
1729 }
1730 if !native_p2p {
1731 return Err("replicated-row join requires native P2P".into());
1732 }
1733 if width == 0 || width > i32::MAX as usize {
1734 return Err(format!(
1735 "replicated-row join width must be in 1..={}, got {width}",
1736 i32::MAX
1737 ));
1738 }
1739 Ok(())
1740}
1741
1742fn launch_tp2_peer_push(
1743 engine: &Engine,
1744 source: &CudaSlice<f32>,
1745 destination: u64,
1746 width: usize,
1747) -> Result<(), Box<dyn std::error::Error>> {
1748 let function = engine.func("q4e_push_f32");
1749 let config = LaunchConfig::for_num_elems(width as u32);
1750 let width = width as i64;
1751 let stream = engine.gpu.stream();
1752 let mut launch = stream.launch_builder(&function);
1753 launch.arg(source).arg(&destination).arg(&width);
1754 unsafe {
1755 launch.launch(config)?;
1756 }
1757 Ok(())
1758}
1759
1760pub enum StepTpGateShards<'a> {
1769 F32(&'a [crate::CudaSlice<f32>]),
1770 Bf16(&'a [crate::CudaSlice<u8>]),
1771}
1772
1773pub struct StepTpDecodeV2Ws {
1774 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1778 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1779 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1780 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1781 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1782 pub(crate) tcol_cap: usize,
1783 w8_aq: Vec<CudaSlice<i8>>,
1787 w8_ad: Vec<CudaSlice<f32>>,
1788 w8_in: usize,
1789 w8o_aq: Vec<CudaSlice<i8>>,
1792 w8o_ad: Vec<CudaSlice<f32>>,
1793 w8o_in: usize,
1794 w8t_aq: Vec<CudaSlice<i8>>,
1798 w8t_ad: Vec<CudaSlice<f32>>,
1799 w8t_in: usize,
1800 w8t_oaq: Vec<CudaSlice<i8>>,
1801 w8t_oad: Vec<CudaSlice<f32>>,
1802 w8t_oin: usize,
1803 w8t_cap: usize,
1804 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1811 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1812 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1813 pub(crate) fa2_cap: usize,
1814 rope_k_t: Vec<CudaSlice<f32>>,
1818 rope_ctr_t: Vec<CudaSlice<u32>>,
1819 rope_pos_t: Vec<CudaSlice<i32>>,
1820 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1824 rows_tab_t: Vec<CudaSlice<u64>>,
1834 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1838 tcol_gated: Vec<CudaSlice<f32>>,
1839 tcol_opart: Vec<CudaSlice<f32>>,
1840 tcol_opeer: Option<CudaSlice<f32>>,
1841 tcol_omix: Option<CudaSlice<f32>>,
1842 tcol_ocap: usize,
1843 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1846 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1847 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1848 pub(crate) q: Vec<CudaSlice<f32>>,
1849 pub(crate) k: Vec<CudaSlice<f32>>,
1850 pub(crate) pos: Vec<CudaSlice<i32>>,
1851 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1853 pub(crate) gate: Vec<CudaSlice<f32>>,
1854 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1855 pub(crate) gated: Vec<CudaSlice<f32>>,
1856 o_partials: Vec<Vec<CudaSlice<f32>>>,
1858 raw_o_partials: Vec<Vec<u64>>,
1862 raw_k: Vec<u64>,
1863 raw_v_raw: Vec<u64>,
1864 ev_rank: Vec<CudaEvent>,
1866 peer_partial: CudaSlice<f32>,
1868 reduce_a: CudaSlice<f32>,
1869 reduce_b: CudaSlice<f32>,
1870 zeros: CudaSlice<f32>,
1872 pub(crate) k_shadow: CudaSlice<f32>,
1873 pub(crate) v_shadow: CudaSlice<f32>,
1874 ev_refresh: CudaEvent,
1875 ev_oproj: CudaEvent,
1876 gate_e: CudaSlice<f32>,
1878 pub(crate) h_stage: Option<CudaSlice<f32>>,
1881 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1882 attn_in: Vec<CudaSlice<f32>>,
1886 raw_h_stage: u64,
1888 raw_pos_stage: u64,
1889 raw_attn_in: Vec<u64>,
1890 raw_pos: Vec<u64>,
1891 raw_o_partial1: u64,
1892 raw_peer_partial: u64,
1893 raw_k1: u64,
1894 raw_v1: u64,
1895 raw_k_shadow: u64,
1896 raw_v_shadow: u64,
1897 raw_mixed_stage_e: u64,
1901 raw_reduce_a: u64,
1902 raw_shadow_stage_e: (u64, u64),
1903 ev_entry: CudaEvent,
1904 e_device: usize,
1905 local_q_dim: usize,
1907 local_kv_dim: usize,
1908 heads: usize,
1909 pub(crate) o_out: usize,
1910 o_block_cols: usize,
1911 blocks_per_rank: usize,
1912}
1913
1914impl TpE4m3HostBounce {
1915 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1916 Self::new_inner(devices, false, false, false, false)
1917 }
1918
1919 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1920 Self::new_inner(devices, false, true, false, false)
1921 }
1922
1923 pub fn new_native_p2p_device_arithmetic(
1924 devices: &[usize],
1925 ) -> Result<Self, Box<dyn std::error::Error>> {
1926 Self::new_inner(devices, false, true, true, false)
1927 }
1928
1929 pub(crate) fn new_configured(
1930 devices: &[usize],
1931 native_p2p: bool,
1932 ep_device_arithmetic: bool,
1933 bulk_p2p: bool,
1934 ) -> Result<Self, Box<dyn std::error::Error>> {
1935 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1936 }
1937
1938 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1943 Self::new_inner(&[device], true, false, false, false)
1944 }
1945
1946 fn new_inner(
1947 devices: &[usize],
1948 allow_single_rank: bool,
1949 native_p2p: bool,
1950 ep_device_arithmetic: bool,
1951 bulk_p2p: bool,
1952 ) -> Result<Self, Box<dyn std::error::Error>> {
1953 if ep_device_arithmetic && !native_p2p {
1954 return Err("device-resident EP arithmetic requires native P2P".into());
1955 }
1956 if bulk_p2p && !native_p2p {
1957 return Err("bulk TP transport requires native P2P".into());
1958 }
1959 let minimum = if allow_single_rank { 1 } else { 2 };
1960 if !(minimum..=8).contains(&devices.len()) {
1961 return Err(format!(
1962 "TP reference requires {minimum}..=8 devices, got {}",
1963 devices.len()
1964 )
1965 .into());
1966 }
1967 let mut unique = devices.to_vec();
1968 unique.sort_unstable();
1969 unique.dedup();
1970 if unique.len() != devices.len() {
1971 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1972 }
1973 let ranks = devices
1974 .iter()
1975 .map(|&device| Engine::new(device))
1976 .collect::<Result<Vec<_>, _>>()?;
1977 if native_p2p {
1978 configure_native_p2p(&ranks, devices)?;
1979 }
1980 if allow_single_rank {
1981 eprintln!(
1982 "[tp] canonical oracle transport=local device={} performance_claim=false",
1983 devices[0]
1984 );
1985 } else if native_p2p {
1986 if ep_device_arithmetic {
1987 eprintln!(
1988 "[tp] correctness transport=native-p2p devices={devices:?} \
1989 native_p2p=true activation=device-host-exact \
1990 accumulation=device-host-exact output=root-readback \
1991 bulk_p2p={bulk_p2p} performance_claim=false"
1992 );
1993 } else {
1994 eprintln!(
1995 "[tp] correctness transport=native-p2p devices={devices:?} \
1996 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1997 performance_claim=false"
1998 );
1999 }
2000 } else {
2001 eprintln!(
2002 "[tp] correctness transport=host-bounce devices={devices:?} \
2003 native_p2p=false performance_claim=false"
2004 );
2005 }
2006 Ok(Self {
2007 devices: devices.to_vec(),
2008 ranks,
2009 native_p2p,
2010 ep_device_arithmetic,
2011 bulk_p2p,
2012 decode_v2: std::sync::Mutex::new(Vec::new()),
2013 })
2014 }
2015
2016 pub fn devices(&self) -> &[usize] {
2017 &self.devices
2018 }
2019
2020 pub fn native_p2p(&self) -> bool {
2021 self.native_p2p
2022 }
2023
2024 pub fn bulk_p2p(&self) -> bool {
2025 self.bulk_p2p
2026 }
2027
2028 pub fn expert_activation_label(&self) -> &'static str {
2029 if self.ep_device_arithmetic {
2030 "device-host-exact"
2031 } else {
2032 "host-canonical"
2033 }
2034 }
2035
2036 pub fn expert_accumulation_label(&self) -> &'static str {
2037 self.expert_activation_label()
2038 }
2039
2040 pub fn expert_output_label(&self) -> &'static str {
2041 if self.ep_device_arithmetic {
2042 "root-readback"
2043 } else {
2044 "host-accumulated"
2045 }
2046 }
2047
2048 pub fn transport_label(&self) -> &'static str {
2049 if self.devices.len() == 1 {
2050 "local"
2051 } else if self.native_p2p {
2052 "native-p2p"
2053 } else {
2054 "host-bounce"
2055 }
2056 }
2057
2058 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
2059 self.ranks
2060 .iter()
2061 .map(|rank| rank.ctx().name().map_err(Into::into))
2062 .collect()
2063 }
2064
2065 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
2071 self.ranks.get(rank)
2072 }
2073
2074 pub fn prepare_tp2_replicated_row_join(
2077 &self,
2078 width: usize,
2079 ) -> Result<Tp2ReplicatedRowJoin, Box<dyn std::error::Error>> {
2080 validate_tp2_replicated_row_join(self.ranks.len(), self.native_p2p, width)?;
2081 let rank0 = &self.ranks[0];
2082 let rank1 = &self.ranks[1];
2083
2084 let (stage0, stage0_raw, event0) = {
2085 let _main = rank0.gpu.enter_main()?;
2086 let stage = [rank0.zeros(width)?, rank0.zeros(width)?];
2087 let stream = rank0.gpu.stream();
2088 let raw = [
2089 stage[0].device_ptr(&stream).0,
2090 stage[1].device_ptr(&stream).0,
2091 ];
2092 let events = [rank0.ctx().new_event(None)?, rank0.ctx().new_event(None)?];
2093 (stage, raw, events)
2094 };
2095 let (stage1, stage1_raw, event1) = {
2096 let _main = rank1.gpu.enter_main()?;
2097 let stage = [rank1.zeros(width)?, rank1.zeros(width)?];
2098 let stream = rank1.gpu.stream();
2099 let raw = [
2100 stage[0].device_ptr(&stream).0,
2101 stage[1].device_ptr(&stream).0,
2102 ];
2103 let events = [rank1.ctx().new_event(None)?, rank1.ctx().new_event(None)?];
2104 (stage, raw, events)
2105 };
2106 Ok(Tp2ReplicatedRowJoin {
2107 width,
2108 parity: 0,
2109 stage0,
2110 stage1,
2111 stage0_raw,
2112 stage1_raw,
2113 event0,
2114 event1,
2115 })
2116 }
2117
2118 pub fn tp2_replicated_row_join(
2122 &self,
2123 join: &mut Tp2ReplicatedRowJoin,
2124 partial0: &CudaSlice<f32>,
2125 partial1: &CudaSlice<f32>,
2126 output0: &mut CudaSlice<f32>,
2127 output1: &mut CudaSlice<f32>,
2128 ) -> Result<(), Box<dyn std::error::Error>> {
2129 validate_tp2_replicated_row_join(self.ranks.len(), self.native_p2p, join.width)?;
2130 let rank0 = &self.ranks[0];
2131 let rank1 = &self.ranks[1];
2132 let width = join.width;
2133 if partial0.len() < width
2134 || partial1.len() < width
2135 || output0.len() < width
2136 || output1.len() < width
2137 || partial0.ordinal() != rank0.ctx().ordinal()
2138 || output0.ordinal() != rank0.ctx().ordinal()
2139 || partial1.ordinal() != rank1.ctx().ordinal()
2140 || output1.ordinal() != rank1.ctx().ordinal()
2141 {
2142 return Err("replicated-row join buffer geometry or ownership mismatch".into());
2143 }
2144
2145 let parity = join.parity;
2146 {
2147 let _main = rank0.gpu.enter_main()?;
2148 launch_tp2_peer_push(rank0, partial0, join.stage1_raw[parity], width)?;
2149 join.event0[parity].record(&rank0.gpu.stream())?;
2150 }
2151 {
2152 let _main = rank1.gpu.enter_main()?;
2153 launch_tp2_peer_push(rank1, partial1, join.stage0_raw[parity], width)?;
2154 join.event1[parity].record(&rank1.gpu.stream())?;
2155 }
2156 {
2157 let _main = rank0.gpu.enter_main()?;
2158 rank0.gpu.stream().wait(&join.event1[parity])?;
2159 rank0.add(partial0, &join.stage0[parity], output0, width)?;
2160 }
2161 {
2162 let _main = rank1.gpu.enter_main()?;
2163 rank1.gpu.stream().wait(&join.event0[parity])?;
2164 rank1.add(&join.stage1[parity], partial1, output1, width)?;
2165 }
2166 join.parity ^= 1;
2167 Ok(())
2168 }
2169
2170 pub fn allocate_tp_kv_cache(
2171 &self,
2172 kv_dim_k: usize,
2173 kv_dim_v: usize,
2174 capacity: usize,
2175 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2176 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
2177 }
2178
2179 pub fn allocate_tp_swa_kv_cache(
2180 &self,
2181 kv_dim_k: usize,
2182 kv_dim_v: usize,
2183 capacity: usize,
2184 window: usize,
2185 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2186 if window == 0 {
2187 return Err("TP SWA KV window must be nonzero".into());
2188 }
2189 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
2190 }
2191
2192 fn allocate_tp_kv_cache_inner(
2193 &self,
2194 kv_dim_k: usize,
2195 kv_dim_v: usize,
2196 capacity: usize,
2197 window: Option<usize>,
2198 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2199 if capacity == 0 || capacity > i32::MAX as usize {
2200 return Err(
2201 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
2202 );
2203 }
2204 let tp = self.ranks.len();
2205 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
2206 let physical_rows = window
2207 .map(|window| crate::cache::swa_ring_rows(window, capacity))
2208 .unwrap_or(capacity);
2209 let k_plane_bytes = physical_rows
2210 .checked_mul(shape.k_token_bytes)
2211 .and_then(|bytes| bytes.checked_add(8))
2212 .ok_or("TP KV K plane-byte overflow")?;
2213 let v_plane_bytes = physical_rows
2214 .checked_mul(shape.v_token_bytes)
2215 .and_then(|bytes| bytes.checked_add(8))
2216 .ok_or("TP KV V plane-byte overflow")?;
2217 let mut ranks = Vec::with_capacity(tp);
2218 for engine in &self.ranks {
2219 let _main = engine.gpu.enter_main()?;
2220 ranks.push(ResidentTpKvCacheRank::new(
2221 engine.alloc_u8(k_plane_bytes)?,
2222 engine.alloc_u8(v_plane_bytes)?,
2223 engine.htod_i32(&[0])?,
2224 ));
2225 }
2226 Ok(match window {
2227 Some(window) => ResidentTpKvCache::new_swa(
2228 ranks,
2229 shape.kv_dim_k,
2230 shape.kv_dim_v,
2231 shape.k_token_bytes,
2232 shape.v_token_bytes,
2233 capacity,
2234 window,
2235 ),
2236 None => ResidentTpKvCache::new(
2237 ranks,
2238 shape.kv_dim_k,
2239 shape.kv_dim_v,
2240 shape.k_token_bytes,
2241 shape.v_token_bytes,
2242 capacity,
2243 ),
2244 })
2245 }
2246
2247 pub fn grow_tp_kv_cache(
2248 &self,
2249 source: &ResidentTpKvCache,
2250 target_capacity: usize,
2251 rows: usize,
2252 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
2253 self.validate_tp_kv_cache(source)?;
2254 let plan = source.prepare_grow(target_capacity, rows)?;
2255 let ranks = self.ranks.len();
2256 let global_k = source
2257 .kv_dim_k()
2258 .checked_mul(ranks)
2259 .ok_or("TP KV grow global K dimension overflow")?;
2260 let global_v = source
2261 .kv_dim_v()
2262 .checked_mul(ranks)
2263 .ok_or("TP KV grow global V dimension overflow")?;
2264 let mut target = match source.ring_window() {
2265 Some(window) => {
2266 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
2267 }
2268 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
2269 };
2270 self.validate_tp_kv_cache(&target)?;
2271
2272 for (rank, engine) in self.ranks.iter().enumerate() {
2273 let _main = engine.gpu.enter_main()?;
2274 let src = source
2275 .rank(rank)
2276 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
2277 let dst = target
2278 .rank_mut(rank)
2279 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
2280 if plan.k_bytes() > 0 {
2281 engine.copy_u8_range_into(
2282 dst.k_mut(),
2283 0,
2284 src.k(),
2285 plan.source_row() * source.k_tok_bytes(),
2286 plan.k_bytes(),
2287 )?;
2288 }
2289 if plan.v_bytes() > 0 {
2290 engine.copy_u8_range_into(
2291 dst.v_mut(),
2292 0,
2293 src.v(),
2294 plan.source_row() * source.v_tok_bytes(),
2295 plan.v_bytes(),
2296 )?;
2297 }
2298 }
2299 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
2300
2301 for engine in &self.ranks {
2304 let _main = engine.gpu.enter_main()?;
2305 engine.stream().synchronize()?;
2306 }
2307 let physical_copy_rows = plan.copy_rows();
2308 target.publish_grow(plan)?;
2309 eprintln!(
2310 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
2311 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
2312 rank_streams_synchronized=true generation_preserved=true",
2313 rows,
2314 source.capacity(),
2315 target_capacity,
2316 ranks,
2317 physical_copy_rows,
2318 source.ring_window(),
2319 );
2320 Ok(target)
2321 }
2322
2323 pub fn hydrate_tp_kv_cache(
2324 &self,
2325 cache: &mut ResidentTpKvCache,
2326 rows: usize,
2327 k_rows: &[u8],
2328 v_rows: &[u8],
2329 ) -> Result<(), Box<dyn std::error::Error>> {
2330 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
2331 }
2332
2333 pub fn hydrate_tp_kv_cache_from(
2334 &self,
2335 cache: &mut ResidentTpKvCache,
2336 logical_len: usize,
2337 resident_start: usize,
2338 k_rows: &[u8],
2339 v_rows: &[u8],
2340 ) -> Result<(), Box<dyn std::error::Error>> {
2341 self.validate_tp_kv_cache(cache)?;
2342 if cache.committed_len() != 0 || cache.staged_len() != 0 {
2343 return Err(format!(
2344 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
2345 cache.committed_len(),
2346 cache.staged_len()
2347 )
2348 .into());
2349 }
2350 if resident_start > logical_len || logical_len > cache.capacity() {
2351 return Err(format!(
2352 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
2353 cache.capacity(),
2354 )
2355 .into());
2356 }
2357 let rows = logical_len - resident_start;
2358 if rows > cache.physical_capacity() {
2359 return Err(format!(
2360 "TP KV hydration rows {rows} exceed physical capacity {}",
2361 cache.physical_capacity()
2362 )
2363 .into());
2364 }
2365 for rank in 0..self.ranks.len() {
2366 let k_rank =
2367 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
2368 let v_rank =
2369 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
2370 let engine = &self.ranks[rank];
2371 let _main = engine.gpu.enter_main()?;
2372 let rank_cache = cache
2373 .rank_mut(rank)
2374 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2375 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
2376 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
2377 }
2378 cache.publish_hydration(logical_len, resident_start)?;
2379 Ok(())
2380 }
2381
2382 #[allow(clippy::too_many_arguments)]
2387 pub fn restore_tp_kv_rows_from_device(
2388 &self,
2389 cache: &mut ResidentTpKvCache,
2390 start: usize,
2391 logical_len: usize,
2392 source_k_raw: u64,
2393 source_v_raw: u64,
2394 source_k_tok_bytes: usize,
2395 source_v_tok_bytes: usize,
2396 ) -> Result<(), Box<dyn std::error::Error>> {
2397 self.validate_tp_kv_cache(cache)?;
2398 if cache.committed_len() != cache.staged_len() {
2399 return Err(format!(
2400 "TP KV verify restore requires quiescent state, got committed/staged={}/{}",
2401 cache.committed_len(),
2402 cache.staged_len()
2403 )
2404 .into());
2405 }
2406 if start > logical_len || logical_len > cache.capacity() {
2407 return Err(format!(
2408 "TP KV verify restore range [{start},{logical_len}) exceeds capacity {}",
2409 cache.capacity()
2410 )
2411 .into());
2412 }
2413 let rows = logical_len - start;
2414 let physical = cache.physical_range(start, logical_len)?;
2415 if physical.len() != rows {
2416 return Err(format!(
2417 "TP KV verify restore range [{start},{logical_len}) is not physically contiguous"
2418 )
2419 .into());
2420 }
2421 let ranks = self.ranks.len();
2422 let k_tok_bytes = cache.k_tok_bytes();
2423 let v_tok_bytes = cache.v_tok_bytes();
2424 if source_k_tok_bytes != k_tok_bytes * ranks || source_v_tok_bytes != v_tok_bytes * ranks {
2425 return Err(format!(
2426 "TP KV verify source token bytes k={source_k_tok_bytes} v={source_v_tok_bytes} \
2427 do not match distributed k={}x{ranks} v={}x{ranks}",
2428 k_tok_bytes, v_tok_bytes
2429 )
2430 .into());
2431 }
2432 for rank in 0..self.ranks.len() {
2433 let engine = &self.ranks[rank];
2434 let _main = engine.gpu.enter_main()?;
2435 use cudarc::driver::DevicePtr;
2436 let stream = engine.stream();
2437 let k_offset = physical
2438 .start
2439 .checked_mul(k_tok_bytes)
2440 .ok_or("TP KV verify K offset overflow")?;
2441 let v_offset = physical
2442 .start
2443 .checked_mul(v_tok_bytes)
2444 .ok_or("TP KV verify V offset overflow")?;
2445 let rank_cache = cache
2446 .rank_mut(rank)
2447 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2448 let k_dst = {
2449 let (pointer, _guard) = rank_cache.k_mut().device_ptr(&stream);
2450 pointer
2451 };
2452 let v_dst = {
2453 let (pointer, _guard) = rank_cache.v_mut().device_ptr(&stream);
2454 pointer
2455 };
2456 for row in 0..rows {
2457 let k_src = source_k_raw + (row * source_k_tok_bytes + rank * k_tok_bytes) as u64;
2458 let v_src = source_v_raw + (row * source_v_tok_bytes + rank * v_tok_bytes) as u64;
2459 let k_out = k_dst + (k_offset + row * k_tok_bytes) as u64;
2460 let v_out = v_dst + (v_offset + row * v_tok_bytes) as u64;
2461 raw_copy_bytes(k_out, k_src, k_tok_bytes, engine)?;
2462 raw_copy_bytes(v_out, v_src, v_tok_bytes, engine)?;
2463 }
2464 }
2465 cache.rewind_to(logical_len)?;
2466 Ok(())
2467 }
2468
2469 pub fn restore_tp_kv_layers_from_device(
2473 &self,
2474 layers: &mut [TpKvVerifiedLayer<'_>],
2475 ) -> Result<bool, Box<dyn std::error::Error>> {
2476 let Some(first) = layers.first() else {
2477 return Ok(false);
2478 };
2479 if !self.native_p2p || first.start >= first.logical_len {
2480 return Ok(false);
2481 }
2482 let ranks = self.ranks.len();
2483 let rows = first.logical_len - first.start;
2484 let logical_len = first.logical_len;
2485 let k_row_bytes = first.cache.k_tok_bytes();
2486 let v_row_bytes = first.cache.v_tok_bytes();
2487 let k_src_stride = first.source_k_tok_bytes;
2488 let v_src_stride = first.source_v_tok_bytes;
2489 let Some(expected_k_stride) = k_row_bytes.checked_mul(ranks) else {
2490 return Ok(false);
2491 };
2492 let Some(expected_v_stride) = v_row_bytes.checked_mul(ranks) else {
2493 return Ok(false);
2494 };
2495 if k_src_stride != expected_k_stride || v_src_stride != expected_v_stride {
2496 return Ok(false);
2497 }
2498
2499 for layer in layers.iter() {
2500 self.validate_tp_kv_cache(layer.cache)?;
2501 if layer.cache.committed_len() != layer.cache.staged_len()
2502 || layer.start > layer.logical_len
2503 || layer.logical_len != logical_len
2504 || layer.logical_len - layer.start != rows
2505 || layer.cache.k_tok_bytes() != k_row_bytes
2506 || layer.cache.v_tok_bytes() != v_row_bytes
2507 || layer.source_k_tok_bytes != k_src_stride
2508 || layer.source_v_tok_bytes != v_src_stride
2509 {
2510 return Ok(false);
2511 }
2512 let physical = layer.cache.physical_range(layer.start, layer.logical_len)?;
2513 if physical.len() != rows {
2514 return Ok(false);
2515 }
2516 }
2517
2518 for rank in 0..ranks {
2519 let engine = &self.ranks[rank];
2520 let _main = engine.gpu.enter_main()?;
2521 let stream = engine.stream();
2522 let n = layers.len();
2523 let mut table = vec![0u64; 5 * n];
2524 for (index, layer) in layers.iter_mut().enumerate() {
2525 let physical = layer.cache.physical_range(layer.start, layer.logical_len)?;
2526 let rank_cache = layer
2527 .cache
2528 .rank_mut(rank)
2529 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2530 table[index] = layer.source_k_raw + (rank * k_row_bytes) as u64;
2531 table[n + index] = layer.source_v_raw + (rank * v_row_bytes) as u64;
2532 table[2 * n + index] = rank_cache.k_mut().device_ptr(&stream).0
2533 + (physical.start * k_row_bytes) as u64;
2534 table[3 * n + index] = rank_cache.v_mut().device_ptr(&stream).0
2535 + (physical.start * v_row_bytes) as u64;
2536 table[4 * n + index] = rank_cache.len_d_mut().device_ptr(&stream).0;
2537 }
2538 let table = engine.htod_u64(&table)?;
2539 engine.copy_batch_uniform_kv_u8_set_len(
2540 &table,
2541 n,
2542 rows,
2543 k_row_bytes,
2544 v_row_bytes,
2545 k_src_stride,
2546 v_src_stride,
2547 logical_len,
2548 )?;
2549 }
2550 let layer_count = layers.len();
2551 for layer in layers.iter_mut() {
2552 layer.cache.publish_device_rewind(logical_len)?;
2553 }
2554 static ANNOUNCED: std::sync::Once = std::sync::Once::new();
2555 ANNOUNCED.call_once(|| {
2556 eprintln!(
2557 "[tp-kv-verify-batch] engaged: layers={} ranks={ranks} rows={rows} \
2558 k_row_bytes={k_row_bytes} v_row_bytes={v_row_bytes}",
2559 layer_count
2560 );
2561 });
2562 Ok(true)
2563 }
2564
2565 pub fn append_tp_kv_transaction(
2566 &self,
2567 cache: &mut ResidentTpKvCache,
2568 transaction: TpKvTransaction,
2569 k_shards: &[CudaSlice<f32>],
2570 v_shards: &[CudaSlice<f32>],
2571 rows: usize,
2572 ) -> Result<(), Box<dyn std::error::Error>> {
2573 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
2574 }
2575
2576 #[allow(clippy::too_many_arguments)]
2581 pub fn append_tp_kv_transaction_inner(
2582 &self,
2583 cache: &mut ResidentTpKvCache,
2584 transaction: TpKvTransaction,
2585 k_shards: &[CudaSlice<f32>],
2586 v_shards: &[CudaSlice<f32>],
2587 rows: usize,
2588 external_rank_appends: bool,
2589 ) -> Result<(), Box<dyn std::error::Error>> {
2590 self.validate_tp_kv_cache(cache)?;
2591 let plan = cache.prepare_append(transaction, rows)?;
2592 let target = plan.target();
2593 let expected_k = rows
2594 .checked_mul(cache.kv_dim_k())
2595 .ok_or("TP KV K append size overflow")?;
2596 let expected_v = rows
2597 .checked_mul(cache.kv_dim_v())
2598 .ok_or("TP KV V append size overflow")?;
2599 if !external_rank_appends
2602 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
2603 {
2604 return Err(format!(
2605 "TP KV append shard counts k={} v={} != ranks {}",
2606 k_shards.len(),
2607 v_shards.len(),
2608 self.ranks.len()
2609 )
2610 .into());
2611 }
2612 let kv_dim_k = cache.kv_dim_k();
2613 let kv_dim_v = cache.kv_dim_v();
2614 let k_tok_bytes = cache.k_tok_bytes();
2615 let v_tok_bytes = cache.v_tok_bytes();
2616 if let Some(ring_base) = cache.ring_base() {
2617 let base_val = ring_base as i32;
2618 for rank in 0..self.ranks.len() {
2619 let engine = &self.ranks[rank];
2620 let _main = engine.gpu.enter_main()?;
2621 let rank_cache = cache
2622 .rank_mut(rank)
2623 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2624 if rank_cache.base_d().is_none() {
2625 rank_cache.arm_base_d(engine.htod_i32(&[base_val])?);
2626 }
2627 }
2628 }
2629 if let Some(KvRingAppend::Rebase {
2630 src_row,
2631 keep_rows,
2632 new_base,
2633 ..
2634 }) = plan.ring_append()
2635 {
2636 for rank in 0..self.ranks.len() {
2637 let engine = &self.ranks[rank];
2638 let _main = engine.gpu.enter_main()?;
2639 let rank_cache = cache
2640 .rank_mut(rank)
2641 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2642 if keep_rows > 0 {
2643 let k_len = keep_rows
2644 .checked_mul(k_tok_bytes)
2645 .ok_or("TP KV K rebase-byte overflow")?;
2646 let v_len = keep_rows
2647 .checked_mul(v_tok_bytes)
2648 .ok_or("TP KV V rebase-byte overflow")?;
2649 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
2650 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2651 engine.copy_u8_range_into(
2652 &mut k_tmp,
2653 0,
2654 rank_cache.k(),
2655 src_row * k_tok_bytes,
2656 k_len,
2657 )?;
2658 engine.copy_u8_range_into(
2659 &mut v_tmp,
2660 0,
2661 rank_cache.v(),
2662 src_row * v_tok_bytes,
2663 v_len,
2664 )?;
2665 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2666 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2667 }
2668 let value = new_base as i32;
2672 if let Some(base_d) = rank_cache.base_d_mut() {
2673 engine.set_i32_one(base_d, value)?;
2674 } else {
2675 rank_cache.arm_base_d(engine.htod_i32(&[value])?);
2676 }
2677 }
2678 }
2679 cache.publish_append_rebase(plan)?;
2680 let write_row = plan.write_row();
2681 for rank in 0..self.ranks.len() {
2682 if external_rank_appends {
2683 break;
2684 }
2685 let engine = &self.ranks[rank];
2686 let _main = engine.gpu.enter_main()?;
2687 if k_shards[rank].len() != expected_k
2688 || v_shards[rank].len() != expected_v
2689 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2690 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2691 {
2692 return Err(format!(
2693 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2694 != expected {expected_k}/{expected_v} on device {}",
2695 k_shards[rank].len(),
2696 k_shards[rank].ordinal(),
2697 v_shards[rank].len(),
2698 v_shards[rank].ordinal(),
2699 engine.ctx().ordinal(),
2700 )
2701 .into());
2702 }
2703 let rank_cache = cache
2704 .rank_mut(rank)
2705 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2706 let (rank_k, rank_v) = rank_cache.planes_mut();
2707 engine.append_kv_quantized_rows(
2708 &k_shards[rank],
2709 &v_shards[rank],
2710 rank_k,
2711 rank_v,
2712 write_row,
2713 rows,
2714 kv_dim_k,
2715 kv_dim_v,
2716 k_tok_bytes,
2717 v_tok_bytes,
2718 false,
2719 )?;
2720 }
2721 if !external_rank_appends {
2722 self.set_tp_kv_len_mirrors(cache, target)?;
2725 }
2726 cache.publish_append_plan(plan)?;
2727 Ok(())
2728 }
2729
2730 pub fn commit_tp_kv_transaction(
2731 &self,
2732 cache: &mut ResidentTpKvCache,
2733 transaction: TpKvTransaction,
2734 accepted_rows: usize,
2735 ) -> Result<(), Box<dyn std::error::Error>> {
2736 self.validate_tp_kv_cache(cache)?;
2737 let target = cache.commit_target(transaction, accepted_rows)?;
2738 self.set_tp_kv_len_mirrors(cache, target)?;
2739 cache.publish_finalize(transaction, target)?;
2740 cache.mark_rows_external(false);
2743 Ok(())
2744 }
2745
2746 pub fn commit_tp_kv_transaction_external(
2752 &self,
2753 cache: &mut ResidentTpKvCache,
2754 transaction: TpKvTransaction,
2755 accepted_rows: usize,
2756 ) -> Result<(), Box<dyn std::error::Error>> {
2757 self.validate_tp_kv_cache(cache)?;
2758 let target = cache.commit_target(transaction, accepted_rows)?;
2759 cache.publish_finalize(transaction, target)?;
2760 cache.mark_rows_external(true);
2764 Ok(())
2765 }
2766
2767 pub fn rollback_tp_kv_transaction(
2768 &self,
2769 cache: &mut ResidentTpKvCache,
2770 transaction: TpKvTransaction,
2771 ) -> Result<(), Box<dyn std::error::Error>> {
2772 self.validate_tp_kv_cache(cache)?;
2773 cache.validate_transaction(transaction)?;
2774 let target = transaction.base_len();
2775 self.set_tp_kv_len_mirrors(cache, target)?;
2776 cache.publish_finalize(transaction, target)?;
2777 Ok(())
2778 }
2779
2780 pub fn tp_kv_device_lengths(
2781 &self,
2782 cache: &ResidentTpKvCache,
2783 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2784 self.validate_tp_kv_cache(cache)?;
2785 let mut lengths = Vec::with_capacity(self.ranks.len());
2786 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2787 let _main = engine.gpu.enter_main()?;
2788 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2789 }
2790 Ok(lengths)
2791 }
2792
2793 fn set_tp_kv_len_mirrors(
2794 &self,
2795 cache: &mut ResidentTpKvCache,
2796 len: usize,
2797 ) -> Result<(), Box<dyn std::error::Error>> {
2798 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2799 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2800 let _main = engine.gpu.enter_main()?;
2801 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2802 }
2803 Ok(())
2804 }
2805
2806 fn validate_tp_kv_cache(
2807 &self,
2808 cache: &ResidentTpKvCache,
2809 ) -> Result<(), Box<dyn std::error::Error>> {
2810 if cache.ranks_len() != self.ranks.len() {
2811 return Err(format!(
2812 "TP KV cache ranks {} != runtime ranks {}",
2813 cache.ranks_len(),
2814 self.ranks.len()
2815 )
2816 .into());
2817 }
2818 let expected_k = cache
2819 .physical_capacity()
2820 .checked_mul(cache.k_tok_bytes())
2821 .and_then(|bytes| bytes.checked_add(8))
2822 .ok_or("TP KV K plane validation overflow")?;
2823 let expected_v = cache
2824 .physical_capacity()
2825 .checked_mul(cache.v_tok_bytes())
2826 .and_then(|bytes| bytes.checked_add(8))
2827 .ok_or("TP KV V plane validation overflow")?;
2828 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2829 let device = engine.ctx().ordinal();
2830 if rank_cache.k().len() != expected_k
2831 || rank_cache.v().len() != expected_v
2832 || rank_cache.len_d().len() != 1
2833 || rank_cache.k().ordinal() != device
2834 || rank_cache.v().ordinal() != device
2835 || rank_cache.len_d().ordinal() != device
2836 {
2837 return Err(format!(
2838 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2839 )
2840 .into());
2841 }
2842 }
2843 Ok(())
2844 }
2845
2846 pub fn full(
2847 &self,
2848 matrix: E4m3BlockMatrix<'_>,
2849 activations: &[f32],
2850 tokens: usize,
2851 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2852 matrix.validate()?;
2853 validate_activations(activations, tokens, matrix.in_features)?;
2854 run_rank(&self.ranks[0], matrix, activations, tokens)
2855 }
2856
2857 #[allow(clippy::manual_is_multiple_of)] pub fn column_parallel(
2862 &self,
2863 matrix: E4m3BlockMatrix<'_>,
2864 activations: &[f32],
2865 tokens: usize,
2866 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2867 matrix.validate()?;
2868 validate_activations(activations, tokens, matrix.in_features)?;
2869 let tp = self.ranks.len();
2870 if matrix.out_features % tp != 0 {
2871 return Err(format!(
2872 "column-parallel out_features {} is not divisible by TP={tp}",
2873 matrix.out_features
2874 )
2875 .into());
2876 }
2877 let local_out = matrix.out_features / tp;
2878 if !local_out.is_multiple_of(FP8_BLOCK) {
2879 return Err(format!(
2880 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2881 E4M3 scale block"
2882 )
2883 .into());
2884 }
2885
2886 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2887 let mut rank_outputs = Vec::with_capacity(tp);
2888 for (rank_index, rank) in self.ranks.iter().enumerate() {
2889 let shard = column_shard(matrix, tp, rank_index)?;
2890 let output = run_rank(rank, shard, activations, tokens)?;
2891 let row_start = rank_index * local_out;
2892 for token in 0..tokens {
2893 gathered[token * matrix.out_features + row_start
2894 ..token * matrix.out_features + row_start + local_out]
2895 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2896 }
2897 rank_outputs.push(output);
2898 }
2899 Ok(ColumnParallelResult {
2900 gathered,
2901 rank_outputs,
2902 })
2903 }
2904
2905 pub fn upload_column_parallel(
2906 &self,
2907 matrix: E4m3BlockMatrix<'_>,
2908 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2909 matrix.validate()?;
2910 let tp = self.ranks.len();
2911 validate_column_shape(matrix, tp)?;
2912 let mut ranks = Vec::with_capacity(tp);
2913 for (rank_index, engine) in self.ranks.iter().enumerate() {
2914 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2915 }
2916 Ok(ResidentColumnParallel {
2917 ranks,
2918 out_features: matrix.out_features,
2919 in_features: matrix.in_features,
2920 })
2921 }
2922
2923 pub fn column_parallel_resident(
2924 &self,
2925 matrix: &ResidentColumnParallel,
2926 activations: &[f32],
2927 tokens: usize,
2928 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2929 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2930 validate_activations(activations, tokens, matrix.in_features)?;
2931 let local_out = matrix.out_features / self.ranks.len();
2932 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2933 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2934 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2935 let output = run_resident_rank(engine, shard, activations, tokens)?;
2936 let row_start = rank_index * local_out;
2937 for token in 0..tokens {
2938 gathered[token * matrix.out_features + row_start
2939 ..token * matrix.out_features + row_start + local_out]
2940 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2941 }
2942 rank_outputs.push(output);
2943 }
2944 Ok(ColumnParallelResult {
2945 gathered,
2946 rank_outputs,
2947 })
2948 }
2949
2950 #[allow(clippy::manual_is_multiple_of)] pub fn row_parallel(
2955 &self,
2956 matrix: E4m3BlockMatrix<'_>,
2957 activations: &[f32],
2958 tokens: usize,
2959 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2960 matrix.validate()?;
2961 validate_activations(activations, tokens, matrix.in_features)?;
2962 let tp = self.ranks.len();
2963 if matrix.in_features % tp != 0 {
2964 return Err(format!(
2965 "row-parallel in_features {} is not divisible by TP={tp}",
2966 matrix.in_features
2967 )
2968 .into());
2969 }
2970 let local_in = matrix.in_features / tp;
2971 if !local_in.is_multiple_of(FP8_BLOCK) {
2972 return Err(format!(
2973 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2974 E4M3 scale block"
2975 )
2976 .into());
2977 }
2978
2979 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2980 let mut rank_partials = Vec::with_capacity(tp);
2981 for (rank_index, rank) in self.ranks.iter().enumerate() {
2982 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2983 let local_activations =
2984 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2985 let shard = E4m3BlockMatrix {
2986 codes: &codes,
2987 scales: &scales,
2988 out_features: matrix.out_features,
2989 in_features: local_in,
2990 };
2991 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2992 for (sum, value) in reduced.iter_mut().zip(&partial) {
2993 *sum += *value;
2994 }
2995 rank_partials.push(partial);
2996 }
2997 Ok(RowParallelResult {
2998 reduced,
2999 rank_partials,
3000 })
3001 }
3002
3003 pub fn upload_row_parallel(
3004 &self,
3005 matrix: E4m3BlockMatrix<'_>,
3006 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
3007 matrix.validate()?;
3008 let tp = self.ranks.len();
3009 validate_row_shape(matrix, tp)?;
3010 let local_in = matrix.in_features / tp;
3011 let mut ranks = Vec::with_capacity(tp);
3012 for (rank_index, engine) in self.ranks.iter().enumerate() {
3013 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
3014 ranks.push(upload_rank(
3015 engine,
3016 E4m3BlockMatrix {
3017 codes: &codes,
3018 scales: &scales,
3019 out_features: matrix.out_features,
3020 in_features: local_in,
3021 },
3022 )?);
3023 }
3024 Ok(ResidentRowParallel {
3025 ranks,
3026 out_features: matrix.out_features,
3027 in_features: matrix.in_features,
3028 })
3029 }
3030
3031 pub fn row_parallel_resident(
3032 &self,
3033 matrix: &ResidentRowParallel,
3034 activations: &[f32],
3035 tokens: usize,
3036 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3037 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
3038 validate_activations(activations, tokens, matrix.in_features)?;
3039 let tp = self.ranks.len();
3040 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3041 let mut rank_partials = Vec::with_capacity(tp);
3042 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3043 let local_activations =
3044 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
3045 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
3046 for (sum, value) in reduced.iter_mut().zip(&partial) {
3047 *sum += *value;
3048 }
3049 rank_partials.push(partial);
3050 }
3051 Ok(RowParallelResult {
3052 reduced,
3053 rank_partials,
3054 })
3055 }
3056
3057 pub fn upload_bf16_column_parallel(
3058 &self,
3059 matrix: Bf16Matrix<'_>,
3060 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3061 self.upload_bf16_column_parallel_inner(matrix, None, false)
3062 }
3063
3064 pub fn upload_step_bf16_column_parallel(
3066 &self,
3067 matrix: Bf16Matrix<'_>,
3068 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3069 self.upload_step_bf16_column_parallel_inner(matrix, false)
3070 }
3071
3072 pub fn upload_step_bf16_column_parallel_f32_mirror(
3077 &self,
3078 matrix: Bf16Matrix<'_>,
3079 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3080 self.upload_step_bf16_column_parallel_inner(matrix, true)
3081 }
3082
3083 fn upload_step_bf16_column_parallel_inner(
3084 &self,
3085 matrix: Bf16Matrix<'_>,
3086 f32_mirror: bool,
3087 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3088 let canonical_chunk_rows =
3089 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
3090 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
3091 }
3092
3093 #[allow(clippy::manual_is_multiple_of)] fn upload_bf16_column_parallel_inner(
3095 &self,
3096 matrix: Bf16Matrix<'_>,
3097 canonical_chunk_rows: Option<usize>,
3098 f32_mirror: bool,
3099 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
3100 matrix.validate()?;
3101 let tp = self.ranks.len();
3102 if matrix.out_features % tp != 0 {
3103 return Err(format!(
3104 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
3105 matrix.out_features
3106 )
3107 .into());
3108 }
3109 let mut ranks = Vec::with_capacity(tp);
3110 for (rank, engine) in self.ranks.iter().enumerate() {
3111 ranks.push(upload_bf16_rank(
3112 engine,
3113 bf16_column_shard(matrix, tp, rank)?,
3114 f32_mirror,
3115 )?);
3116 }
3117 Ok(ResidentBf16ColumnParallel {
3118 ranks,
3119 out_features: matrix.out_features,
3120 in_features: matrix.in_features,
3121 canonical_chunk_rows,
3122 })
3123 }
3124
3125 pub fn bf16_column_parallel_resident(
3126 &self,
3127 matrix: &ResidentBf16ColumnParallel,
3128 activations: &[f32],
3129 tokens: usize,
3130 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
3131 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3132 validate_activations(activations, tokens, matrix.in_features)?;
3133 let local_out = matrix.out_features / self.ranks.len();
3134 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
3135 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3136 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3137 let output = run_resident_bf16_rank(
3138 engine,
3139 shard,
3140 activations,
3141 tokens,
3142 matrix.canonical_chunk_rows,
3143 )?;
3144 for token in 0..tokens {
3145 let src = &output[token * local_out..(token + 1) * local_out];
3146 let dst_start = token * matrix.out_features + rank * local_out;
3147 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
3148 }
3149 rank_outputs.push(output);
3150 }
3151 Ok(ColumnParallelResult {
3152 gathered,
3153 rank_outputs,
3154 })
3155 }
3156
3157 pub fn bf16_column_parallel_resident_native(
3164 &self,
3165 matrix: &ResidentBf16ColumnParallel,
3166 activations: &[f32],
3167 tokens: usize,
3168 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3169 let rank_outputs =
3170 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
3171 let local_out = matrix.out_features / self.ranks.len();
3172 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
3173 }
3174
3175 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
3180 self.ranks
3181 .first()
3182 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
3183 }
3184
3185 pub fn bf16_column_parallel_resident_native_device(
3198 &self,
3199 matrix: &ResidentBf16ColumnParallel,
3200 root_activation: &CudaSlice<f32>,
3201 tokens: usize,
3202 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3203 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
3204 matrix,
3205 root_activation,
3206 tokens,
3207 )?;
3208 let local_out = matrix.out_features / self.ranks.len();
3209 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
3210 let root = &self.ranks[0];
3211 let _main = root.gpu.enter_main()?;
3212 root.stream().synchronize()?;
3213 Ok(gathered)
3214 }
3215
3216 pub fn bf16_column_parallel_resident_device_shards_from_root(
3221 &self,
3222 matrix: &ResidentBf16ColumnParallel,
3223 root_activation: &CudaSlice<f32>,
3224 tokens: usize,
3225 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3226 if self.ranks.len() > 1 && !self.native_p2p {
3227 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3228 }
3229 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3230 let values = tokens
3231 .checked_mul(matrix.in_features)
3232 .ok_or("device BF16 column activation size overflow")?;
3233 let root = &self.ranks[0];
3234 if tokens == 0
3235 || root_activation.len() < values
3236 || root_activation.ordinal() != root.ctx().ordinal()
3237 {
3238 return Err("device BF16 column root activation geometry mismatch".into());
3239 }
3240
3241 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3242 let root_input = {
3243 let _main = root.gpu.enter_main()?;
3244 let mut root_input = root.uninit(values)?;
3245 root.stream()
3246 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3247 root_input
3248 };
3249 {
3253 let _main = root.gpu.enter_main()?;
3254 root.stream().synchronize()?;
3255 }
3256 rank_inputs.push(root_input);
3257 for engine in &self.ranks[1..] {
3258 let peer_input = {
3259 let _main = engine.gpu.enter_main()?;
3260 let mut peer_input = engine.uninit(values)?;
3261 engine
3262 .stream()
3263 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3264 peer_input
3265 };
3266 rank_inputs.push(peer_input);
3267 }
3268
3269 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3270 #[allow(clippy::needless_range_loop)]
3271 for rank in 0..self.ranks.len() {
3273 rank_outputs.push(run_resident_bf16_rank_device(
3274 &self.ranks[rank],
3275 &matrix.ranks[rank],
3276 &rank_inputs[rank],
3277 tokens,
3278 matrix.canonical_chunk_rows,
3279 self.bulk_p2p,
3280 )?);
3281 }
3282 Ok(rank_outputs)
3283 }
3284
3285 pub fn bf16_column_parallel_resident_device_shards(
3292 &self,
3293 matrix: &ResidentBf16ColumnParallel,
3294 activations: &[f32],
3295 tokens: usize,
3296 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3297 if self.ranks.len() > 1 && !self.native_p2p {
3298 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
3299 }
3300 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3301 validate_activations(activations, tokens, matrix.in_features)?;
3302
3303 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3304 let root_input = {
3305 let root = &self.ranks[0];
3306 let _main = root.gpu.enter_main()?;
3307 root.htod(activations)?
3308 };
3309 {
3315 let root = &self.ranks[0];
3316 let _main = root.gpu.enter_main()?;
3317 root.stream().synchronize()?;
3318 }
3319 rank_inputs.push(root_input);
3320 for engine in &self.ranks[1..] {
3321 let peer_input = {
3322 let _main = engine.gpu.enter_main()?;
3323 let mut peer_input = engine.uninit(activations.len())?;
3324 engine
3325 .stream()
3326 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
3327 peer_input
3328 };
3329 rank_inputs.push(peer_input);
3330 }
3331
3332 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
3333 #[allow(clippy::needless_range_loop)]
3334 for rank in 0..self.ranks.len() {
3336 rank_outputs.push(run_resident_bf16_rank_device(
3337 &self.ranks[rank],
3338 &matrix.ranks[rank],
3339 &rank_inputs[rank],
3340 tokens,
3341 matrix.canonical_chunk_rows,
3342 self.bulk_p2p,
3343 )?);
3344 }
3345 Ok(rank_outputs)
3346 }
3347
3348 pub fn allocate_replicated_device_rows(
3352 &self,
3353 tokens: usize,
3354 width: usize,
3355 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3356 if self.ranks.len() > 1 && !self.native_p2p {
3357 return Err("replicated device rows require native P2P ranks".into());
3358 }
3359 let values = tokens
3360 .checked_mul(width)
3361 .ok_or("replicated device row size overflow")?;
3362 let rank_lengths = vec![values; self.ranks.len()];
3363 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
3364 let mut ranks = Vec::with_capacity(self.ranks.len());
3365 for engine in &self.ranks {
3366 let _main = engine.gpu.enter_main()?;
3367 ranks.push(engine.uninit(values)?);
3368 }
3369 Ok(ResidentReplicatedDeviceRows {
3370 ranks,
3371 tokens,
3372 width,
3373 })
3374 }
3375
3376 pub fn refresh_replicated_device_rows_from_root(
3378 &self,
3379 rows: &mut ResidentReplicatedDeviceRows,
3380 source: &CudaSlice<f32>,
3381 ) -> Result<(), Box<dyn std::error::Error>> {
3382 if self.ranks.len() > 1 && !self.native_p2p {
3383 return Err("replicated device rows require native P2P ranks".into());
3384 }
3385 validate_replicated_device_rows(&self.ranks, rows)?;
3386 let root = self
3387 .ranks
3388 .first()
3389 .ok_or("replicated rows have no root rank")?;
3390 let values = replicated_device_row_source_values(
3391 rows.tokens,
3392 rows.width,
3393 source.len(),
3394 source.ordinal(),
3395 root.ctx().ordinal(),
3396 )?;
3397 let (root_rows, peer_rows) = rows
3398 .ranks
3399 .split_first_mut()
3400 .ok_or("replicated rows have no root allocation")?;
3401 {
3402 let _main = root.gpu.enter_main()?;
3403 let mut destination = root_rows.slice_mut(0..values);
3404 root.stream()
3405 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
3406 root.stream().synchronize()?;
3407 }
3408 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
3409 let _main = engine.gpu.enter_main()?;
3410 let mut destination = peer_rows.slice_mut(0..values);
3411 engine
3412 .stream()
3413 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
3414 }
3415 Ok(())
3416 }
3417
3418 pub fn upload_replicated_device_rows(
3420 &self,
3421 rows: &[f32],
3422 tokens: usize,
3423 width: usize,
3424 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3425 if self.ranks.len() > 1 && !self.native_p2p {
3426 return Err("replicated device rows require native P2P ranks".into());
3427 }
3428 validate_activations(rows, tokens, width)?;
3429 let root = self
3430 .ranks
3431 .first()
3432 .ok_or("replicated rows have no root rank")?;
3433 let root_rows = {
3434 let _main = root.gpu.enter_main()?;
3435 root.htod(rows)?
3436 };
3437 {
3438 let _main = root.gpu.enter_main()?;
3439 root.stream().synchronize()?;
3440 }
3441 let mut ranks = Vec::with_capacity(self.ranks.len());
3442 ranks.push(root_rows);
3443 for engine in self.ranks.iter().skip(1) {
3444 let _main = engine.gpu.enter_main()?;
3445 let mut peer_rows = engine.uninit(rows.len())?;
3446 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
3447 ranks.push(peer_rows);
3448 }
3449 Ok(ResidentReplicatedDeviceRows {
3450 ranks,
3451 tokens,
3452 width,
3453 })
3454 }
3455
3456 pub fn bf16_column_parallel_resident_replicated_device_shards(
3458 &self,
3459 matrix: &ResidentBf16ColumnParallel,
3460 activations: &ResidentReplicatedDeviceRows,
3461 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
3462 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3463 validate_replicated_device_rows(&self.ranks, activations)?;
3464 if activations.width != matrix.in_features {
3465 return Err(format!(
3466 "replicated BF16 column input width {} != matrix width {}",
3467 activations.width, matrix.in_features
3468 )
3469 .into());
3470 }
3471 let mut outputs = Vec::with_capacity(self.ranks.len());
3472 for rank in 0..self.ranks.len() {
3473 outputs.push(run_resident_bf16_rank_device(
3474 &self.ranks[rank],
3475 &matrix.ranks[rank],
3476 &activations.ranks[rank],
3477 activations.tokens,
3478 matrix.canonical_chunk_rows,
3479 self.bulk_p2p,
3480 )?);
3481 }
3482 Ok(outputs)
3483 }
3484
3485 #[allow(clippy::too_many_arguments)]
3487 pub fn upload_sigmoid_topk_router(
3488 &self,
3489 weight: Bf16Matrix<'_>,
3490 correction_bias: &[f32],
3491 active: Option<&[bool]>,
3492 experts_per_token: usize,
3493 scaling_factor: f32,
3494 route_norm: bool,
3495 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
3496 weight.validate()?;
3497 if correction_bias.len() != weight.out_features
3498 || experts_per_token == 0
3499 || experts_per_token > weight.out_features
3500 || !correction_bias.iter().all(|value| value.is_finite())
3501 || !scaling_factor.is_finite()
3502 || scaling_factor <= 0.0
3503 {
3504 return Err(format!(
3505 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
3506 weight.out_features,
3507 weight.in_features,
3508 correction_bias.len(),
3509 experts_per_token,
3510 )
3511 .into());
3512 }
3513 let active_row = active
3514 .map(|mask| {
3515 if mask.len() != weight.out_features {
3516 return Err(format!(
3517 "sigmoid router active mask {} != experts {}",
3518 mask.len(),
3519 weight.out_features
3520 ));
3521 }
3522 Ok(mask
3523 .iter()
3524 .map(|&enabled| u8::from(enabled))
3525 .collect::<Vec<_>>())
3526 })
3527 .transpose()?
3528 .unwrap_or_else(|| vec![1; weight.out_features]);
3529 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
3530 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
3531
3532 let root = self
3533 .ranks
3534 .first()
3535 .ok_or("sigmoid router runtime has no root rank")?;
3536 let _main = root.gpu.enter_main()?;
3537 let bf16 = root.htod_bytes(weight.bytes)?;
3538 let weight_f32 = root.bf16_to_f32(
3539 &bf16.slice(0..bf16.len()),
3540 weight.out_features * weight.in_features,
3541 )?;
3542 Ok(ResidentSigmoidTopKRouter {
3543 weight: weight_f32,
3544 correction_bias: root.htod(correction_bias)?,
3545 active: root.htod_bytes(&active_row)?,
3546 root_device: root.ctx().ordinal(),
3547 input_width: weight.in_features,
3548 expert_count: weight.out_features,
3549 experts_per_token,
3550 active_count,
3551 scaling_factor,
3552 route_norm,
3553 })
3554 }
3555
3556 pub fn sigmoid_topk_replicated_device_rows_host(
3561 &self,
3562 router: &ResidentSigmoidTopKRouter,
3563 input: &ResidentReplicatedDeviceRows,
3564 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
3565 validate_replicated_device_rows(&self.ranks, input)?;
3566 if input.width != router.input_width {
3567 return Err(format!(
3568 "sigmoid router input width {} != resident width {}",
3569 input.width, router.input_width
3570 )
3571 .into());
3572 }
3573 let root = self
3574 .ranks
3575 .first()
3576 .ok_or("sigmoid router runtime has no root rank")?;
3577 let _main = root.gpu.enter_main()?;
3578 if root.ctx().ordinal() != router.root_device
3579 || router.weight.ordinal() != router.root_device
3580 || router.correction_bias.ordinal() != router.root_device
3581 || router.active.ordinal() != router.root_device
3582 {
3583 return Err("sigmoid router root residency changed".into());
3584 }
3585 let logits = root.router_gemv(
3586 &router.weight,
3587 &input.ranks[0],
3588 router.input_width,
3589 router.expert_count,
3590 input.tokens,
3591 )?;
3592 let (selected, weights) = root.moe_router_sigmoid_topk_host(
3593 &logits,
3594 input.tokens,
3595 router.expert_count,
3596 router.experts_per_token,
3597 router.active_count,
3598 &router.correction_bias,
3599 &router.active,
3600 router.scaling_factor,
3601 router.route_norm,
3602 )?;
3603 Ok(SigmoidTopKHostOutput {
3604 logits: root.dtoh(&logits)?,
3605 selected,
3606 weights,
3607 })
3608 }
3609
3610 pub fn upload_replicated_bf16_swiglu(
3612 &self,
3613 gate: Bf16Matrix<'_>,
3614 up: Bf16Matrix<'_>,
3615 down: Bf16Matrix<'_>,
3616 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
3617 gate.validate()?;
3618 up.validate()?;
3619 down.validate()?;
3620 if gate.in_features != up.in_features
3621 || gate.out_features != up.out_features
3622 || down.in_features != gate.out_features
3623 || down.out_features != gate.in_features
3624 {
3625 return Err(format!(
3626 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
3627 gate.out_features,
3628 gate.in_features,
3629 up.out_features,
3630 up.in_features,
3631 down.out_features,
3632 down.in_features,
3633 )
3634 .into());
3635 }
3636 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
3637 let mut up_ranks = Vec::with_capacity(self.ranks.len());
3638 let mut down_ranks = Vec::with_capacity(self.ranks.len());
3639 for engine in &self.ranks {
3640 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
3641 up_ranks.push(upload_bf16_rank(engine, up, false)?);
3642 down_ranks.push(upload_bf16_rank(engine, down, false)?);
3643 }
3644 Ok(ResidentReplicatedBf16SwiGlu {
3645 gate: gate_ranks,
3646 up: up_ranks,
3647 down: down_ranks,
3648 input_width: gate.in_features,
3649 intermediate_width: gate.out_features,
3650 })
3651 }
3652
3653 pub fn replicated_bf16_swiglu_resident_device(
3655 &self,
3656 mlp: &ResidentReplicatedBf16SwiGlu,
3657 input: &ResidentReplicatedDeviceRows,
3658 activation_limit: Option<f32>,
3659 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3660 validate_step_expert_activation_limit(activation_limit)?;
3661 validate_replicated_device_rows(&self.ranks, input)?;
3662 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3663 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3664 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3665 if input.width != mlp.input_width
3666 || mlp.gate.len() != self.ranks.len()
3667 || mlp.up.len() != self.ranks.len()
3668 || mlp.down.len() != self.ranks.len()
3669 {
3670 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3671 }
3672
3673 let mut outputs = Vec::with_capacity(self.ranks.len());
3674 for rank in 0..self.ranks.len() {
3675 let engine = &self.ranks[rank];
3676 let gate = run_resident_bf16_rank_device(
3677 engine,
3678 &mlp.gate[rank],
3679 &input.ranks[rank],
3680 input.tokens,
3681 None,
3682 self.bulk_p2p,
3683 )?;
3684 let up = run_resident_bf16_rank_device(
3685 engine,
3686 &mlp.up[rank],
3687 &input.ranks[rank],
3688 input.tokens,
3689 None,
3690 self.bulk_p2p,
3691 )?;
3692 let _main = engine.gpu.enter_main()?;
3693 let values = input
3694 .tokens
3695 .checked_mul(mlp.intermediate_width)
3696 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3697 let mut activation = engine.uninit(values)?;
3698 if let Some(limit) = activation_limit {
3699 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3700 } else {
3701 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3702 }
3703 outputs.push(run_resident_bf16_rank_device(
3704 engine,
3705 &mlp.down[rank],
3706 &activation,
3707 input.tokens,
3708 None,
3709 self.bulk_p2p,
3710 )?);
3711 }
3712 Ok(ResidentReplicatedDeviceRows {
3713 ranks: outputs,
3714 tokens: input.tokens,
3715 width: mlp.input_width,
3716 })
3717 }
3718
3719 pub fn rms_norm_replicated_device_rows(
3721 &self,
3722 input: &ResidentReplicatedDeviceRows,
3723 weight: &[f32],
3724 eps: f32,
3725 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3726 validate_replicated_device_rows(&self.ranks, input)?;
3727 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3728 return Err(format!(
3729 "replicated RMS norm weight/eps {}/{} != width {}",
3730 weight.len(),
3731 eps,
3732 input.width
3733 )
3734 .into());
3735 }
3736 let mut ranks = Vec::with_capacity(self.ranks.len());
3737 for (rank, engine) in self.ranks.iter().enumerate() {
3738 let _main = engine.gpu.enter_main()?;
3739 let weight = engine.htod(weight)?;
3740 let mut output = engine.uninit(input.tokens * input.width)?;
3741 engine.rms_norm(
3742 &input.ranks[rank],
3743 &weight,
3744 &mut output,
3745 input.width,
3746 input.tokens,
3747 eps,
3748 )?;
3749 ranks.push(output);
3750 }
3751 Ok(ResidentReplicatedDeviceRows {
3752 ranks,
3753 tokens: input.tokens,
3754 width: input.width,
3755 })
3756 }
3757
3758 pub fn add_rms_norm_replicated_device_rows(
3760 &self,
3761 input: &ResidentReplicatedDeviceRows,
3762 update: &ResidentReplicatedDeviceRows,
3763 weight: &[f32],
3764 eps: f32,
3765 ) -> Result<
3766 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3767 Box<dyn std::error::Error>,
3768 > {
3769 validate_replicated_device_rows(&self.ranks, input)?;
3770 validate_replicated_device_rows(&self.ranks, update)?;
3771 if input.tokens != update.tokens
3772 || input.width != update.width
3773 || weight.len() != input.width
3774 || !eps.is_finite()
3775 || eps <= 0.0
3776 {
3777 return Err(format!(
3778 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3779 input.tokens,
3780 input.width,
3781 update.tokens,
3782 update.width,
3783 weight.len(),
3784 )
3785 .into());
3786 }
3787 let values = input.tokens * input.width;
3788 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3789 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3790 for (rank, engine) in self.ranks.iter().enumerate() {
3791 let _main = engine.gpu.enter_main()?;
3792 let weight = engine.htod(weight)?;
3793 let mut residual = engine.uninit(values)?;
3794 let mut normalized = engine.uninit(values)?;
3795 engine.add_rms_norm(
3796 &input.ranks[rank],
3797 &update.ranks[rank],
3798 &weight,
3799 &mut residual,
3800 &mut normalized,
3801 input.width,
3802 input.tokens,
3803 eps,
3804 )?;
3805 residual_ranks.push(residual);
3806 normalized_ranks.push(normalized);
3807 }
3808 Ok((
3809 ResidentReplicatedDeviceRows {
3810 ranks: residual_ranks,
3811 tokens: input.tokens,
3812 width: input.width,
3813 },
3814 ResidentReplicatedDeviceRows {
3815 ranks: normalized_ranks,
3816 tokens: input.tokens,
3817 width: input.width,
3818 },
3819 ))
3820 }
3821
3822 pub fn collect_replicated_device_rows(
3823 &self,
3824 rows: &ResidentReplicatedDeviceRows,
3825 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3826 validate_replicated_device_rows(&self.ranks, rows)?;
3827 let mut outputs = Vec::with_capacity(self.ranks.len());
3828 for (rank, engine) in self.ranks.iter().enumerate() {
3829 let _main = engine.gpu.enter_main()?;
3830 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3831 }
3832 Ok(outputs)
3833 }
3834
3835 #[allow(clippy::manual_is_multiple_of)] pub fn upload_bf16_row_parallel(
3837 &self,
3838 matrix: Bf16Matrix<'_>,
3839 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3840 matrix.validate()?;
3841 let tp = self.ranks.len();
3842 if matrix.in_features % tp != 0 {
3843 return Err(format!(
3844 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3845 matrix.in_features
3846 )
3847 .into());
3848 }
3849 let mut ranks = Vec::with_capacity(tp);
3850 for (rank, engine) in self.ranks.iter().enumerate() {
3851 let shard = bf16_row_shard(matrix, tp, rank)?;
3852 ranks.push(upload_bf16_rank(
3853 engine,
3854 Bf16Matrix {
3855 bytes: &shard,
3856 out_features: matrix.out_features,
3857 in_features: matrix.in_features / tp,
3858 },
3859 false,
3860 )?);
3861 }
3862 Ok(ResidentBf16RowParallel {
3863 ranks,
3864 out_features: matrix.out_features,
3865 in_features: matrix.in_features,
3866 })
3867 }
3868
3869 pub fn bf16_row_parallel_resident(
3870 &self,
3871 matrix: &ResidentBf16RowParallel,
3872 activations: &[f32],
3873 tokens: usize,
3874 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3875 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3876 validate_activations(activations, tokens, matrix.in_features)?;
3877 let tp = self.ranks.len();
3878 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3879 let mut rank_partials = Vec::with_capacity(tp);
3880 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3881 let local_activations =
3882 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3883 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3884 for (sum, value) in reduced.iter_mut().zip(&partial) {
3885 *sum += value;
3886 }
3887 rank_partials.push(partial);
3888 }
3889 Ok(RowParallelResult {
3890 reduced,
3891 rank_partials,
3892 })
3893 }
3894
3895 pub fn upload_step_bf16_row_parallel(
3897 &self,
3898 matrix: Bf16Matrix<'_>,
3899 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3900 self.upload_step_bf16_row_parallel_inner(matrix, false)
3901 }
3902
3903 pub fn upload_step_bf16_row_parallel_f32_mirror(
3904 &self,
3905 matrix: Bf16Matrix<'_>,
3906 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3907 self.upload_step_bf16_row_parallel_inner(matrix, true)
3908 }
3909
3910 fn upload_step_bf16_row_parallel_inner(
3911 &self,
3912 matrix: Bf16Matrix<'_>,
3913 f32_mirror: bool,
3914 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3915 matrix.validate()?;
3916 let tp = self.ranks.len();
3917 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3918 let local_in = matrix.in_features / tp;
3919 let blocks_per_rank = local_in / canonical_chunk_cols;
3920 let mut ranks = Vec::with_capacity(tp);
3921 for (rank, engine) in self.ranks.iter().enumerate() {
3922 let mut blocks = Vec::with_capacity(blocks_per_rank);
3923 for block in 0..blocks_per_rank {
3924 let global_block = rank * blocks_per_rank + block;
3925 let col_start = global_block * canonical_chunk_cols;
3926 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3927 blocks.push(upload_bf16_rank(
3928 engine,
3929 Bf16Matrix {
3930 bytes: &bytes,
3931 out_features: matrix.out_features,
3932 in_features: canonical_chunk_cols,
3933 },
3934 f32_mirror,
3935 )?);
3936 }
3937 ranks.push(blocks);
3938 }
3939 Ok(ResidentStepBf16RowParallel {
3940 ranks,
3941 out_features: matrix.out_features,
3942 in_features: matrix.in_features,
3943 canonical_chunk_cols,
3944 })
3945 }
3946
3947 pub fn step_bf16_row_parallel_resident(
3952 &self,
3953 matrix: &ResidentStepBf16RowParallel,
3954 activations: &[f32],
3955 tokens: usize,
3956 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3957 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3958 validate_activations(activations, tokens, matrix.in_features)?;
3959 let root = &self.ranks[0];
3960 let output_len = tokens
3961 .checked_mul(matrix.out_features)
3962 .ok_or("Step BF16 row output size overflow")?;
3963 let mut reduced = {
3964 let _main = root.gpu.enter_main()?;
3965 root.htod(&vec![0.0f32; output_len])?
3966 };
3967 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3968 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3969 for (block, resident) in blocks.iter().enumerate() {
3970 let global_block = rank * blocks_per_rank + block;
3971 let input = activation_shard(
3972 activations,
3973 tokens,
3974 matrix.in_features,
3975 PRODUCT_MAX_CARDS,
3976 global_block,
3977 );
3978 let partial =
3979 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3980 let next = {
3981 let _main = root.gpu.enter_main()?;
3982 let partial = root.htod(&partial)?;
3983 let mut next = root.uninit(output_len)?;
3984 root.add(&reduced, &partial, &mut next, output_len)?;
3985 next
3986 };
3987 reduced = next;
3988 }
3989 }
3990 let _main = root.gpu.enter_main()?;
3991 root.dtoh(&reduced)
3992 }
3993
3994 pub fn step_bf16_row_parallel_resident_native(
4000 &self,
4001 matrix: &ResidentStepBf16RowParallel,
4002 activations: &[f32],
4003 tokens: usize,
4004 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4005 if self.ranks.len() > 1 && !self.native_p2p {
4006 return Err("native Step BF16 row parallelism requires P2P ranks".into());
4007 }
4008 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4009 validate_activations(activations, tokens, matrix.in_features)?;
4010 let root = &self.ranks[0];
4011 let root_input = {
4012 let _main = root.gpu.enter_main()?;
4013 root.htod(activations)?
4014 };
4015 {
4018 let _main = root.gpu.enter_main()?;
4019 root.stream().synchronize()?;
4020 }
4021 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
4022 let _main = root.gpu.enter_main()?;
4023 root.dtoh(&reduced)
4024 }
4025
4026 pub fn step_bf16_row_parallel_resident_native_device(
4034 &self,
4035 matrix: &ResidentStepBf16RowParallel,
4036 root_activation: &CudaSlice<f32>,
4037 tokens: usize,
4038 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4039 if self.ranks.len() > 1 && !self.native_p2p {
4040 return Err("native Step BF16 row parallelism requires P2P ranks".into());
4041 }
4042 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4043 let values = tokens
4044 .checked_mul(matrix.in_features)
4045 .ok_or("device Step BF16 row activation size overflow")?;
4046 let root = &self.ranks[0];
4047 if tokens == 0
4048 || root_activation.len() < values
4049 || root_activation.ordinal() != root.ctx().ordinal()
4050 {
4051 return Err("device Step BF16 row root activation geometry mismatch".into());
4052 }
4053 let root_input = {
4054 let _main = root.gpu.enter_main()?;
4055 let mut root_input = root.uninit(values)?;
4056 root.stream()
4057 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
4058 root.stream().synchronize()?; root_input
4060 };
4061 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
4062 let _main = root.gpu.enter_main()?;
4063 root.stream().synchronize()?;
4064 Ok(reduced)
4065 }
4066
4067 fn step_bf16_row_native_reduce_from_root(
4072 &self,
4073 matrix: &ResidentStepBf16RowParallel,
4074 root_input: &CudaSlice<f32>,
4075 tokens: usize,
4076 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4077 let root = &self.ranks[0];
4078 let output_len = tokens
4079 .checked_mul(matrix.out_features)
4080 .ok_or("native Step BF16 row output size overflow")?;
4081 let mut reduced = {
4082 let _main = root.gpu.enter_main()?;
4083 root.htod(&vec![0.0f32; output_len])?
4084 };
4085 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4086 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
4087 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
4088 let mut remote_partial_keepalive = Vec::new();
4089 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4090 for (block, resident) in blocks.iter().enumerate() {
4091 let global_block = rank * blocks_per_rank + block;
4092 let col_start = global_block * matrix.canonical_chunk_cols;
4093 let block_len = tokens
4094 .checked_mul(matrix.canonical_chunk_cols)
4095 .ok_or("native Step BF16 row block size overflow")?;
4096 let block_input = if self.bulk_p2p {
4097 let root_packed = {
4098 let _main = root.gpu.enter_main()?;
4099 let mut root_packed = root.uninit(block_len)?;
4100 root.copy_rows_strided(
4101 root_input,
4102 &mut root_packed,
4103 matrix.canonical_chunk_cols,
4104 tokens,
4105 matrix.in_features,
4106 col_start,
4107 )?;
4108 root_packed
4109 };
4110 if rank == 0 {
4111 root_packed
4112 } else {
4113 {
4116 let _main = root.gpu.enter_main()?;
4117 root.stream().synchronize()?;
4118 }
4119 let engine = &self.ranks[rank];
4120 let _main = engine.gpu.enter_main()?;
4121 let mut block_input = engine.uninit(block_len)?;
4122 engine
4123 .stream()
4124 .memcpy_dtod(&root_packed, &mut block_input)?;
4125 root_packed_keepalive.push(root_packed);
4126 block_input
4127 }
4128 } else {
4129 let engine = &self.ranks[rank];
4130 let _main = engine.gpu.enter_main()?;
4131 let mut block_input = engine.uninit(block_len)?;
4132 for token in 0..tokens {
4133 let source_start = token * matrix.in_features + col_start;
4134 let source = root_input
4135 .slice(source_start..source_start + matrix.canonical_chunk_cols);
4136 let destination_start = token * matrix.canonical_chunk_cols;
4137 let mut destination = block_input.slice_mut(
4138 destination_start..destination_start + matrix.canonical_chunk_cols,
4139 );
4140 engine.stream().memcpy_dtod(&source, &mut destination)?;
4141 }
4142 block_input
4143 };
4144 let partial = run_resident_bf16_rank_device(
4145 &self.ranks[rank],
4146 resident,
4147 &block_input,
4148 tokens,
4149 None,
4150 self.bulk_p2p,
4151 )?;
4152 block_input_keepalive.push(block_input);
4153 let root_partial = if rank == 0 {
4154 partial
4155 } else {
4156 {
4159 let engine = &self.ranks[rank];
4160 let _main = engine.gpu.enter_main()?;
4161 engine.stream().synchronize()?;
4162 }
4163 let _main = root.gpu.enter_main()?;
4164 let mut peer_partial = root.uninit(output_len)?;
4165 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4166 remote_partial_keepalive.push(partial);
4167 peer_partial
4168 };
4169 let next = {
4170 let _main = root.gpu.enter_main()?;
4171 let mut next = root.uninit(output_len)?;
4172 root.add(&reduced, &root_partial, &mut next, output_len)?;
4173 next
4174 };
4175 reduced = next;
4176 }
4177 }
4178 {
4179 let _main = root.gpu.enter_main()?;
4180 root.stream().synchronize()?;
4181 }
4182 drop(remote_partial_keepalive);
4183 drop(root_packed_keepalive);
4184 drop(block_input_keepalive);
4185 Ok(reduced)
4186 }
4187
4188 pub fn step_bf16_row_parallel_resident_root_device(
4191 &self,
4192 matrix: &ResidentStepBf16RowParallel,
4193 rank_activations: &[CudaSlice<f32>],
4194 tokens: usize,
4195 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4196 if self.ranks.len() > 1 && !self.native_p2p {
4197 return Err(
4198 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
4199 );
4200 }
4201 validate_step_bf16_row_residency(&self.ranks, matrix)?;
4202 let local_width = matrix.in_features / self.ranks.len();
4203 let shard_len = tokens
4204 .checked_mul(local_width)
4205 .ok_or("device Step BF16 row shard size overflow")?;
4206 if tokens == 0
4207 || rank_activations.len() != self.ranks.len()
4208 || rank_activations
4209 .iter()
4210 .zip(&self.ranks)
4211 .any(|(rows, engine)| {
4212 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
4213 })
4214 {
4215 return Err("device Step BF16 row activation shard geometry changed".into());
4216 }
4217
4218 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
4219 let mut block_inputs = Vec::with_capacity(self.ranks.len());
4220 let mut partials = Vec::with_capacity(self.ranks.len());
4221 for (rank, blocks) in matrix.ranks.iter().enumerate() {
4222 if blocks.len() != blocks_per_rank {
4223 return Err(format!(
4224 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
4225 blocks.len()
4226 )
4227 .into());
4228 }
4229 let engine = &self.ranks[rank];
4230 let _main = engine.gpu.enter_main()?;
4231 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
4232 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
4233 for (block, resident) in blocks.iter().enumerate() {
4234 let block_len = tokens
4235 .checked_mul(matrix.canonical_chunk_cols)
4236 .ok_or("device Step BF16 row block size overflow")?;
4237 let mut block_input = engine.uninit(block_len)?;
4238 let local_col_start = block * matrix.canonical_chunk_cols;
4239 if self.bulk_p2p {
4240 engine.copy_rows_strided(
4241 &rank_activations[rank],
4242 &mut block_input,
4243 matrix.canonical_chunk_cols,
4244 tokens,
4245 local_width,
4246 local_col_start,
4247 )?;
4248 } else {
4249 for token in 0..tokens {
4250 let source_start = token * local_width + local_col_start;
4251 let source = rank_activations[rank]
4252 .slice(source_start..source_start + matrix.canonical_chunk_cols);
4253 let destination_start = token * matrix.canonical_chunk_cols;
4254 let mut destination = block_input.slice_mut(
4255 destination_start..destination_start + matrix.canonical_chunk_cols,
4256 );
4257 engine.stream().memcpy_dtod(&source, &mut destination)?;
4258 }
4259 }
4260 let partial = run_resident_bf16_rank_device(
4261 engine,
4262 resident,
4263 &block_input,
4264 tokens,
4265 None,
4266 self.bulk_p2p,
4267 )?;
4268 rank_inputs.push(block_input);
4269 rank_partials.push(partial);
4270 }
4271 block_inputs.push(rank_inputs);
4272 partials.push(rank_partials);
4273 }
4274 for engine in self.ranks.iter().skip(1) {
4275 let _main = engine.gpu.enter_main()?;
4276 engine.stream().synchronize()?;
4277 }
4278
4279 let output_len = tokens
4280 .checked_mul(matrix.out_features)
4281 .ok_or("device Step BF16 row output size overflow")?;
4282 let root = &self.ranks[0];
4283 let _main = root.gpu.enter_main()?;
4284 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
4285 let mut remote_partials = Vec::new();
4286 for (rank, rank_partials) in partials.into_iter().enumerate() {
4287 for partial in rank_partials {
4288 let root_partial = if rank == 0 {
4289 partial
4290 } else {
4291 let mut peer_partial = root.uninit(output_len)?;
4292 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
4293 remote_partials.push(partial);
4294 peer_partial
4295 };
4296 let mut next = root.uninit(output_len)?;
4297 root.add(&reduced, &root_partial, &mut next, output_len)?;
4298 reduced = next;
4299 }
4300 }
4301 root.stream().synchronize()?;
4302 drop(remote_partials);
4303 drop(block_inputs);
4304 Ok(reduced)
4305 }
4306
4307 pub fn step_bf16_row_parallel_resident_replicated_device(
4309 &self,
4310 matrix: &ResidentStepBf16RowParallel,
4311 rank_activations: &[CudaSlice<f32>],
4312 tokens: usize,
4313 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4314 let reduced =
4315 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
4316 let output_len = tokens
4317 .checked_mul(matrix.out_features)
4318 .ok_or("device Step BF16 row output size overflow")?;
4319 let mut ranks = Vec::with_capacity(self.ranks.len());
4320 ranks.push(reduced);
4321 for engine in self.ranks.iter().skip(1) {
4322 let _main = engine.gpu.enter_main()?;
4323 let mut peer_output = engine.uninit(output_len)?;
4324 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
4325 ranks.push(peer_output);
4326 }
4327 Ok(ResidentReplicatedDeviceRows {
4328 ranks,
4329 tokens,
4330 width: matrix.out_features,
4331 })
4332 }
4333
4334 pub fn upload_expert(
4335 &self,
4336 gate: E4m3BlockMatrix<'_>,
4337 up: E4m3BlockMatrix<'_>,
4338 down: E4m3BlockMatrix<'_>,
4339 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
4340 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4341 return Err("TP expert gate/up dimensions differ".into());
4342 }
4343 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4344 return Err(format!(
4345 "TP expert down {}x{} does not invert gate/up {}x{}",
4346 down.out_features, down.in_features, gate.out_features, gate.in_features
4347 )
4348 .into());
4349 }
4350 Ok(ResidentTpExpert {
4351 gate: self.upload_column_parallel(gate)?,
4352 up: self.upload_column_parallel(up)?,
4353 down: self.upload_row_parallel(down)?,
4354 input_width: gate.in_features,
4355 expert_width: gate.out_features,
4356 })
4357 }
4358
4359 pub fn run_expert(
4360 &self,
4361 expert: &ResidentTpExpert,
4362 input: &[f32],
4363 tokens: usize,
4364 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4365 validate_activations(input, tokens, expert.input_width)?;
4366 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
4367 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
4368 let activated: Vec<f32> = gate
4369 .gathered
4370 .iter()
4371 .zip(&up.gathered)
4372 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4373 .collect();
4374 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
4375 Ok(self
4376 .row_parallel_resident(&expert.down, &activated, tokens)?
4377 .reduced)
4378 }
4379
4380 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel(
4382 &self,
4383 gate: E4m3ExpertBank<'_>,
4384 up: E4m3ExpertBank<'_>,
4385 down: E4m3ExpertBank<'_>,
4386 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
4387 gate.validate()?;
4388 up.validate()?;
4389 down.validate()?;
4390 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4391 return Err("EP gate/up/down expert counts differ".into());
4392 }
4393 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4394 return Err("EP gate/up dimensions differ".into());
4395 }
4396 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4397 return Err(format!(
4398 "EP down {}x{} does not invert gate/up {}x{}",
4399 down.out_features, down.in_features, gate.out_features, gate.in_features
4400 )
4401 .into());
4402 }
4403 if gate.expert_count % self.ranks.len() != 0 {
4404 return Err(format!(
4405 "EP expert count {} is not divisible by {} ranks",
4406 gate.expert_count,
4407 self.ranks.len()
4408 )
4409 .into());
4410 }
4411
4412 let per_rank = gate.expert_count / self.ranks.len();
4413 let mut ranks = Vec::with_capacity(self.ranks.len());
4414 for (rank, engine) in self.ranks.iter().enumerate() {
4415 let expert_range = rank * per_rank..(rank + 1) * per_rank;
4416 ranks.push(ResidentEpRank {
4417 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
4418 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
4419 down: upload_expert_bank_rank(engine, down, expert_range)?,
4420 });
4421 }
4422 Ok(ResidentExpertParallel {
4423 ranks,
4424 expert_count: gate.expert_count,
4425 input_width: gate.in_features,
4426 expert_width: gate.out_features,
4427 })
4428 }
4429
4430 #[allow(clippy::too_many_arguments)]
4436 pub fn prepare_step_grouped_fp8_gate(
4437 &self,
4438 gate: E4m3ExpertBank<'_>,
4439 up: E4m3ExpertBank<'_>,
4440 down: E4m3ExpertBank<'_>,
4441 input: &[f32],
4442 tokens: usize,
4443 selected: &[usize],
4444 activation_limit: Option<f32>,
4445 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
4446 gate.validate()?;
4447 up.validate()?;
4448 down.validate()?;
4449 validate_step_expert_activation_limit(activation_limit)?;
4450 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
4451 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
4452 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
4453 {
4454 return Err(format!(
4455 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
4456 got gate/up/down={}/{}/{}",
4457 gate.expert_count, up.expert_count, down.expert_count,
4458 )
4459 .into());
4460 }
4461 if gate.in_features != up.in_features
4462 || gate.out_features != STEP_GROUPED_FP8_WIDTH
4463 || up.out_features != STEP_GROUPED_FP8_WIDTH
4464 || down.in_features != STEP_GROUPED_FP8_WIDTH
4465 || down.out_features != gate.in_features
4466 {
4467 return Err(format!(
4468 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
4469 gate.out_features,
4470 gate.in_features,
4471 up.out_features,
4472 up.in_features,
4473 down.out_features,
4474 down.in_features,
4475 )
4476 .into());
4477 }
4478 validate_activations(input, tokens, gate.in_features)?;
4479 let pairs = tokens
4480 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4481 .ok_or("official Step grouped FP8 route count overflow")?;
4482 if selected.len() != pairs {
4483 return Err(format!(
4484 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
4485 ({pairs})",
4486 selected.len()
4487 )
4488 .into());
4489 }
4490 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
4491 let mut unique = routes.to_vec();
4492 unique.sort_unstable();
4493 unique.dedup();
4494 if unique.len() != STEP_GROUPED_FP8_TOP_K {
4495 return Err(format!(
4496 "official Step grouped FP8 token {token} routes are not top-8 unique: \
4497 {routes:?}"
4498 )
4499 .into());
4500 }
4501 }
4502
4503 let engine = self
4504 .ranks
4505 .first()
4506 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4507 let _main = engine.gpu.enter_main()?;
4508 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
4509 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
4510 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
4511 let down = upload_expert_bank_rank(engine, down, expert_range)?;
4512 let input = engine.htod(input)?;
4513 let route_csr = ExpertCsr::from_token_routes(
4514 STEP_GROUPED_FP8_EXPERTS,
4515 tokens,
4516 STEP_GROUPED_FP8_TOP_K,
4517 selected,
4518 )?
4519 .upload(engine)?;
4520 let pair_rows = (0..pairs).collect::<Vec<_>>();
4521 let down_csr =
4522 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
4523 .upload(engine)?;
4524 let gate_workspace =
4525 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
4526 let up_workspace =
4527 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
4528 let down_workspace =
4529 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
4530 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
4531 Ok(PreparedStepGroupedFp8Gate {
4532 device: engine.ctx().ordinal(),
4533 gate,
4534 up,
4535 down,
4536 input,
4537 route_csr,
4538 down_csr,
4539 gate_workspace,
4540 up_workspace,
4541 down_workspace,
4542 activation,
4543 activation_limit,
4544 tokens,
4545 pairs,
4546 })
4547 }
4548
4549 pub fn run_step_grouped_fp8_gate(
4551 &self,
4552 plan: &mut PreparedStepGroupedFp8Gate,
4553 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4554 let engine = self
4555 .ranks
4556 .first()
4557 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
4558 if engine.ctx().ordinal() != plan.device {
4559 return Err(format!(
4560 "official Step grouped FP8 plan device {} != rank-zero device {}",
4561 plan.device,
4562 engine.ctx().ordinal()
4563 )
4564 .into());
4565 }
4566 let _main = engine.gpu.enter_main()?;
4567
4568 plan.gate_workspace.quantize(engine, &plan.input)?;
4569 plan.gate_workspace.project(
4570 engine,
4571 &plan.gate.codes,
4572 &plan.gate.scales,
4573 &plan.route_csr,
4574 plan.gate.code_stride,
4575 plan.gate.scale_stride,
4576 1.0,
4577 )?;
4578 plan.up_workspace.quantize(engine, &plan.input)?;
4579 plan.up_workspace.project(
4580 engine,
4581 &plan.up.codes,
4582 &plan.up.scales,
4583 &plan.route_csr,
4584 plan.up.code_stride,
4585 plan.up.scale_stride,
4586 1.0,
4587 )?;
4588 if let Some(limit) = plan.activation_limit {
4589 engine.silu_clamped_mul_host_expf(
4590 plan.gate_workspace.output(),
4591 plan.up_workspace.output(),
4592 limit,
4593 &mut plan.activation,
4594 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4595 )?;
4596 } else {
4597 engine.silu_mul_host_expf(
4598 plan.gate_workspace.output(),
4599 plan.up_workspace.output(),
4600 &mut plan.activation,
4601 plan.pairs * STEP_GROUPED_FP8_WIDTH,
4602 )?;
4603 }
4604 plan.down_workspace.quantize(engine, &plan.activation)?;
4605 plan.down_workspace.project(
4606 engine,
4607 &plan.down.codes,
4608 &plan.down.scales,
4609 &plan.down_csr,
4610 plan.down.code_stride,
4611 plan.down.scale_stride,
4612 1.0,
4613 )?;
4614
4615 Ok(StepGroupedFp8ProjectionOutput {
4616 gate: engine.dtoh(plan.gate_workspace.output())?,
4617 up: engine.dtoh(plan.up_workspace.output())?,
4618 down: engine.dtoh(plan.down_workspace.output())?,
4619 })
4620 }
4621
4622 pub fn prepare_step_grouped_expert_parallel_gate(
4623 &self,
4624 experts: &ResidentExpertParallel,
4625 input: &[f32],
4626 tokens: usize,
4627 selected: &[usize],
4628 activation_limit: Option<f32>,
4629 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4630 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
4631 experts,
4632 input,
4633 tokens,
4634 selected,
4635 activation_limit,
4636 tokens,
4637 )
4638 }
4639
4640 #[allow(clippy::too_many_arguments)]
4641 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
4642 &self,
4643 experts: &ResidentExpertParallel,
4644 input: &[f32],
4645 tokens: usize,
4646 selected: &[usize],
4647 activation_limit: Option<f32>,
4648 max_tokens: usize,
4649 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
4650 if !self.native_p2p || !self.ep_device_arithmetic {
4651 return Err(
4652 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
4653 );
4654 }
4655 validate_step_expert_activation_limit(activation_limit)?;
4656 validate_ep_residency(&self.ranks, experts)?;
4657 validate_activations(input, tokens, experts.input_width)?;
4658 if max_tokens < tokens || max_tokens > i32::MAX as usize {
4659 return Err(format!(
4660 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
4661 )
4662 .into());
4663 }
4664 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4665 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4666 {
4667 return Err(format!(
4668 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4669 STEP_GROUPED_FP8_EXPERTS,
4670 STEP_GROUPED_FP8_WIDTH,
4671 experts.expert_count,
4672 experts.expert_width,
4673 )
4674 .into());
4675 }
4676 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4677 let max_pairs = max_tokens
4678 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4679 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4680 let input_capacity = max_tokens
4681 .checked_mul(experts.input_width)
4682 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4683
4684 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4685 for engine in &self.ranks {
4686 let _main = engine.gpu.enter_main()?;
4687 rank_inputs.push(engine.uninit(input_capacity)?);
4688 }
4689
4690 let mut owners = Vec::with_capacity(self.ranks.len());
4691 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4692 if rank.gate.expert_range != rank.up.expert_range
4693 || rank.gate.expert_range != rank.down.expert_range
4694 {
4695 return Err(format!(
4696 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4697 owner_rank
4698 )
4699 .into());
4700 }
4701 let local_experts = rank.gate.expert_range.len();
4702 let engine = &self.ranks[owner_rank];
4703 let _main = engine.gpu.enter_main()?;
4704 let route_csr =
4705 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4706 let down_csr =
4707 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4708 let gate_workspace = Fp8GroupedWorkspace::new(
4709 engine,
4710 experts.input_width,
4711 experts.expert_width,
4712 max_tokens,
4713 max_pairs,
4714 )?;
4715 let up_workspace = Fp8GroupedWorkspace::new(
4716 engine,
4717 experts.input_width,
4718 experts.expert_width,
4719 max_tokens,
4720 max_pairs,
4721 )?;
4722 let down_workspace = Fp8GroupedWorkspace::new(
4723 engine,
4724 experts.expert_width,
4725 experts.input_width,
4726 max_pairs,
4727 max_pairs,
4728 )?;
4729 let activation = engine.uninit(
4730 max_pairs
4731 .checked_mul(experts.expert_width)
4732 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4733 )?;
4734 owners.push(PreparedStepGroupedExpertOwner {
4735 rank: owner_rank,
4736 global_pairs: Vec::new(),
4737 route_csr,
4738 down_csr,
4739 gate_workspace,
4740 up_workspace,
4741 down_workspace,
4742 activation,
4743 });
4744 }
4745
4746 let mut plan = PreparedStepGroupedExpertParallelGate {
4747 rank_inputs,
4748 owners,
4749 activation_limit,
4750 tokens: 0,
4751 pairs: 0,
4752 max_tokens,
4753 max_pairs,
4754 input_width: experts.input_width,
4755 expert_width: experts.expert_width,
4756 generation: 0,
4757 executed_generation: None,
4758 ready: false,
4759 };
4760 self.refresh_step_grouped_expert_parallel_gate(
4761 experts, &mut plan, input, tokens, selected,
4762 )?;
4763 Ok(plan)
4764 }
4765
4766 #[allow(clippy::type_complexity)] fn prepare_step_grouped_expert_parallel_refresh(
4768 &self,
4769 experts: &ResidentExpertParallel,
4770 plan: &PreparedStepGroupedExpertParallelGate,
4771 tokens: usize,
4772 selected: &[usize],
4773 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4774 {
4775 validate_ep_residency(&self.ranks, experts)?;
4776 if plan.rank_inputs.len() != self.ranks.len()
4777 || plan.owners.len() != self.ranks.len()
4778 || plan.input_width != experts.input_width
4779 || plan.expert_width != experts.expert_width
4780 || tokens > plan.max_tokens
4781 {
4782 return Err(format!(
4783 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4784 input={}/{} expert={}/{} tokens={}/{}",
4785 plan.rank_inputs.len(),
4786 self.ranks.len(),
4787 plan.owners.len(),
4788 self.ranks.len(),
4789 plan.input_width,
4790 experts.input_width,
4791 plan.expert_width,
4792 experts.expert_width,
4793 tokens,
4794 plan.max_tokens,
4795 )
4796 .into());
4797 }
4798 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4799 if pairs > plan.max_pairs {
4800 return Err(format!(
4801 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4802 plan.max_pairs
4803 )
4804 .into());
4805 }
4806 let next_generation = plan
4807 .generation
4808 .checked_add(1)
4809 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4810 let owner_routes = partition_expert_owner_routes(
4811 experts.expert_count,
4812 self.ranks.len(),
4813 tokens,
4814 STEP_GROUPED_FP8_TOP_K,
4815 selected,
4816 )?;
4817 let mut schedules = Vec::with_capacity(self.ranks.len());
4818 for routes in owner_routes {
4819 if routes.selected.is_empty() {
4820 schedules.push(None);
4821 continue;
4822 }
4823 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4824 let local_pairs = routes.selected.len();
4825 let route_csr = ExpertCsr::from_pair_rows(
4826 local_experts,
4827 tokens,
4828 &routes.selected,
4829 &routes.token_rows,
4830 )?;
4831 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4832 let down_csr = ExpertCsr::from_pair_rows(
4833 local_experts,
4834 local_pairs,
4835 &routes.selected,
4836 &down_rows,
4837 )?;
4838 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4839 global_pairs: routes.global_pairs,
4840 route_csr,
4841 down_csr,
4842 }));
4843 }
4844 Ok((pairs, next_generation, schedules))
4845 }
4846
4847 fn commit_step_grouped_expert_parallel_refresh(
4848 &self,
4849 plan: &mut PreparedStepGroupedExpertParallelGate,
4850 tokens: usize,
4851 pairs: usize,
4852 next_generation: u64,
4853 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4854 ) -> Result<(), Box<dyn std::error::Error>> {
4855 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4856 let engine = &self.ranks[owner.rank];
4857 let _main = engine.gpu.enter_main()?;
4858 if let Some(schedule) = schedule {
4859 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4860 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4861 owner.global_pairs = schedule.global_pairs;
4862 } else {
4863 owner.route_csr.clear();
4864 owner.down_csr.clear();
4865 owner.global_pairs.clear();
4866 }
4867 }
4868 plan.tokens = tokens;
4869 plan.pairs = pairs;
4870 plan.generation = next_generation;
4871 plan.ready = true;
4872 Ok(())
4873 }
4874
4875 pub fn refresh_step_grouped_expert_parallel_gate(
4876 &self,
4877 experts: &ResidentExpertParallel,
4878 plan: &mut PreparedStepGroupedExpertParallelGate,
4879 input: &[f32],
4880 tokens: usize,
4881 selected: &[usize],
4882 ) -> Result<(), Box<dyn std::error::Error>> {
4883 validate_activations(input, tokens, experts.input_width)?;
4884 let (pairs, next_generation, schedules) =
4885 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4886
4887 plan.ready = false;
4888 plan.executed_generation = None;
4889 {
4890 let root = &self.ranks[0];
4891 let _main = root.gpu.enter_main()?;
4892 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4893 root.stream().memcpy_htod(input, &mut destination)?;
4894 root.stream().synchronize()?;
4895 }
4896 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4897 let root_input = &root_inputs[0];
4898 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4899 let engine = &self.ranks[rank + 1];
4900 let _main = engine.gpu.enter_main()?;
4901 let mut destination = peer_input.slice_mut(0..input.len());
4902 engine
4903 .stream()
4904 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4905 }
4906 self.commit_step_grouped_expert_parallel_refresh(
4907 plan,
4908 tokens,
4909 pairs,
4910 next_generation,
4911 schedules,
4912 )
4913 }
4914
4915 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4920 &self,
4921 experts: &ResidentExpertParallel,
4922 plan: &mut PreparedStepGroupedExpertParallelGate,
4923 input: &CudaSlice<f32>,
4924 tokens: usize,
4925 selected: &[usize],
4926 ) -> Result<(), Box<dyn std::error::Error>> {
4927 let input_values = tokens
4928 .checked_mul(experts.input_width)
4929 .ok_or("Step owner-grouped FP8 input size overflow")?;
4930 let root = self
4931 .ranks
4932 .first()
4933 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4934 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4935 return Err(format!(
4936 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4937 device {}",
4938 input.len(),
4939 input.ordinal(),
4940 input_values,
4941 root.ctx().ordinal(),
4942 )
4943 .into());
4944 }
4945 let (pairs, next_generation, schedules) =
4946 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4947
4948 plan.ready = false;
4949 plan.executed_generation = None;
4950 {
4951 let _main = root.gpu.enter_main()?;
4952 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4953 root.stream()
4954 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4955 root.stream().synchronize()?;
4956 }
4957 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4958 let root_input = &root_inputs[0];
4959 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4960 let engine = &self.ranks[rank + 1];
4961 let _main = engine.gpu.enter_main()?;
4962 let mut destination = peer_input.slice_mut(0..input_values);
4963 engine
4964 .stream()
4965 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4966 }
4967 self.commit_step_grouped_expert_parallel_refresh(
4968 plan,
4969 tokens,
4970 pairs,
4971 next_generation,
4972 schedules,
4973 )
4974 }
4975
4976 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4981 &self,
4982 experts: &ResidentExpertParallel,
4983 plan: &mut PreparedStepGroupedExpertParallelGate,
4984 input: &ResidentReplicatedDeviceRows,
4985 ) -> Result<(), Box<dyn std::error::Error>> {
4986 validate_ep_residency(&self.ranks, experts)?;
4987 validate_replicated_device_rows(&self.ranks, input)?;
4988 if !plan.ready
4989 || input.tokens != plan.tokens
4990 || input.width != plan.input_width
4991 || input.tokens > plan.max_tokens
4992 || plan.rank_inputs.len() != self.ranks.len()
4993 || plan.owners.len() != self.ranks.len()
4994 || plan.input_width != experts.input_width
4995 || plan.expert_width != experts.expert_width
4996 {
4997 return Err("Step owner-grouped replicated input geometry changed".into());
4998 }
4999 let values = input
5000 .tokens
5001 .checked_mul(input.width)
5002 .ok_or("Step owner-grouped replicated input size overflow")?;
5003 let next_generation = plan
5004 .generation
5005 .checked_add(1)
5006 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
5007 plan.ready = false;
5008 plan.executed_generation = None;
5009 for (rank, engine) in self.ranks.iter().enumerate() {
5010 let _main = engine.gpu.enter_main()?;
5011 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
5012 engine
5013 .stream()
5014 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
5015 }
5016 plan.generation = next_generation;
5017 plan.ready = true;
5018 Ok(())
5019 }
5020
5021 pub fn execute_step_grouped_expert_parallel_gate(
5022 &self,
5023 experts: &ResidentExpertParallel,
5024 plan: &mut PreparedStepGroupedExpertParallelGate,
5025 ) -> Result<(), Box<dyn std::error::Error>> {
5026 validate_ep_residency(&self.ranks, experts)?;
5027 if !plan.ready
5028 || plan.rank_inputs.len() != self.ranks.len()
5029 || plan.owners.len() != self.ranks.len()
5030 || plan.input_width != experts.input_width
5031 || plan.expert_width != experts.expert_width
5032 {
5033 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
5034 }
5035 plan.executed_generation = None;
5036
5037 for owner in &mut plan.owners {
5038 if owner.global_pairs.is_empty() {
5039 continue;
5040 }
5041 let engine = &self.ranks[owner.rank];
5042 let bank = &experts.ranks[owner.rank];
5043 let _main = engine.gpu.enter_main()?;
5044 let local_pairs = owner.global_pairs.len();
5045 owner.gate_workspace.quantize_for_shape(
5046 engine,
5047 &plan.rank_inputs[owner.rank],
5048 plan.tokens,
5049 local_pairs,
5050 )?;
5051 owner.gate_workspace.project(
5052 engine,
5053 &bank.gate.codes,
5054 &bank.gate.scales,
5055 &owner.route_csr,
5056 bank.gate.code_stride,
5057 bank.gate.scale_stride,
5058 1.0,
5059 )?;
5060 owner.up_workspace.quantize_for_shape(
5061 engine,
5062 &plan.rank_inputs[owner.rank],
5063 plan.tokens,
5064 local_pairs,
5065 )?;
5066 owner.up_workspace.project(
5067 engine,
5068 &bank.up.codes,
5069 &bank.up.scales,
5070 &owner.route_csr,
5071 bank.up.code_stride,
5072 bank.up.scale_stride,
5073 1.0,
5074 )?;
5075 }
5076 for owner in &mut plan.owners {
5077 if owner.global_pairs.is_empty() {
5078 continue;
5079 }
5080 let engine = &self.ranks[owner.rank];
5081 let _main = engine.gpu.enter_main()?;
5082 let values = owner.global_pairs.len() * plan.expert_width;
5083 if let Some(limit) = plan.activation_limit {
5084 engine.silu_clamped_mul_host_expf(
5085 owner.gate_workspace.output(),
5086 owner.up_workspace.output(),
5087 limit,
5088 &mut owner.activation,
5089 values,
5090 )?;
5091 } else {
5092 engine.silu_mul_host_expf(
5093 owner.gate_workspace.output(),
5094 owner.up_workspace.output(),
5095 &mut owner.activation,
5096 values,
5097 )?;
5098 }
5099 }
5100 for owner in &mut plan.owners {
5101 if owner.global_pairs.is_empty() {
5102 continue;
5103 }
5104 let engine = &self.ranks[owner.rank];
5105 let bank = &experts.ranks[owner.rank];
5106 let _main = engine.gpu.enter_main()?;
5107 let local_pairs = owner.global_pairs.len();
5108 owner.down_workspace.quantize_for_shape(
5109 engine,
5110 &owner.activation,
5111 local_pairs,
5112 local_pairs,
5113 )?;
5114 owner.down_workspace.project(
5115 engine,
5116 &bank.down.codes,
5117 &bank.down.scales,
5118 &owner.down_csr,
5119 bank.down.code_stride,
5120 bank.down.scale_stride,
5121 1.0,
5122 )?;
5123 }
5124 plan.executed_generation = Some(plan.generation);
5125 Ok(())
5126 }
5127
5128 pub fn collect_step_grouped_expert_parallel_gate(
5129 &self,
5130 plan: &PreparedStepGroupedExpertParallelGate,
5131 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
5132 if !plan.ready || plan.executed_generation != Some(plan.generation) {
5133 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
5134 }
5135 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
5136 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
5137 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
5138 for owner in &plan.owners {
5139 if owner.global_pairs.is_empty() {
5140 continue;
5141 }
5142 let engine = &self.ranks[owner.rank];
5143 let _main = engine.gpu.enter_main()?;
5144 let owner_gate = engine.dtoh_view(
5145 &owner
5146 .gate_workspace
5147 .output()
5148 .slice(0..owner.gate_workspace.output_len()),
5149 )?;
5150 let owner_up = engine.dtoh_view(
5151 &owner
5152 .up_workspace
5153 .output()
5154 .slice(0..owner.up_workspace.output_len()),
5155 )?;
5156 let owner_down = engine.dtoh_view(
5157 &owner
5158 .down_workspace
5159 .output()
5160 .slice(0..owner.down_workspace.output_len()),
5161 )?;
5162 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
5163 let local_expert = local_pair * plan.expert_width;
5164 let global_expert = global_pair * plan.expert_width;
5165 gate[global_expert..global_expert + plan.expert_width]
5166 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
5167 up[global_expert..global_expert + plan.expert_width]
5168 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
5169
5170 let local_hidden = local_pair * plan.input_width;
5171 let global_hidden = global_pair * plan.input_width;
5172 down[global_hidden..global_hidden + plan.input_width]
5173 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
5174 }
5175 }
5176 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
5177 }
5178
5179 pub fn run_step_grouped_expert_parallel_gate(
5180 &self,
5181 experts: &ResidentExpertParallel,
5182 plan: &mut PreparedStepGroupedExpertParallelGate,
5183 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
5184 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
5185 self.collect_step_grouped_expert_parallel_gate(plan)
5186 }
5187
5188 pub fn prepare_step_grouped_expert_parallel_combine(
5189 &self,
5190 plan: &PreparedStepGroupedExpertParallelGate,
5191 route_weights: &[f32],
5192 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
5193 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
5194 return Err(
5195 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
5196 );
5197 }
5198 let owner_pairs = plan
5199 .owners
5200 .iter()
5201 .map(|owner| owner.global_pairs.as_slice())
5202 .collect::<Vec<_>>();
5203 let shape = validate_weighted_route_combine(
5204 plan.input_width,
5205 STEP_GROUPED_FP8_TOP_K,
5206 plan.max_tokens,
5207 plan.tokens,
5208 &owner_pairs,
5209 route_weights,
5210 )?;
5211 if shape.max_pairs != plan.max_pairs {
5212 return Err(format!(
5213 "Step owner-grouped combine capacity {} != projection capacity {}",
5214 shape.max_pairs, plan.max_pairs
5215 )
5216 .into());
5217 }
5218 let root = self
5219 .ranks
5220 .first()
5221 .ok_or("Step owner-grouped combine has no root rank")?;
5222 let slot_values = shape
5223 .max_pairs
5224 .checked_mul(plan.input_width)
5225 .ok_or("Step owner-grouped combine slot capacity overflow")?;
5226 let output_values = plan
5227 .max_tokens
5228 .checked_mul(plan.input_width)
5229 .ok_or("Step owner-grouped combine output capacity overflow")?;
5230 let (root_device, owners, peer_staging, slots, weights, output) = {
5231 let _main = root.gpu.enter_main()?;
5232 let mut owners = Vec::with_capacity(plan.owners.len());
5233 for _ in &plan.owners {
5234 owners.push(PreparedPeerWeightedRouteOwner {
5235 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
5236 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
5237 weights: root.htod(&vec![0.0; shape.max_pairs])?,
5238 active_pairs: 0,
5239 });
5240 }
5241 (
5242 root.ctx().ordinal(),
5243 owners,
5244 root.uninit(slot_values)?,
5245 root.uninit(slot_values)?,
5246 root.uninit(shape.max_pairs)?,
5247 root.uninit(output_values)?,
5248 )
5249 };
5250 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
5251 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
5252 for engine in self.ranks.iter().skip(1) {
5253 let _main = engine.gpu.enter_main()?;
5254 peer_devices.push(engine.ctx().ordinal());
5255 peer_outputs.push(engine.uninit(output_values)?);
5256 }
5257 let mut combine = PreparedPeerWeightedRouteCombine {
5258 root_device,
5259 owners,
5260 peer_staging,
5261 slots,
5262 weights,
5263 output,
5264 peer_devices,
5265 peer_outputs,
5266 width: plan.input_width,
5267 experts_per_token: STEP_GROUPED_FP8_TOP_K,
5268 max_tokens: plan.max_tokens,
5269 max_pairs: shape.max_pairs,
5270 tokens: 0,
5271 pairs: 0,
5272 projection_generation: 0,
5273 output_generation: None,
5274 broadcast_generation: None,
5275 ready: false,
5276 };
5277 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
5278 Ok(combine)
5279 }
5280
5281 pub fn refresh_step_grouped_expert_parallel_combine(
5282 &self,
5283 plan: &PreparedStepGroupedExpertParallelGate,
5284 combine: &mut PreparedPeerWeightedRouteCombine,
5285 route_weights: &[f32],
5286 ) -> Result<(), Box<dyn std::error::Error>> {
5287 let output_capacity = combine
5288 .max_tokens
5289 .checked_mul(combine.width)
5290 .ok_or("Step owner-grouped combine output capacity overflow")?;
5291 if !plan.ready
5292 || combine.owners.len() != plan.owners.len()
5293 || combine.peer_devices.len() + 1 != self.ranks.len()
5294 || combine.peer_outputs.len() + 1 != self.ranks.len()
5295 || combine.width != plan.input_width
5296 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
5297 || combine.max_tokens != plan.max_tokens
5298 || combine.max_pairs != plan.max_pairs
5299 || combine.output.len() < output_capacity
5300 || combine
5301 .peer_outputs
5302 .iter()
5303 .any(|output| output.len() < output_capacity)
5304 {
5305 return Err("Step owner-grouped combine/projection geometry changed".into());
5306 }
5307 if self
5308 .ranks
5309 .iter()
5310 .skip(1)
5311 .zip(&combine.peer_devices)
5312 .any(|(engine, &device)| engine.ctx().ordinal() != device)
5313 {
5314 return Err("Step owner-grouped combine peer devices changed".into());
5315 }
5316 let owner_pairs = plan
5317 .owners
5318 .iter()
5319 .map(|owner| owner.global_pairs.as_slice())
5320 .collect::<Vec<_>>();
5321 let shape = validate_weighted_route_combine(
5322 combine.width,
5323 combine.experts_per_token,
5324 combine.max_tokens,
5325 plan.tokens,
5326 &owner_pairs,
5327 route_weights,
5328 )?;
5329 if shape.max_pairs != combine.max_pairs {
5330 return Err("Step owner-grouped combine capacity changed during refresh".into());
5331 }
5332 let metadata = owner_pairs
5333 .iter()
5334 .map(|pairs| {
5335 let token_rows = pairs
5336 .iter()
5337 .map(|&pair| (pair / combine.experts_per_token) as i32)
5338 .collect::<Vec<_>>();
5339 let slots = pairs
5340 .iter()
5341 .map(|&pair| (pair % combine.experts_per_token) as i32)
5342 .collect::<Vec<_>>();
5343 let weights = pairs
5344 .iter()
5345 .map(|&pair| route_weights[pair])
5346 .collect::<Vec<_>>();
5347 (token_rows, slots, weights)
5348 })
5349 .collect::<Vec<_>>();
5350
5351 combine.ready = false;
5352 combine.output_generation = None;
5353 combine.broadcast_generation = None;
5354 let root = self
5355 .ranks
5356 .first()
5357 .ok_or("Step owner-grouped combine has no root rank")?;
5358 let _main = root.gpu.enter_main()?;
5359 if root.ctx().ordinal() != combine.root_device {
5360 return Err(format!(
5361 "Step owner-grouped combine root device changed {} != {}",
5362 root.ctx().ordinal(),
5363 combine.root_device
5364 )
5365 .into());
5366 }
5367 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
5368 if token_rows.is_empty() {
5369 owner.active_pairs = 0;
5370 continue;
5371 }
5372 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
5373 root.htod_i32_into(&mut owner.slots, &slots)?;
5374 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
5375 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
5376 owner.active_pairs = token_rows.len();
5377 }
5378 combine.tokens = plan.tokens;
5379 combine.pairs = shape.pairs;
5380 combine.projection_generation = plan.generation;
5381 combine.ready = true;
5382 Ok(())
5383 }
5384
5385 pub fn execute_step_grouped_expert_parallel_combine(
5386 &self,
5387 plan: &PreparedStepGroupedExpertParallelGate,
5388 combine: &mut PreparedPeerWeightedRouteCombine,
5389 ) -> Result<(), Box<dyn std::error::Error>> {
5390 if !plan.ready
5391 || plan.executed_generation != Some(plan.generation)
5392 || !combine.ready
5393 || combine.tokens != plan.tokens
5394 || combine.pairs != plan.pairs
5395 || combine.width != plan.input_width
5396 || combine.owners.len() != plan.owners.len()
5397 || combine.projection_generation != plan.generation
5398 {
5399 return Err("Step owner-grouped combine is stale or its geometry changed".into());
5400 }
5401 combine.output_generation = None;
5402 combine.broadcast_generation = None;
5403 for owner in &plan.owners {
5404 if owner.rank == 0 || owner.global_pairs.is_empty() {
5405 continue;
5406 }
5407 let engine = &self.ranks[owner.rank];
5408 let _main = engine.gpu.enter_main()?;
5409 engine.stream().synchronize()?;
5410 }
5411 let root = self
5412 .ranks
5413 .first()
5414 .ok_or("Step owner-grouped combine has no root rank")?;
5415 let _main = root.gpu.enter_main()?;
5416 if root.ctx().ordinal() != combine.root_device {
5417 return Err("Step owner-grouped combine is not resident on the root device".into());
5418 }
5419 for (index, owner) in plan.owners.iter().enumerate() {
5420 let metadata = &combine.owners[index];
5421 if owner.global_pairs.len() != metadata.active_pairs {
5422 return Err(format!(
5423 "Step owner-grouped combine owner {index} rows {} != metadata {}",
5424 owner.global_pairs.len(),
5425 metadata.active_pairs
5426 )
5427 .into());
5428 }
5429 if metadata.active_pairs == 0 {
5430 continue;
5431 }
5432 let values = metadata
5433 .active_pairs
5434 .checked_mul(combine.width)
5435 .ok_or("Step owner-grouped combine peer value count overflow")?;
5436 if owner.rank == 0 {
5437 root.scatter_slot(
5438 owner.down_workspace.output(),
5439 &metadata.token_rows,
5440 &metadata.slots,
5441 &metadata.weights,
5442 &mut combine.slots,
5443 &mut combine.weights,
5444 combine.width,
5445 combine.experts_per_token,
5446 metadata.active_pairs,
5447 )?;
5448 } else {
5449 let source = owner.down_workspace.output().slice(0..values);
5450 let mut destination = combine.peer_staging.slice_mut(0..values);
5451 root.stream().memcpy_dtod(&source, &mut destination)?;
5452 root.scatter_slot(
5453 &combine.peer_staging,
5454 &metadata.token_rows,
5455 &metadata.slots,
5456 &metadata.weights,
5457 &mut combine.slots,
5458 &mut combine.weights,
5459 combine.width,
5460 combine.experts_per_token,
5461 metadata.active_pairs,
5462 )?;
5463 }
5464 }
5465 root.reduce_slots_host(
5466 &combine.slots,
5467 &combine.weights,
5468 &mut combine.output,
5469 combine.width,
5470 combine.experts_per_token,
5471 combine.tokens,
5472 )?;
5473 combine.output_generation = Some(plan.generation);
5474 Ok(())
5475 }
5476
5477 pub fn collect_step_grouped_expert_parallel_combine(
5478 &self,
5479 plan: &PreparedStepGroupedExpertParallelGate,
5480 combine: &PreparedPeerWeightedRouteCombine,
5481 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5482 if !plan.ready
5483 || combine.output_generation != Some(plan.generation)
5484 || combine.projection_generation != plan.generation
5485 {
5486 return Err("Step owner-grouped combine output is stale or has not executed".into());
5487 }
5488 let root = self
5489 .ranks
5490 .first()
5491 .ok_or("Step owner-grouped combine has no root rank")?;
5492 let _main = root.gpu.enter_main()?;
5493 if root.ctx().ordinal() != combine.root_device {
5494 return Err("Step owner-grouped combine is not resident on the root device".into());
5495 }
5496 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
5497 }
5498
5499 pub fn copy_step_grouped_expert_parallel_combine_root(
5504 &self,
5505 plan: &PreparedStepGroupedExpertParallelGate,
5506 combine: &PreparedPeerWeightedRouteCombine,
5507 destination: &Engine,
5508 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5509 if !plan.ready
5510 || combine.output_generation != Some(plan.generation)
5511 || combine.projection_generation != plan.generation
5512 {
5513 return Err("Step owner-grouped combine output is stale or has not executed".into());
5514 }
5515 let root = self
5516 .ranks
5517 .first()
5518 .ok_or("Step owner-grouped combine has no root rank")?;
5519 if root.ctx().ordinal() != combine.root_device
5520 || destination.ctx().ordinal() != combine.root_device
5521 {
5522 return Err(format!(
5523 "Step owner-grouped combine root/destination devices {}/{} != {}",
5524 root.ctx().ordinal(),
5525 destination.ctx().ordinal(),
5526 combine.root_device,
5527 )
5528 .into());
5529 }
5530 let values = combine
5531 .tokens
5532 .checked_mul(combine.width)
5533 .ok_or("Step owner-grouped combine copy size overflow")?;
5534 {
5535 let _main = root.gpu.enter_main()?;
5536 root.stream().synchronize()?;
5537 }
5538 let _main = destination.gpu.enter_main()?;
5539 let mut output = destination.uninit(values)?;
5540 destination
5541 .stream()
5542 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
5543 Ok(output)
5544 }
5545
5546 pub fn broadcast_step_grouped_expert_parallel_combine(
5547 &self,
5548 plan: &PreparedStepGroupedExpertParallelGate,
5549 combine: &mut PreparedPeerWeightedRouteCombine,
5550 ) -> Result<(), Box<dyn std::error::Error>> {
5551 if !plan.ready
5552 || combine.output_generation != Some(plan.generation)
5553 || combine.projection_generation != plan.generation
5554 || combine.peer_devices.len() + 1 != self.ranks.len()
5555 || combine.peer_outputs.len() + 1 != self.ranks.len()
5556 {
5557 return Err("Step owner-grouped combine output cannot be broadcast".into());
5558 }
5559 combine.broadcast_generation = None;
5560 let values = combine
5561 .tokens
5562 .checked_mul(combine.width)
5563 .ok_or("Step owner-grouped combine broadcast size overflow")?;
5564 {
5565 let root = self
5566 .ranks
5567 .first()
5568 .ok_or("Step owner-grouped combine has no root rank")?;
5569 let _main = root.gpu.enter_main()?;
5570 if root.ctx().ordinal() != combine.root_device {
5571 return Err("Step owner-grouped combine root device changed".into());
5572 }
5573 root.stream().synchronize()?;
5574 }
5575 let source = &combine.output;
5576 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
5577 let engine = &self.ranks[index + 1];
5578 let _main = engine.gpu.enter_main()?;
5579 if engine.ctx().ordinal() != combine.peer_devices[index] {
5580 return Err(format!(
5581 "Step owner-grouped combine peer {} device changed",
5582 index + 1
5583 )
5584 .into());
5585 }
5586 let mut destination = destination_buffer.slice_mut(0..values);
5587 engine
5588 .stream()
5589 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
5590 }
5591 combine.broadcast_generation = Some(plan.generation);
5592 Ok(())
5593 }
5594
5595 pub fn collect_step_grouped_expert_parallel_broadcast(
5596 &self,
5597 plan: &PreparedStepGroupedExpertParallelGate,
5598 combine: &PreparedPeerWeightedRouteCombine,
5599 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
5600 if !plan.ready
5601 || combine.output_generation != Some(plan.generation)
5602 || combine.broadcast_generation != Some(plan.generation)
5603 || combine.peer_outputs.len() + 1 != self.ranks.len()
5604 {
5605 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
5606 }
5607 let values = combine
5608 .tokens
5609 .checked_mul(combine.width)
5610 .ok_or("Step owner-grouped combine collection size overflow")?;
5611 let mut outputs = Vec::with_capacity(self.ranks.len());
5612 {
5613 let root = &self.ranks[0];
5614 let _main = root.gpu.enter_main()?;
5615 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
5616 }
5617 for (index, output) in combine.peer_outputs.iter().enumerate() {
5618 let engine = &self.ranks[index + 1];
5619 let _main = engine.gpu.enter_main()?;
5620 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
5621 }
5622 Ok(outputs)
5623 }
5624
5625 pub fn finish_step_grouped_expert_parallel_layer(
5627 &self,
5628 plan: &PreparedStepGroupedExpertParallelGate,
5629 combine: &PreparedPeerWeightedRouteCombine,
5630 shared: &ResidentReplicatedDeviceRows,
5631 residual: &ResidentReplicatedDeviceRows,
5632 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
5633 validate_replicated_device_rows(&self.ranks, shared)?;
5634 validate_replicated_device_rows(&self.ranks, residual)?;
5635 if !plan.ready
5636 || plan.executed_generation != Some(plan.generation)
5637 || combine.output_generation != Some(plan.generation)
5638 || combine.broadcast_generation != Some(plan.generation)
5639 || combine.projection_generation != plan.generation
5640 || combine.peer_outputs.len() + 1 != self.ranks.len()
5641 || shared.tokens != combine.tokens
5642 || residual.tokens != combine.tokens
5643 || shared.width != combine.width
5644 || residual.width != combine.width
5645 {
5646 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
5647 }
5648 let values = combine
5649 .tokens
5650 .checked_mul(combine.width)
5651 .ok_or("Step full-layer output size overflow")?;
5652 let mut ranks = Vec::with_capacity(self.ranks.len());
5653 for rank in 0..self.ranks.len() {
5654 let engine = &self.ranks[rank];
5655 let _main = engine.gpu.enter_main()?;
5656 let routed = if rank == 0 {
5657 &combine.output
5658 } else {
5659 &combine.peer_outputs[rank - 1]
5660 };
5661 let mut ffn = engine.uninit(values)?;
5662 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
5663 let mut output = engine.uninit(values)?;
5664 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5665 ranks.push(output);
5666 }
5667 Ok(ResidentReplicatedDeviceRows {
5668 ranks,
5669 tokens: combine.tokens,
5670 width: combine.width,
5671 })
5672 }
5673
5674 pub fn run_step_grouped_expert_parallel_combine(
5675 &self,
5676 plan: &PreparedStepGroupedExpertParallelGate,
5677 combine: &mut PreparedPeerWeightedRouteCombine,
5678 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5679 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5680 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5681 }
5682
5683 pub fn upload_tensor_parallel(
5684 &self,
5685 gate: E4m3ExpertBank<'_>,
5686 up: E4m3ExpertBank<'_>,
5687 down: E4m3ExpertBank<'_>,
5688 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5689 gate.validate()?;
5690 up.validate()?;
5691 down.validate()?;
5692 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5693 return Err("TP gate/up/down expert counts differ".into());
5694 }
5695 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5696 return Err("TP gate/up dimensions differ".into());
5697 }
5698 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5699 return Err(format!(
5700 "TP down {}x{} does not invert gate/up {}x{}",
5701 down.out_features, down.in_features, gate.out_features, gate.in_features
5702 )
5703 .into());
5704 }
5705 let tp = self.ranks.len();
5706 validate_column_bank_shape(gate, tp)?;
5707 validate_column_bank_shape(up, tp)?;
5708 validate_row_bank_shape(down, tp)?;
5709
5710 let mut gate_ranks = Vec::with_capacity(tp);
5711 let mut up_ranks = Vec::with_capacity(tp);
5712 let mut down_ranks = Vec::with_capacity(tp);
5713 for (rank, engine) in self.ranks.iter().enumerate() {
5714 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5715 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5716 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5717 }
5718 Ok(ResidentTensorParallel {
5719 bank: ResidentTpExpertBank {
5720 gate: gate_ranks,
5721 up: up_ranks,
5722 down: down_ranks,
5723 expert_count: gate.expert_count,
5724 input_width: gate.in_features,
5725 expert_width: gate.out_features,
5726 },
5727 })
5728 }
5729
5730 pub fn run_tensor_parallel_routes(
5731 &self,
5732 experts: &ResidentTensorParallel,
5733 input: &[f32],
5734 tokens: usize,
5735 selected: &[usize],
5736 route_weights: &[f32],
5737 experts_per_token: usize,
5738 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5739 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5740 validate_activations(input, tokens, experts.bank.input_width)?;
5741 let pairs = tokens
5742 .checked_mul(experts_per_token)
5743 .ok_or("TP route count overflow")?;
5744 if selected.len() != pairs || route_weights.len() != pairs {
5745 return Err(format!(
5746 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5747 {experts_per_token} ({pairs})",
5748 selected.len(),
5749 route_weights.len(),
5750 )
5751 .into());
5752 }
5753 if !route_weights.iter().all(|weight| weight.is_finite()) {
5754 return Err("TP route weights contain a non-finite value".into());
5755 }
5756
5757 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5758 for token in 0..tokens {
5759 let input_row =
5760 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5761 for slot in 0..experts_per_token {
5762 let pair = token * experts_per_token + slot;
5763 let expert = selected[pair];
5764 if expert >= experts.bank.expert_count {
5765 return Err(format!(
5766 "TP selected expert {expert} outside 0..{}",
5767 experts.bank.expert_count
5768 )
5769 .into());
5770 }
5771 let down = if self.native_p2p {
5772 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5773 } else {
5774 let gate =
5775 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5776 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5777 let activated: Vec<f32> = gate
5778 .iter()
5779 .zip(&up)
5780 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5781 .collect();
5782 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5783 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5784 };
5785 let weight = route_weights[pair];
5786 for (sum, value) in output
5787 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5788 .iter_mut()
5789 .zip(down)
5790 {
5791 *sum += weight * value;
5792 }
5793 }
5794 }
5795 Ok(output)
5796 }
5797
5798 fn run_column_bank_expert(
5799 &self,
5800 ranks: &[ResidentE4m3ExpertBankRank],
5801 expert: usize,
5802 input: &[f32],
5803 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5804 let local_out = ranks
5805 .first()
5806 .ok_or("TP column bank has no ranks")?
5807 .out_features;
5808 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5809 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5810 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5811 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5812 }
5813 Ok(gathered)
5814 }
5815
5816 fn run_row_bank_expert(
5817 &self,
5818 ranks: &[ResidentE4m3ExpertBankRank],
5819 expert: usize,
5820 input: &[f32],
5821 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5822 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5823 if input.len() != local_in * ranks.len() {
5824 return Err(format!(
5825 "TP row input {} != {} ranks x {local_in}",
5826 input.len(),
5827 ranks.len()
5828 )
5829 .into());
5830 }
5831 let out_features = ranks[0].out_features;
5832 let mut reduced = vec![0.0f32; out_features];
5833 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5834 let blocks = bank
5835 .k_blocks
5836 .ok_or("TP row bank is not packed in native K-block order")?;
5837 if blocks * FP8_BLOCK != local_in {
5838 return Err(format!(
5839 "TP row bank has {blocks} blocks but local input width is {local_in}"
5840 )
5841 .into());
5842 }
5843 for block in 0..blocks {
5844 let global_start = rank * local_in + block * FP8_BLOCK;
5845 let partial = run_resident_bank_expert_block(
5846 engine,
5847 bank,
5848 expert,
5849 block,
5850 &input[global_start..global_start + FP8_BLOCK],
5851 )?;
5852 for (sum, value) in reduced.iter_mut().zip(partial) {
5853 *sum += value;
5854 }
5855 }
5856 }
5857 Ok(reduced)
5858 }
5859
5860 fn run_tensor_parallel_expert_native(
5861 &self,
5862 bank: &ResidentTpExpertBank,
5863 expert: usize,
5864 input: &[f32],
5865 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5866 if !self.native_p2p || self.ranks.len() < 2 {
5867 return Err("native TP expert execution requires at least two P2P ranks".into());
5868 }
5869 let local_out = bank
5870 .gate
5871 .first()
5872 .ok_or("native TP gate bank has no ranks")?
5873 .out_features;
5874 if local_out * self.ranks.len() != bank.expert_width {
5875 return Err(format!(
5876 "native TP gate shards {}x{local_out} != expert width {}",
5877 self.ranks.len(),
5878 bank.expert_width
5879 )
5880 .into());
5881 }
5882
5883 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5886 let root_input = {
5887 let root = &self.ranks[0];
5888 let _main = root.gpu.enter_main()?;
5889 root.htod(input)?
5890 };
5891 rank_inputs.push(root_input);
5892 for engine in &self.ranks[1..] {
5893 let peer_input = {
5894 let _main = engine.gpu.enter_main()?;
5895 let mut peer_input = engine.uninit(input.len())?;
5896 engine
5897 .stream()
5898 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5899 peer_input
5900 };
5901 rank_inputs.push(peer_input);
5902 }
5903
5904 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5905 let mut up_shards = Vec::with_capacity(self.ranks.len());
5906 #[allow(clippy::needless_range_loop)]
5907 for rank in 0..self.ranks.len() {
5909 gate_shards.push(run_resident_bank_expert_device(
5910 &self.ranks[rank],
5911 &bank.gate[rank],
5912 expert,
5913 &rank_inputs[rank],
5914 1,
5915 )?);
5916 up_shards.push(run_resident_bank_expert_device(
5917 &self.ranks[rank],
5918 &bank.up[rank],
5919 expert,
5920 &rank_inputs[rank],
5921 1,
5922 )?);
5923 }
5924
5925 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5929 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5930 let activated = gate
5931 .iter()
5932 .zip(&up)
5933 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5934 .collect::<Vec<_>>();
5935 debug_assert_eq!(activated.len(), bank.expert_width);
5936
5937 let root_activated = {
5938 let root = &self.ranks[0];
5939 let _main = root.gpu.enter_main()?;
5940 root.htod(&activated)?
5941 };
5942 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5943 for (rank, engine) in self.ranks.iter().enumerate() {
5944 let start = rank * local_out;
5945 let source = root_activated.slice(start..start + local_out);
5946 let local = {
5947 let _main = engine.gpu.enter_main()?;
5948 let mut local = engine.uninit(local_out)?;
5949 engine.stream().memcpy_dtod(&source, &mut local)?;
5950 local
5951 };
5952 rank_activated.push(local);
5953 }
5954
5955 let out_features = bank
5956 .down
5957 .first()
5958 .ok_or("native TP down bank has no ranks")?
5959 .out_features;
5960 let mut reduced = {
5961 let root = &self.ranks[0];
5962 let _main = root.gpu.enter_main()?;
5963 root.htod(&vec![0.0f32; out_features])?
5964 };
5965 let mut remote_partial_keepalive = Vec::new();
5966 #[allow(clippy::needless_range_loop)]
5967 for rank in 0..self.ranks.len() {
5969 let down = &bank.down[rank];
5970 let blocks = down
5971 .k_blocks
5972 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5973 if blocks * FP8_BLOCK != local_out {
5974 return Err(format!(
5975 "native TP rank {rank} has {blocks} blocks but local activation width is \
5976 {local_out}"
5977 )
5978 .into());
5979 }
5980 for block in 0..blocks {
5981 let start = block * FP8_BLOCK;
5982 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5983 let partial = run_resident_bank_expert_block_device(
5984 &self.ranks[rank],
5985 down,
5986 expert,
5987 block,
5988 &input_block,
5989 )?;
5990 let root_partial = if rank == 0 {
5991 partial
5992 } else {
5993 let root = &self.ranks[0];
5994 let _main = root.gpu.enter_main()?;
5995 let mut peer_partial = root.uninit(out_features)?;
5996 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5997 remote_partial_keepalive.push(partial);
5998 peer_partial
5999 };
6000 let next = {
6001 let root = &self.ranks[0];
6002 let _main = root.gpu.enter_main()?;
6003 let mut next = root.uninit(out_features)?;
6004 root.add(&reduced, &root_partial, &mut next, out_features)?;
6005 next
6006 };
6007 reduced = next;
6008 }
6009 }
6010 let output = {
6011 let root = &self.ranks[0];
6012 let _main = root.gpu.enter_main()?;
6013 root.dtoh(&reduced)?
6014 };
6015 drop(remote_partial_keepalive);
6016 Ok(output)
6017 }
6018
6019 pub fn gather_native_column_shards_device(
6021 &self,
6022 shards: &[CudaSlice<f32>],
6023 tokens: usize,
6024 local_out: usize,
6025 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6026 let shard_len = tokens
6027 .checked_mul(local_out)
6028 .ok_or("native TP gather shard size overflow")?;
6029 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
6030 return Err("native TP gather shard geometry mismatch".into());
6031 }
6032 for engine in &self.ranks[1..] {
6036 let _main = engine.gpu.enter_main()?;
6037 engine.stream().synchronize()?;
6038 }
6039 let root = &self.ranks[0];
6040 let _main = root.gpu.enter_main()?;
6041 let global_out = shards
6042 .len()
6043 .checked_mul(local_out)
6044 .ok_or("native TP gather output width overflow")?;
6045 let gathered_len = tokens
6046 .checked_mul(global_out)
6047 .ok_or("native TP gather output size overflow")?;
6048 let mut gathered = root.uninit(gathered_len)?;
6049 if self.bulk_p2p {
6050 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
6051 if shards.len() > 1 {
6052 let mut staging = root.uninit(shard_len)?;
6053 for (rank, shard) in shards.iter().enumerate().skip(1) {
6054 root.stream().memcpy_dtod(shard, &mut staging)?;
6055 root.place_rows_strided(
6056 &staging,
6057 &mut gathered,
6058 local_out,
6059 tokens,
6060 global_out,
6061 rank * local_out,
6062 )?;
6063 }
6064 }
6065 } else {
6066 for token in 0..tokens {
6067 for (rank, shard) in shards.iter().enumerate() {
6068 let source = shard.slice(token * local_out..(token + 1) * local_out);
6069 let start = token * global_out + rank * local_out;
6070 let mut destination = gathered.slice_mut(start..start + local_out);
6071 root.stream().memcpy_dtod(&source, &mut destination)?;
6072 }
6073 }
6074 }
6075 Ok(gathered)
6076 }
6077
6078 pub fn gather_native_column_shards(
6079 &self,
6080 shards: &[CudaSlice<f32>],
6081 tokens: usize,
6082 local_out: usize,
6083 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6084 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
6085 let root = &self.ranks[0];
6086 let _main = root.gpu.enter_main()?;
6087 root.dtoh(&gathered)
6088 }
6089
6090 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
6091 &self.decode_v2
6092 }
6093
6094 #[allow(clippy::manual_is_multiple_of)] pub(crate) fn decode_v2_ensure(
6104 &self,
6105 e: &Engine,
6106 q_m: &ResidentBf16ColumnParallel,
6107 k_m: &ResidentBf16ColumnParallel,
6108 v_m: &ResidentBf16ColumnParallel,
6109 o_m: &ResidentStepBf16RowParallel,
6110 heads: usize,
6111 ) -> Result<usize, Box<dyn std::error::Error>> {
6112 if self.ranks.len() > 1 && !self.native_p2p {
6113 return Err("step TP decode v2 requires native P2P ranks".into());
6114 }
6115 let ranks = self.ranks.len();
6116 let fused_door = step_tp_qkv_fused_enabled()?;
6120 let arm_ok = |weight: &ResidentBf16Weight| match weight {
6121 ResidentBf16Weight::F32(_) => true,
6122 ResidentBf16Weight::Bf16(_) => fused_door,
6123 };
6124 for matrix in [q_m, k_m, v_m] {
6125 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
6126 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
6127 return Err("step TP decode v2 QKV geometry mismatch".into());
6128 }
6129 for rank in &matrix.ranks {
6130 if !arm_ok(&rank.weight) {
6131 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
6132 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
6133 .into());
6134 }
6135 }
6136 }
6137 validate_step_bf16_row_residency(&self.ranks, o_m)?;
6138 for blocks in &o_m.ranks {
6139 for block in blocks {
6140 if !arm_ok(&block.weight) {
6141 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
6142 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
6143 .into());
6144 }
6145 }
6146 }
6147 if v_m.out_features != k_m.out_features
6148 || o_m.in_features != q_m.out_features
6149 || heads == 0
6150 || heads % ranks != 0
6151 {
6152 return Err("step TP decode v2 K/V/O geometry mismatch".into());
6153 }
6154 let local_q_dim = q_m.out_features / ranks;
6155 let local_kv_dim = k_m.out_features / ranks;
6156 let o_out = o_m.out_features;
6157 let o_block_cols = o_m.canonical_chunk_cols;
6158 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
6159 if blocks_per_rank == 0
6160 || o_m
6161 .ranks
6162 .iter()
6163 .any(|blocks| blocks.len() != blocks_per_rank)
6164 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
6165 {
6166 return Err("step TP decode v2 O canonical block grid mismatch".into());
6167 }
6168
6169 let mut guard = self
6170 .decode_v2
6171 .lock()
6172 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6173 if let Some(index) = guard.iter().position(|ws| {
6174 ws.local_q_dim == local_q_dim
6175 && ws.local_kv_dim == local_kv_dim
6176 && ws.heads == heads
6177 && ws.o_out == o_out
6178 && ws.o_block_cols == o_block_cols
6179 && ws.blocks_per_rank == blocks_per_rank
6180 && ws.e_device == e.ctx().ordinal()
6181 && ws.q.len() == ranks
6182 }) {
6183 return Ok(index);
6184 }
6185
6186 let mut q_raw = Vec::with_capacity(ranks);
6187 let mut k_raw = Vec::with_capacity(ranks);
6188 let mut v_raw = Vec::with_capacity(ranks);
6189 let mut q = Vec::with_capacity(ranks);
6190 let mut k = Vec::with_capacity(ranks);
6191 let mut pos = Vec::with_capacity(ranks);
6192 let mut gate = Vec::with_capacity(ranks);
6193 let mut attn_out = Vec::with_capacity(ranks);
6194 let mut gated = Vec::with_capacity(ranks);
6195 let mut fuse_ctr = Vec::with_capacity(ranks);
6196 let mut o_partials = Vec::with_capacity(ranks);
6197 let mut ev_rank = Vec::with_capacity(ranks);
6198 let direct_join = oproj_direct_on();
6199 for (rank, engine) in self.ranks.iter().enumerate() {
6200 let _main = engine.gpu.enter_main()?;
6201 q_raw.push(engine.uninit(local_q_dim)?);
6202 k_raw.push(engine.uninit(local_kv_dim)?);
6203 v_raw.push(engine.uninit(local_kv_dim)?);
6204 q.push(engine.uninit(local_q_dim)?);
6205 k.push(engine.uninit(local_kv_dim)?);
6206 pos.push(engine.htod_i32(&[0])?);
6207 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
6208 gate.push(engine.uninit(heads / ranks)?);
6209 attn_out.push(engine.uninit(local_q_dim)?);
6210 gated.push(engine.uninit(local_q_dim)?);
6211 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
6212 for _ in 0..blocks_per_rank {
6213 if direct_join && rank != 0 {
6216 let root = &self.ranks[0];
6217 let _root_main = root.gpu.enter_main()?;
6218 rank_partials.push(root.uninit(o_out)?);
6219 } else {
6220 rank_partials.push(engine.uninit(o_out)?);
6221 }
6222 }
6223 o_partials.push(rank_partials);
6224 ev_rank.push(engine.ctx().new_event(None)?);
6225 }
6226 use cudarc::driver::DevicePtr;
6227 let mut raw_o_partials = Vec::with_capacity(ranks);
6228 let mut raw_k = Vec::with_capacity(ranks);
6229 let mut raw_v_raw = Vec::with_capacity(ranks);
6230 for rank in 0..ranks {
6231 let engine = &self.ranks[rank];
6232 {
6233 let _main = engine.gpu.enter_main()?;
6234 let stream = engine.stream();
6235 let (k_ptr, _k_guard) = k[rank].device_ptr(&stream);
6236 let (v_ptr, _v_guard) = v_raw[rank].device_ptr(&stream);
6237 raw_k.push(k_ptr);
6238 raw_v_raw.push(v_ptr);
6239 }
6240 let partial_engine = if direct_join && rank != 0 {
6241 &self.ranks[0]
6242 } else {
6243 engine
6244 };
6245 let _main = partial_engine.gpu.enter_main()?;
6246 let stream = partial_engine.stream();
6247 let mut rank_raw = Vec::with_capacity(blocks_per_rank);
6248 for partial in &o_partials[rank] {
6249 let (ptr, _guard) = partial.device_ptr(&stream);
6250 rank_raw.push(ptr);
6251 }
6252 raw_o_partials.push(rank_raw);
6253 }
6254 let root = &self.ranks[0];
6255 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
6256 let _main = root.gpu.enter_main()?;
6257 (
6258 root.uninit(o_out)?,
6259 root.uninit(o_out)?,
6260 root.uninit(o_out)?,
6261 root.htod(&vec![0.0f32; o_out])?,
6262 root.uninit(ranks * local_kv_dim)?,
6263 root.uninit(ranks * local_kv_dim)?,
6264 root.ctx().new_event(None)?,
6265 root.ctx().new_event(None)?,
6266 )
6267 };
6268 let (raw_peer_partial, raw_k_shadow, raw_v_shadow) = {
6269 let _main = root.gpu.enter_main()?;
6270 let stream = root.stream();
6271 let (peer, _peer_guard) = peer_partial.device_ptr(&stream);
6272 let (k, _k_guard) = k_shadow.device_ptr(&stream);
6273 let (v, _v_guard) = v_shadow.device_ptr(&stream);
6274 (peer, k, v)
6275 };
6276 let (gate_e, ev_entry) = {
6277 let _main = e.gpu.enter_main()?;
6278 (e.uninit(heads)?, e.ctx().new_event(None)?)
6279 };
6280 let raw_attn_in = Vec::new();
6281 let raw_pos = Vec::new();
6282 guard.push(StepTpDecodeV2Ws {
6283 tcol_q: Vec::new(),
6284 tcol_k: Vec::new(),
6285 tcol_v: Vec::new(),
6286 tcol_g: Vec::new(),
6287 tcol_in: Vec::new(),
6288 tcol_cap: 0,
6289 w8_aq: Vec::new(),
6290 w8_ad: Vec::new(),
6291 w8_in: 0,
6292 w8o_aq: Vec::new(),
6293 w8o_ad: Vec::new(),
6294 w8o_in: 0,
6295 w8t_aq: Vec::new(),
6296 w8t_ad: Vec::new(),
6297 w8t_in: 0,
6298 w8t_oaq: Vec::new(),
6299 w8t_oad: Vec::new(),
6300 w8t_oin: 0,
6301 w8t_cap: 0,
6302 fa2_q: Vec::new(),
6303 fa2_gate: Vec::new(),
6304 fa2_gated: Vec::new(),
6305 fa2_cap: 0,
6306 rope_k_t: Vec::new(),
6307 rope_ctr_t: Vec::new(),
6308 rope_pos_t: Vec::new(),
6309 rows_tabs: Vec::new(),
6310 rows_tab_t: Vec::new(),
6311 rows_tab_shadow: Vec::new(),
6312 tcol_gated: Vec::new(),
6313 tcol_opart: Vec::new(),
6314 tcol_opeer: None,
6315 tcol_omix: None,
6316 tcol_ocap: 0,
6317 q_raw,
6318 k_raw,
6319 v_raw,
6320 q,
6321 k,
6322 pos,
6323 fuse_ctr,
6324 gate,
6325 attn_out,
6326 gated,
6327 o_partials,
6328 raw_o_partials,
6329 raw_k,
6330 raw_v_raw,
6331 ev_rank,
6332 peer_partial,
6333 reduce_a,
6334 reduce_b,
6335 zeros,
6336 k_shadow,
6337 v_shadow,
6338 ev_refresh,
6339 ev_oproj,
6340 gate_e,
6341 attn_in: Vec::new(),
6342 h_stage: None,
6343 pos_stage: None,
6344 raw_h_stage: 0,
6345 raw_pos_stage: 0,
6346 raw_attn_in,
6347 raw_pos,
6348 raw_o_partial1: 0,
6349 raw_peer_partial,
6350 raw_k1: 0,
6351 raw_v1: 0,
6352 raw_k_shadow,
6353 raw_v_shadow,
6354 raw_mixed_stage_e: 0,
6355 raw_reduce_a: 0,
6356 raw_shadow_stage_e: (0, 0),
6357 ev_entry,
6358 e_device: e.ctx().ordinal(),
6359 local_q_dim,
6360 local_kv_dim,
6361 heads,
6362 o_out,
6363 o_block_cols,
6364 blocks_per_rank,
6365 });
6366 eprintln!(
6367 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
6368 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
6369 residency=persistent ordering=evented performance_claim=false"
6370 );
6371 Ok(guard.len() - 1)
6372 }
6373
6374 #[allow(clippy::too_many_arguments)]
6382 #[allow(clippy::too_many_arguments)]
6387 pub fn decode_v2_input_qkv_tcol(
6388 &self,
6389 ws_index: usize,
6390 e: &Engine,
6391 h_t: &CudaSlice<f32>,
6392 t: usize,
6393 q_m: &ResidentBf16ColumnParallel,
6394 k_m: &ResidentBf16ColumnParallel,
6395 v_m: &ResidentBf16ColumnParallel,
6396 gate_shards: Option<StepTpGateShards<'_>>,
6397 ) -> Result<(), Box<dyn std::error::Error>> {
6398 let ranks = self.ranks.len();
6399 let mut guard = self
6400 .decode_v2
6401 .lock()
6402 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6403 let ws = guard
6404 .get_mut(ws_index)
6405 .ok_or("step TP decode v2 workspace index out of range")?;
6406 let in_f = q_m.in_features;
6407 if h_t.len() < t * in_f || t == 0 || t > 32 {
6408 return Err("decode_v2_input_qkv_tcol geometry".into());
6409 }
6410 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6412 ws.tcol_q.clear();
6413 ws.tcol_k.clear();
6414 ws.tcol_v.clear();
6415 ws.tcol_g.clear();
6416 ws.tcol_in.clear();
6417 for engine in &self.ranks {
6418 let _m = engine.gpu.enter_main()?;
6419 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
6420 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
6421 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
6422 ws.tcol_g
6423 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
6424 ws.tcol_in.push(engine.uninit(32 * in_f)?);
6425 }
6426 ws.tcol_cap = 32;
6427 }
6428 use cudarc::driver::DevicePtr;
6430 let raw_src = {
6431 let _main = e.gpu.enter_main()?;
6432 let stream = e.stream();
6433 let (p, _g) = h_t.device_ptr(&stream);
6434 ws.ev_entry.record(&stream)?;
6435 p
6436 };
6437 for rank in 0..ranks {
6438 let engine = &self.ranks[rank];
6439 let _main = engine.gpu.enter_main()?;
6440 engine.stream().wait(&ws.ev_entry)?;
6441 let raw_dst = {
6442 let stream = engine.stream();
6443 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
6444 p
6445 };
6446 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
6447 let out_g = match &gate_shards {
6448 Some(_) => ws.heads / ranks,
6449 None => 0,
6450 };
6451 match (
6452 &q_m.ranks[rank].weight,
6453 &k_m.ranks[rank].weight,
6454 &v_m.ranks[rank].weight,
6455 ) {
6456 (
6457 ResidentBf16Weight::Bf16(wq),
6458 ResidentBf16Weight::Bf16(wk),
6459 ResidentBf16Weight::Bf16(wv),
6460 ) => {
6461 let wg = match &gate_shards {
6462 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
6463 Some(StepTpGateShards::F32(_)) => {
6464 return Err(
6465 "tcol verify: gate shard class does not match bf16 QKV".into()
6466 );
6467 }
6468 None => wq,
6469 };
6470 let StepTpDecodeV2Ws {
6471 tcol_q,
6472 tcol_k,
6473 tcol_v,
6474 tcol_g,
6475 tcol_in,
6476 local_q_dim,
6477 local_kv_dim,
6478 w8t_aq,
6479 w8t_ad,
6480 w8t_in,
6481 w8t_cap,
6482 ..
6483 } = &mut *ws;
6484 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6487 let refk = *REFK
6488 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
6489 if refk {
6490 let lq = *local_q_dim;
6491 let lkv = *local_kv_dim;
6492 let mut hrow = engine.uninit(in_f)?;
6493 let mut qr = engine.uninit(lq)?;
6494 let mut kr = engine.uninit(lkv)?;
6495 let mut vr = engine.uninit(lkv)?;
6496 let mut gr = engine.uninit(out_g.max(1))?;
6497 for c in 0..t {
6498 {
6499 let mut dst = hrow.slice_mut(0..in_f);
6500 engine.stream().memcpy_dtod(
6501 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
6502 &mut dst,
6503 )?;
6504 }
6505 engine.matvec_bf16_qkvg_into(
6506 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
6507 lq, lkv, out_g,
6508 )?;
6509 let stream = engine.stream();
6510 {
6511 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
6512 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
6513 }
6514 {
6515 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
6516 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
6517 }
6518 {
6519 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
6520 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
6521 }
6522 if out_g > 0 {
6523 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
6524 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
6525 }
6526 }
6527 } else if crate::step_tp_w8_on()
6528 && q_m.ranks[rank].q8.is_some()
6529 && k_m.ranks[rank].q8.is_some()
6530 && v_m.ranks[rank].q8.is_some()
6531 && in_f.is_multiple_of(32)
6532 {
6533 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
6539 w8t_aq.clear();
6540 w8t_ad.clear();
6541 for e_rank in &self.ranks {
6542 let _m = e_rank.gpu.enter_main()?;
6543 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6544 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6545 }
6546 *w8t_in = in_f;
6547 *w8t_cap = 32;
6548 }
6549 engine.quantize_q8_1_into(
6550 &tcol_in[rank],
6551 t,
6552 in_f,
6553 &mut w8t_aq[rank],
6554 &mut w8t_ad[rank],
6555 )?;
6556 engine.qmatvec_q8_0_qkv_rp_t_into(
6557 q_m.ranks[rank].q8.as_ref().unwrap(),
6558 k_m.ranks[rank].q8.as_ref().unwrap(),
6559 v_m.ranks[rank].q8.as_ref().unwrap(),
6560 &w8t_aq[rank],
6561 &w8t_ad[rank],
6562 &mut tcol_q[rank],
6563 &mut tcol_k[rank],
6564 &mut tcol_v[rank],
6565 in_f,
6566 *local_q_dim,
6567 *local_kv_dim,
6568 t,
6569 )?;
6570 if out_g > 0 {
6571 engine.matvec_bf16_rows_into(
6572 wg,
6573 &tcol_in[rank],
6574 &mut tcol_g[rank],
6575 in_f,
6576 out_g,
6577 t,
6578 )?;
6579 }
6580 } else {
6581 engine.matvec_bf16_qkvg_tcol_into(
6582 wq,
6583 wk,
6584 wv,
6585 wg,
6586 &tcol_in[rank],
6587 &mut tcol_q[rank],
6588 &mut tcol_k[rank],
6589 &mut tcol_v[rank],
6590 &mut tcol_g[rank],
6591 in_f,
6592 *local_q_dim,
6593 *local_kv_dim,
6594 out_g,
6595 t,
6596 )?;
6597 }
6598 }
6599 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
6600 }
6601 }
6602 Ok(())
6603 }
6604
6605 pub(crate) fn decode_v2_oproj_tcol_eligible(
6609 &self,
6610 ws: &StepTpDecodeV2Ws,
6611 o_m: &ResidentStepBf16RowParallel,
6612 ) -> bool {
6613 self.ranks.len() == 2
6614 && ws.blocks_per_rank == 4
6615 && step_tp_qkv_fused_enabled().unwrap_or(false)
6616 && no_local_shadow_on()
6617 && o_m
6618 .ranks
6619 .iter()
6620 .flatten()
6621 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
6622 }
6623
6624 pub(crate) fn decode_v2_stash_fa2(
6629 &self,
6630 ws: &mut StepTpDecodeV2Ws,
6631 e: &Engine,
6632 col: usize,
6633 ) -> Result<(), Box<dyn std::error::Error>> {
6634 let ranks = self.ranks.len();
6635 if col >= 32 {
6636 return Err("decode_v2_stash_fa2 column out of range".into());
6637 }
6638 let lq = ws.local_q_dim;
6639 let lg = (ws.heads / ranks).max(1);
6640 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6641 ws.fa2_q.clear();
6642 ws.fa2_gate.clear();
6643 ws.fa2_gated.clear();
6644 ws.rope_k_t.clear();
6645 ws.rope_ctr_t.clear();
6646 ws.rope_pos_t.clear();
6647 ws.rows_tab_t.clear();
6648 for engine in &self.ranks {
6649 let _m = engine.gpu.enter_main()?;
6650 ws.fa2_q.push(engine.uninit(32 * lq)?);
6651 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6652 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6653 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
6654 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6655 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6656 ws.rows_tab_t
6657 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6658 }
6659 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6660 ws.fa2_cap = 32;
6661 }
6662 for rank in 0..ranks {
6663 let engine = &self.ranks[rank];
6664 let _main = engine.gpu.enter_main()?;
6665 {
6666 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
6667 engine
6668 .stream()
6669 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
6670 }
6671 {
6672 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
6673 engine
6674 .stream()
6675 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
6676 }
6677 ws.ev_rank[rank].record(&engine.stream())?;
6678 }
6679 {
6680 let _main = e.gpu.enter_main()?;
6681 for ev in ws.ev_rank.iter() {
6682 e.stream().wait(ev)?;
6683 }
6684 }
6685 Ok(())
6686 }
6687
6688 #[allow(clippy::too_many_arguments)]
6698 pub(crate) fn decode_v2_rope_fa_rows(
6699 &self,
6700 ws_index: usize,
6701 e: &Engine,
6702 o_m: &ResidentStepBf16RowParallel,
6703 session_parts: &[Vec<[u64; 4]>],
6704 tab_keys: &[u64],
6705 positions: &[i32],
6706 stage_pos: bool,
6707 same_session: bool,
6708 q_norms: &[CudaSlice<f32>],
6709 k_norms: &[CudaSlice<f32>],
6710 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6711 t: usize,
6712 head_dim: usize,
6713 n_rot: usize,
6714 window: usize,
6715 max_ns: usize,
6716 scale: f32,
6717 k_tok_bytes: usize,
6718 v_tok_bytes: usize,
6719 eps: f32,
6720 rope_base: f32,
6721 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6722 use cudarc::driver::DevicePtr;
6723 let ranks = self.ranks.len();
6724 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6725 return Err("rope fa rows geometry".into());
6726 }
6727 {
6728 let mut guard = self
6729 .decode_v2
6730 .lock()
6731 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6732 let ws = guard
6733 .get_mut(ws_index)
6734 .ok_or("step TP decode v2 workspace index out of range")?;
6735 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6736 return Err("rope fa rows without tcol slabs".into());
6737 }
6738 let lq = ws.local_q_dim;
6739 let lkv = ws.local_kv_dim;
6740 let lg = (ws.heads / ranks).max(1);
6741 let local_heads = (ws.heads / ranks).max(1);
6742 let local_kv_heads = (lkv / head_dim).max(1);
6743 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6745 ws.fa2_q.clear();
6746 ws.fa2_gate.clear();
6747 ws.fa2_gated.clear();
6748 ws.rope_k_t.clear();
6749 ws.rope_ctr_t.clear();
6750 ws.rope_pos_t.clear();
6751 ws.rows_tab_t.clear();
6752 for engine in &self.ranks {
6753 let _m = engine.gpu.enter_main()?;
6754 ws.fa2_q.push(engine.uninit(32 * lq)?);
6755 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6756 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6757 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6758 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6759 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6760 ws.rows_tab_t
6761 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6762 }
6763 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6764 ws.fa2_cap = 32;
6765 }
6766 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6767 ws.tcol_gated.clear();
6768 ws.tcol_opart.clear();
6769 for engine in &self.ranks {
6770 let _m = engine.gpu.enter_main()?;
6771 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6772 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6773 }
6774 let root = &self.ranks[0];
6775 let _m = root.gpu.enter_main()?;
6776 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6777 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6778 ws.tcol_ocap = 32;
6779 }
6780 for rank in 0..ranks {
6781 let engine = &self.ranks[rank];
6782 let _main = engine.gpu.enter_main()?;
6783 if stage_pos {
6784 let host: Vec<i32> = positions[..t].to_vec();
6785 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6786 engine.stream().memcpy_htod(&host, &mut view)?;
6787 }
6788 let ctr_base = {
6807 let s = engine.stream();
6808 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6809 p
6810 };
6811 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6812 if rows_tab_stale_scan() {
6818 if ws.rows_tab_shadow.len() != ranks {
6819 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6820 }
6821 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6822 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank])
6823 && prev != &host
6824 {
6825 let words = ["k", "v", "len", "base", "ctr", "back"];
6826 let moved: Vec<String> = (0..host.len())
6827 .filter(|&i| prev.get(i) != Some(&host[i]))
6828 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6829 .collect();
6830 let stale =
6831 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6832 eprintln!(
6833 "[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",
6834 tab_keys[rank],
6835 moved.join(",")
6836 );
6837 }
6838 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
6839 }
6840 let legacy_memo = !rows_tab_restage_on();
6841 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6842 let tab = engine.stream().clone_htod(&host)?;
6843 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6844 }
6845 if !legacy_memo {
6846 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
6847 engine.stream().memcpy_htod(&host, &mut view)?;
6848 }
6849 let StepTpDecodeV2Ws {
6850 tcol_q,
6851 tcol_k,
6852 tcol_v,
6853 tcol_g,
6854 fa2_q,
6855 fa2_gated,
6856 rope_k_t,
6857 rope_pos_t,
6858 rows_tabs,
6859 rows_tab_t,
6860 ..
6861 } = &mut *ws;
6862 let tab = if legacy_memo {
6863 rows_tabs[rank]
6864 .get(&tab_keys[rank])
6865 .ok_or("rows tab memo lost its entry")?
6866 } else {
6867 &rows_tab_t[rank]
6868 };
6869 engine.qk_norm_rope_append_inc_dcw_rows(
6870 &tcol_q[rank],
6871 &tcol_k[rank],
6872 &tcol_v[rank],
6873 &q_norms[rank],
6874 &k_norms[rank],
6875 &mut fa2_q[rank],
6876 &mut rope_k_t[rank],
6877 tab,
6878 &rope_pos_t[rank],
6879 same_session,
6880 t,
6881 lkv,
6882 lkv,
6883 k_tok_bytes,
6884 v_tok_bytes,
6885 head_dim,
6886 n_rot,
6887 local_heads,
6888 local_kv_heads,
6889 eps,
6890 rope_base,
6891 1.0,
6892 rope_freqs[rank],
6893 )?;
6894 engine.fa_decode_dcw_rows(
6895 &fa2_q[rank],
6896 tab,
6897 &mut fa2_gated[rank],
6898 t,
6899 head_dim,
6900 local_heads,
6901 local_kv_heads,
6902 window,
6903 max_ns,
6904 scale,
6905 k_tok_bytes,
6906 v_tok_bytes,
6907 &tcol_g[rank],
6908 )?;
6909 let StepTpDecodeV2Ws {
6910 fa2_gated,
6911 tcol_gated,
6912 ..
6913 } = &mut *ws;
6914 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6915 engine
6916 .stream()
6917 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6918 }
6919 }
6920 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6921 }
6922
6923 #[allow(clippy::too_many_arguments)]
6930 pub(crate) fn decode_v2_fa_rows_join(
6931 &self,
6932 ws_index: usize,
6933 e: &Engine,
6934 o_m: &ResidentStepBf16RowParallel,
6935 tabs: &[&crate::CudaSlice<u64>],
6936 t: usize,
6937 head_dim: usize,
6938 window: usize,
6939 max_ns: usize,
6940 scale: f32,
6941 k_tok_bytes: usize,
6942 v_tok_bytes: usize,
6943 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6944 let ranks = self.ranks.len();
6945 if tabs.len() != ranks {
6946 return Err("fa rows join needs one table per rank".into());
6947 }
6948 {
6949 let mut guard = self
6950 .decode_v2
6951 .lock()
6952 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6953 let ws = guard
6954 .get_mut(ws_index)
6955 .ok_or("step TP decode v2 workspace index out of range")?;
6956 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6957 return Err("fa rows join without stashed rows".into());
6958 }
6959 let lq = ws.local_q_dim;
6960 let local_heads = (ws.heads / ranks).max(1);
6961 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6962 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6963 ws.tcol_gated.clear();
6964 ws.tcol_opart.clear();
6965 for engine in &self.ranks {
6966 let _m = engine.gpu.enter_main()?;
6967 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6968 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6969 }
6970 let root = &self.ranks[0];
6971 let _m = root.gpu.enter_main()?;
6972 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6973 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6974 ws.tcol_ocap = 32;
6975 }
6976 for rank in 0..ranks {
6977 let engine = &self.ranks[rank];
6978 let _main = engine.gpu.enter_main()?;
6979 {
6980 let StepTpDecodeV2Ws {
6981 fa2_q,
6982 fa2_gate,
6983 fa2_gated,
6984 ..
6985 } = &mut *ws;
6986 engine.fa_decode_dcw_rows(
6987 &fa2_q[rank],
6988 tabs[rank],
6989 &mut fa2_gated[rank],
6990 t,
6991 head_dim,
6992 local_heads,
6993 local_kv_heads,
6994 window,
6995 max_ns,
6996 scale,
6997 k_tok_bytes,
6998 v_tok_bytes,
6999 &fa2_gate[rank],
7000 )?;
7001 }
7002 let StepTpDecodeV2Ws {
7003 fa2_gated,
7004 tcol_gated,
7005 ..
7006 } = &mut *ws;
7007 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
7008 engine
7009 .stream()
7010 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
7011 }
7012 }
7013 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
7014 }
7015
7016 pub(crate) fn decode_v2_stash_gated(
7021 &self,
7022 ws: &mut StepTpDecodeV2Ws,
7023 e: &Engine,
7024 col: usize,
7025 ) -> Result<(), Box<dyn std::error::Error>> {
7026 let ranks = self.ranks.len();
7027 if col >= 32 {
7031 return Err("decode_v2_stash_gated column out of range".into());
7032 }
7033 let lq = ws.local_q_dim;
7034 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
7035 ws.tcol_gated.clear();
7036 ws.tcol_opart.clear();
7037 for engine in &self.ranks {
7038 let _m = engine.gpu.enter_main()?;
7039 ws.tcol_gated.push(engine.uninit(32 * lq)?);
7040 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
7041 }
7042 let root = &self.ranks[0];
7043 let _m = root.gpu.enter_main()?;
7044 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
7045 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
7046 ws.tcol_ocap = 32;
7047 }
7048 for rank in 0..ranks {
7049 let engine = &self.ranks[rank];
7050 let _main = engine.gpu.enter_main()?;
7051 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
7052 engine
7053 .stream()
7054 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
7055 ws.ev_rank[rank].record(&engine.stream())?;
7059 }
7060 {
7061 let _main = e.gpu.enter_main()?;
7062 for ev in ws.ev_rank.iter() {
7063 e.stream().wait(ev)?;
7064 }
7065 }
7066 Ok(())
7067 }
7068
7069 pub(crate) fn decode_v2_oproj_tcol(
7075 &self,
7076 ws_index: usize,
7077 e: &Engine,
7078 o_m: &ResidentStepBf16RowParallel,
7079 t: usize,
7080 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7081 let ranks = self.ranks.len();
7082 let mut guard = self
7083 .decode_v2
7084 .lock()
7085 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
7086 let ws = guard
7087 .get_mut(ws_index)
7088 .ok_or("step TP decode v2 workspace index out of range")?;
7089 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
7090 return Err("decode_v2_oproj_tcol geometry".into());
7091 }
7092 for rank in 0..ranks {
7093 let engine = &self.ranks[rank];
7094 let _main = engine.gpu.enter_main()?;
7095 let mut weights = Vec::with_capacity(4);
7096 for block in 0..4 {
7097 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7098 return Err("tcol o_proj requires bf16-resident O blocks".into());
7099 };
7100 weights.push(weight);
7101 }
7102 {
7103 let StepTpDecodeV2Ws {
7104 tcol_gated,
7105 tcol_opart,
7106 local_q_dim,
7107 o_block_cols,
7108 o_out,
7109 w8t_oaq,
7110 w8t_oad,
7111 w8t_oin,
7112 w8t_cap,
7113 ..
7114 } = &mut *ws;
7115 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7118 let refk = *REFK
7119 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
7120 if refk {
7121 let lq = *local_q_dim;
7122 let mut xr = engine.uninit(lq)?;
7123 let mut yr = engine.uninit(*o_out)?;
7124 for c in 0..t {
7125 {
7126 let mut dst = xr.slice_mut(0..lq);
7127 engine.stream().memcpy_dtod(
7128 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
7129 &mut dst,
7130 )?;
7131 }
7132 engine.matvec_bf16_b4_into(
7133 [weights[0], weights[1], weights[2], weights[3]],
7134 &xr,
7135 &mut yr,
7136 *o_block_cols,
7137 *o_out,
7138 )?;
7139 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
7140 engine
7141 .stream()
7142 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
7143 }
7144 } else if crate::step_tp_w8_on()
7145 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
7146 && (4 * *o_block_cols) % 32 == 0
7147 {
7148 let in_f = 4 * *o_block_cols;
7152 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
7153 w8t_oaq.clear();
7154 w8t_oad.clear();
7155 for e_rank in &self.ranks {
7156 let _m = e_rank.gpu.enter_main()?;
7157 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
7158 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
7159 }
7160 *w8t_oin = in_f;
7161 *w8t_cap = (*w8t_cap).max(32);
7162 }
7163 engine.quantize_q8_1_into(
7164 &tcol_gated[rank],
7165 t,
7166 in_f,
7167 &mut w8t_oaq[rank],
7168 &mut w8t_oad[rank],
7169 )?;
7170 engine.qmatvec_q8_0_b4_rp_t_into(
7171 [
7172 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7173 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7174 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7175 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7176 ],
7177 &w8t_oaq[rank],
7178 &w8t_oad[rank],
7179 &mut tcol_opart[rank],
7180 *o_block_cols,
7181 *o_out,
7182 t,
7183 )?;
7184 } else {
7185 engine.matvec_bf16_b4_tcol_into(
7186 [weights[0], weights[1], weights[2], weights[3]],
7187 &tcol_gated[rank],
7188 &mut tcol_opart[rank],
7189 *o_block_cols,
7190 *o_out,
7191 t,
7192 )?;
7193 }
7194 }
7195 if rank != 0 {
7196 ws.ev_rank[rank].record(&engine.stream())?;
7197 }
7198 }
7199 let root = &self.ranks[0];
7200 {
7201 let _main = root.gpu.enter_main()?;
7202 for ev in ws.ev_rank.iter().skip(1) {
7203 root.stream().wait(ev)?;
7204 }
7205 {
7206 let StepTpDecodeV2Ws {
7207 tcol_opart,
7208 tcol_opeer,
7209 tcol_omix,
7210 o_out,
7211 ..
7212 } = &mut *ws;
7213 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
7214 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
7215 {
7216 let mut dst = opeer.slice_mut(0..t * *o_out);
7217 root.stream()
7218 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
7219 }
7220 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
7223 }
7224 ws.ev_oproj.record(&root.stream())?;
7225 }
7226 let _main = e.gpu.enter_main()?;
7227 e.stream().wait(&ws.ev_oproj)?;
7228 let mut out = e.uninit(t * ws.o_out)?;
7229 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
7230 e.stream().memcpy_dtod(
7231 &omix.slice(0..t * ws.o_out),
7232 &mut out.slice_mut(0..t * ws.o_out),
7233 )?;
7234 Ok(out)
7235 }
7236
7237 #[allow(clippy::too_many_arguments)] pub(crate) fn decode_v2_input_qkv(
7239 &self,
7240 ws: &mut StepTpDecodeV2Ws,
7241 e: &Engine,
7242 h: &CudaSlice<f32>,
7243 pos_d: &CudaSlice<i32>,
7244 gate_raw: Option<&CudaSlice<f32>>,
7245 gate_shards: Option<StepTpGateShards<'_>>,
7246 decode_input: &mut ResidentReplicatedDeviceRows,
7247 q_m: &ResidentBf16ColumnParallel,
7248 k_m: &ResidentBf16ColumnParallel,
7249 v_m: &ResidentBf16ColumnParallel,
7250 q_norm: &[CudaSlice<f32>],
7251 k_norm: &[CudaSlice<f32>],
7252 head_dim: usize,
7253 n_rot: usize,
7254 rope_base: f32,
7255 rope_freqs: &[Option<&CudaSlice<f32>>],
7256 rms_eps: f32,
7257 has_gate: bool,
7258 defer_norm_rope: bool,
7259 tcol_col: Option<usize>,
7260 ) -> Result<(), Box<dyn std::error::Error>> {
7261 let ranks = self.ranks.len();
7262 validate_replicated_device_rows(&self.ranks, decode_input)?;
7263 let gate_sources = usize::from(gate_raw.is_some()) + usize::from(gate_shards.is_some());
7264 if decode_input.tokens != 1
7265 || decode_input.width != q_m.in_features
7266 || pos_d.len() != 1
7267 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
7268 || (has_gate && gate_sources != 1)
7269 || (!has_gate && gate_sources != 0)
7270 || gate_shards.as_ref().is_some_and(|shards| match shards {
7271 StepTpGateShards::F32(shards) => shards.len() != ranks,
7272 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
7273 })
7274 || q_norm.len() != ranks
7275 || k_norm.len() != ranks
7276 || rope_freqs.len() != ranks
7277 || e.ctx().ordinal() != ws.e_device
7278 {
7279 return Err("step TP decode v2 input geometry mismatch".into());
7280 }
7281
7282 let qkv_fused = step_tp_qkv_fused_enabled()?;
7283 if gate_shards.is_some() && !qkv_fused {
7284 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
7285 }
7286 let values = decode_input.width;
7287 if h.len() != values {
7288 return Err(format!(
7289 "step TP decode v2 hidden width {} != replicated width {values}",
7290 h.len()
7291 )
7292 .into());
7293 }
7294
7295 if qkv_fused {
7296 if ws.h_stage.is_none() {
7300 use cudarc::driver::DevicePtr;
7301 let _main = e.gpu.enter_main()?;
7302 let h_stage = e.uninit(values)?;
7303 let pos_stage = e.htod_i32(&[0])?;
7304 {
7305 let stream = e.stream();
7306 let (hp, _g0) = h_stage.device_ptr(&stream);
7307 let (pp, _g1) = pos_stage.device_ptr(&stream);
7308 ws.raw_h_stage = hp;
7309 ws.raw_pos_stage = pp;
7310 }
7311 ws.h_stage = Some(h_stage);
7312 ws.pos_stage = Some(pos_stage);
7313 for rank in 0..ranks {
7314 use cudarc::driver::DevicePtr;
7315 let engine = &self.ranks[rank];
7316 let _rmain = engine.gpu.enter_main()?;
7317 let attn_in = engine.uninit(values)?;
7318 let (dp, pp) = {
7319 let stream = engine.stream();
7320 let (dp, _g2) = attn_in.device_ptr(&stream);
7321 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
7322 (dp, pp)
7323 };
7324 ws.raw_attn_in.push(dp);
7325 ws.raw_pos.push(pp);
7326 ws.attn_in.push(attn_in);
7327 }
7328 {
7329 use cudarc::driver::DevicePtr;
7330 let root = &self.ranks[0];
7331 let _rmain = root.gpu.enter_main()?;
7332 let stream = root.stream();
7333 let (a, _g) = ws.peer_partial.device_ptr(&stream);
7334 let (b, _g) = ws.k_shadow.device_ptr(&stream);
7335 let (c, _g) = ws.v_shadow.device_ptr(&stream);
7336 ws.raw_peer_partial = a;
7337 ws.raw_k_shadow = b;
7338 ws.raw_v_shadow = c;
7339 }
7340 {
7341 use cudarc::driver::DevicePtr;
7342 let rank1 = &self.ranks[1];
7343 let _rmain = rank1.gpu.enter_main()?;
7344 let stream = rank1.stream();
7345 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
7346 let (b, _g) = ws.k[1].device_ptr(&stream);
7347 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
7348 ws.raw_o_partial1 = a;
7349 ws.raw_k1 = b;
7350 ws.raw_v1 = c;
7351 }
7352 }
7353 {
7354 let _main = e.gpu.enter_main()?;
7355 {
7356 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
7359 let mut dst = h_stage.slice_mut(0..values);
7360 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
7361 }
7362 {
7363 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
7364 let mut dst = pos_stage.slice_mut(0..1);
7365 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
7366 }
7367 ws.ev_entry.record(&e.stream())?;
7368 }
7369 for rank in 0..ranks {
7370 let engine = &self.ranks[rank];
7371 let _main = engine.gpu.enter_main()?;
7372 engine.stream().wait(&ws.ev_entry)?;
7373 }
7374 } else {
7375 {
7377 let _main = e.gpu.enter_main()?;
7378 if let Some(gate_raw) = gate_raw {
7379 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
7380 e.stream()
7381 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
7382 }
7383 ws.ev_entry.record(&e.stream())?;
7384 }
7385 {
7386 let root = &self.ranks[0];
7387 let _main = root.gpu.enter_main()?;
7388 root.stream().wait(&ws.ev_entry)?;
7389 let mut destination = decode_input.ranks[0].slice_mut(0..values);
7390 root.stream()
7391 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
7392 ws.ev_refresh.record(&root.stream())?;
7393 }
7394 for rank in 1..ranks {
7395 let engine = &self.ranks[rank];
7396 let _main = engine.gpu.enter_main()?;
7397 engine.stream().wait(&ws.ev_refresh)?;
7398 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
7399 let mut destination = peer_rows[0].slice_mut(0..values);
7400 engine
7401 .stream()
7402 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
7403 }
7404 }
7405 for rank in 0..ranks {
7406 self.decode_v2_input_qkv_rank(
7407 ws,
7408 pos_d,
7409 decode_input,
7410 q_m,
7411 k_m,
7412 v_m,
7413 q_norm,
7414 k_norm,
7415 head_dim,
7416 n_rot,
7417 rope_base,
7418 rope_freqs,
7419 rms_eps,
7420 gate_shards.as_ref(),
7421 has_gate,
7422 qkv_fused,
7423 defer_norm_rope,
7424 rank,
7425 tcol_col,
7426 )?;
7427 }
7428 Ok(())
7429 }
7430
7431 #[allow(clippy::too_many_arguments)]
7434 pub(crate) fn decode_v2_input_qkv_rank(
7435 &self,
7436 ws: &mut StepTpDecodeV2Ws,
7437 pos_d: &CudaSlice<i32>,
7438 decode_input: &mut ResidentReplicatedDeviceRows,
7439 q_m: &ResidentBf16ColumnParallel,
7440 k_m: &ResidentBf16ColumnParallel,
7441 v_m: &ResidentBf16ColumnParallel,
7442 q_norm: &[CudaSlice<f32>],
7443 k_norm: &[CudaSlice<f32>],
7444 head_dim: usize,
7445 n_rot: usize,
7446 rope_base: f32,
7447 rope_freqs: &[Option<&CudaSlice<f32>>],
7448 rms_eps: f32,
7449 gate_shards: Option<&StepTpGateShards<'_>>,
7450 has_gate: bool,
7451 qkv_fused: bool,
7452 defer_norm_rope: bool,
7453 rank: usize,
7454 tcol_col: Option<usize>,
7455 ) -> Result<(), Box<dyn std::error::Error>> {
7456 let ranks = self.ranks.len();
7457 let local_heads = ws.local_q_dim / head_dim;
7458 let local_kv_heads = ws.local_kv_dim / head_dim;
7459 let engine = &self.ranks[rank];
7460 let _main = engine.gpu.enter_main()?;
7461 let ws_e_device = ws.e_device;
7462 if qkv_fused && tcol_col.is_some() {
7467 #[allow(clippy::unnecessary_unwrap)]
7468 let c = tcol_col.expect("checked");
7470 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
7471 return Err("tcol select without precompute".into());
7472 }
7473 if engine.ctx().ordinal() != ws_e_device {
7477 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7478 }
7479 let StepTpDecodeV2Ws {
7480 tcol_q,
7481 tcol_k,
7482 tcol_v,
7483 tcol_g,
7484 q_raw,
7485 k_raw,
7486 v_raw,
7487 gate,
7488 local_q_dim,
7489 local_kv_dim,
7490 heads,
7491 ..
7492 } = &mut *ws;
7493 let lg = *heads / ranks;
7494 let stream = engine.stream();
7495 {
7496 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
7497 stream.memcpy_dtod(
7498 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
7499 &mut dst,
7500 )?;
7501 }
7502 {
7503 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
7504 stream.memcpy_dtod(
7505 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7506 &mut dst,
7507 )?;
7508 }
7509 {
7510 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
7511 stream.memcpy_dtod(
7512 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
7513 &mut dst,
7514 )?;
7515 }
7516 if has_gate && lg > 0 {
7517 let mut dst = gate[rank].slice_mut(0..lg);
7518 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
7519 }
7520 if !defer_norm_rope {
7521 } else {
7525 return Ok(());
7526 }
7527 }
7528 if qkv_fused {
7529 let same_dev = engine.ctx().ordinal() == ws.e_device;
7534 if !same_dev {
7535 raw_copy_bytes(
7536 ws.raw_attn_in[rank],
7537 ws.raw_h_stage,
7538 q_m.in_features * 4,
7539 engine,
7540 )?;
7541 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
7542 }
7543 let StepTpDecodeV2Ws {
7544 q_raw,
7545 k_raw,
7546 v_raw,
7547 gate,
7548 gate_e,
7549 attn_in,
7550 h_stage,
7551 heads,
7552 local_q_dim,
7553 local_kv_dim,
7554 w8_aq,
7555 w8_ad,
7556 w8_in,
7557 ..
7558 } = &mut *ws;
7559 let input_ref: &CudaSlice<f32> = if same_dev {
7560 h_stage
7561 .as_ref()
7562 .ok_or("step TP decode v2 stage not armed")?
7563 } else {
7564 &attn_in[rank]
7565 };
7566 match (
7567 &q_m.ranks[rank].weight,
7568 &k_m.ranks[rank].weight,
7569 &v_m.ranks[rank].weight,
7570 ) {
7571 (
7572 ResidentBf16Weight::F32(wq),
7573 ResidentBf16Weight::F32(wk),
7574 ResidentBf16Weight::F32(wv),
7575 ) => {
7576 let (wg, out_g) = match &gate_shards {
7577 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
7578 Some(StepTpGateShards::Bf16(_)) => {
7579 return Err("step TP decode v2 gate shard class does not \
7580 match the F32 projections"
7581 .into());
7582 }
7583 None => (&*gate_e, 0),
7585 };
7586 engine.matvec_f32_qkv_into(
7587 wq,
7588 wk,
7589 wv,
7590 wg,
7591 input_ref,
7592 &mut q_raw[rank],
7593 &mut k_raw[rank],
7594 &mut v_raw[rank],
7595 &mut gate[rank],
7596 q_m.in_features,
7597 *local_q_dim,
7598 *local_kv_dim,
7599 out_g,
7600 )?;
7601 }
7602 (
7603 ResidentBf16Weight::Bf16(wq),
7604 ResidentBf16Weight::Bf16(wk),
7605 ResidentBf16Weight::Bf16(wv),
7606 ) => {
7607 let (wg, out_g) = match &gate_shards {
7608 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
7609 Some(StepTpGateShards::F32(_)) => {
7610 return Err("step TP decode v2 gate shard class does not \
7611 match the bf16 projections"
7612 .into());
7613 }
7614 None => (wq, 0),
7615 };
7616 let in_f = q_m.in_features;
7623 let q8_ready = crate::step_tp_w8_on()
7624 && q_m.ranks[rank].q8.is_some()
7625 && k_m.ranks[rank].q8.is_some()
7626 && v_m.ranks[rank].q8.is_some();
7627 if q8_ready {
7628 if *w8_in != in_f || w8_aq.len() != ranks {
7629 w8_aq.clear();
7630 w8_ad.clear();
7631 for e_rank in &self.ranks {
7632 let _m = e_rank.gpu.enter_main()?;
7633 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7634 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7635 }
7636 *w8_in = in_f;
7637 }
7638 engine.quantize_q8_1_into(
7639 input_ref,
7640 1,
7641 in_f,
7642 &mut w8_aq[rank],
7643 &mut w8_ad[rank],
7644 )?;
7645 engine.qmatvec_q8_0_qkv_rp_into(
7650 q_m.ranks[rank].q8.as_ref().unwrap(),
7651 k_m.ranks[rank].q8.as_ref().unwrap(),
7652 v_m.ranks[rank].q8.as_ref().unwrap(),
7653 &w8_aq[rank],
7654 &w8_ad[rank],
7655 &mut q_raw[rank],
7656 &mut k_raw[rank],
7657 &mut v_raw[rank],
7658 in_f,
7659 *local_q_dim,
7660 *local_kv_dim,
7661 )?;
7662 if out_g > 0 {
7663 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7664 }
7665 } else {
7666 engine.matvec_bf16_qkvg_into(
7667 wq,
7668 wk,
7669 wv,
7670 wg,
7671 input_ref,
7672 &mut q_raw[rank],
7673 &mut k_raw[rank],
7674 &mut v_raw[rank],
7675 &mut gate[rank],
7676 q_m.in_features,
7677 *local_q_dim,
7678 *local_kv_dim,
7679 out_g,
7680 )?;
7681 }
7682 }
7683 _ => {
7684 return Err("step TP decode v2 QKV projections mix residency classes".into());
7685 }
7686 }
7687 } else {
7688 for (matrix, local_out, raw) in [
7689 (q_m, ws.local_q_dim, &mut ws.q_raw),
7690 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7691 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7692 ] {
7693 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7694 return Err("step TP decode v2 lost its F32 projection residency".into());
7695 };
7696 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7697 engine.linear_f32_resident_canonical_rows_t1_into(
7698 &decode_input.ranks[rank],
7699 values_w,
7700 &mut raw[rank],
7701 matrix.in_features,
7702 local_out,
7703 chunk_rows,
7704 )?;
7705 }
7706 }
7707 if qkv_fused && defer_norm_rope {
7708 } else if qkv_fused {
7710 let StepTpDecodeV2Ws {
7713 q_raw,
7714 k_raw,
7715 q,
7716 k,
7717 pos,
7718 pos_stage,
7719 ..
7720 } = &mut *ws;
7721 let same_dev = engine.ctx().ordinal() == ws_e_device;
7722 let pos_ref: &CudaSlice<i32> = if same_dev {
7723 pos_stage
7724 .as_ref()
7725 .ok_or("step TP decode v2 pos stage not armed")?
7726 } else {
7727 &pos[rank]
7728 };
7729 engine.qk_norm_rope_into(
7730 &q_raw[rank],
7731 &k_raw[rank],
7732 &q_norm[rank],
7733 &k_norm[rank],
7734 &mut q[rank],
7735 &mut k[rank],
7736 pos_ref,
7737 head_dim,
7738 n_rot,
7739 local_heads,
7740 local_kv_heads,
7741 rms_eps,
7742 rope_base,
7743 1.0,
7744 rope_freqs[rank],
7745 )?;
7746 } else {
7747 engine.rms_norm(
7748 &ws.q_raw[rank],
7749 &q_norm[rank],
7750 &mut ws.q[rank],
7751 head_dim,
7752 local_heads,
7753 rms_eps,
7754 )?;
7755 engine.rms_norm(
7756 &ws.k_raw[rank],
7757 &k_norm[rank],
7758 &mut ws.k[rank],
7759 head_dim,
7760 local_kv_heads,
7761 rms_eps,
7762 )?;
7763 {
7764 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7765 engine
7766 .stream()
7767 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7768 }
7769 engine.rope_neox2(
7770 &mut ws.q[rank],
7771 &mut ws.k[rank],
7772 &ws.pos[rank],
7773 head_dim,
7774 n_rot,
7775 local_heads,
7776 local_kv_heads,
7777 1,
7778 rope_base,
7779 1.0,
7780 rope_freqs[rank],
7781 )?;
7782 }
7783 if has_gate && gate_shards.is_none() {
7784 let gate_start = rank * (ws.heads / ranks);
7785 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7786 engine.stream().memcpy_dtod(
7787 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7788 &mut gate_dst,
7789 )?;
7790 }
7791 Ok(())
7792 }
7793
7794 pub(crate) fn decode_v2_finish_rank_partial(
7798 &self,
7799 ws: &mut StepTpDecodeV2Ws,
7800 o_m: &ResidentStepBf16RowParallel,
7801 o_fused: bool,
7802 rank: usize,
7803 ) -> Result<(), Box<dyn std::error::Error>> {
7804 let engine = &self.ranks[rank];
7805 let _main = engine.gpu.enter_main()?;
7806 if o_fused {
7807 let StepTpDecodeV2Ws {
7808 gated,
7809 o_partials,
7810 o_block_cols,
7811 o_out,
7812 w8o_aq,
7813 w8o_ad,
7814 w8o_in,
7815 ..
7816 } = &mut *ws;
7817 let all_f32 = o_m.ranks[rank]
7818 .iter()
7819 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7820 if all_f32 {
7821 let mut weights = Vec::with_capacity(4);
7822 for block in 0..4 {
7823 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7824 unreachable!("all_f32 checked above");
7825 };
7826 weights.push(weight);
7827 }
7828 engine.matvec_f32_b4_into(
7829 [weights[0], weights[1], weights[2], weights[3]],
7830 &gated[rank],
7831 &mut o_partials[rank][0],
7832 *o_block_cols,
7833 *o_out,
7834 )?;
7835 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
7836 let in_f = 4 * *o_block_cols;
7841 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
7842 w8o_aq.clear();
7843 w8o_ad.clear();
7844 for e_rank in &self.ranks {
7845 let _m = e_rank.gpu.enter_main()?;
7846 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7847 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7848 }
7849 *w8o_in = in_f;
7850 }
7851 engine.quantize_q8_1_into(
7852 &gated[rank],
7853 1,
7854 in_f,
7855 &mut w8o_aq[rank],
7856 &mut w8o_ad[rank],
7857 )?;
7858 engine.qmatvec_q8_0_b4_rp_into(
7859 [
7860 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7861 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7862 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7863 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7864 ],
7865 &w8o_aq[rank],
7866 &w8o_ad[rank],
7867 &mut o_partials[rank][0],
7868 *o_block_cols,
7869 *o_out,
7870 )?;
7871 } else {
7872 let mut weights = Vec::with_capacity(4);
7873 for block in 0..4 {
7874 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7875 return Err("step TP decode v2 O projections mix residency classes".into());
7876 };
7877 weights.push(weight);
7878 }
7879 engine.matvec_bf16_b4_into(
7880 [weights[0], weights[1], weights[2], weights[3]],
7881 &gated[rank],
7882 &mut o_partials[rank][0],
7883 *o_block_cols,
7884 *o_out,
7885 )?;
7886 }
7887 } else {
7888 for block in 0..ws.blocks_per_rank {
7889 let x =
7890 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7891 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7892 match &o_m.ranks[rank][block].weight {
7893 ResidentBf16Weight::F32(weight) => {
7894 let w = weight.slice(0..weight.len());
7895 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7896 }
7897 ResidentBf16Weight::Bf16(weight) => {
7898 engine.matvec_bf16_views_into(
7899 weight,
7900 &x,
7901 &mut y,
7902 ws.o_block_cols,
7903 ws.o_out,
7904 )?;
7905 }
7906 }
7907 }
7908 }
7909 Ok(())
7910 }
7911
7912 pub(crate) fn decode_v2_finish(
7920 &self,
7921 ws: &mut StepTpDecodeV2Ws,
7922 e: &Engine,
7923 o_m: &ResidentStepBf16RowParallel,
7924 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7925 let ranks = self.ranks.len();
7926 if e.ctx().ordinal() != ws.e_device {
7927 return Err("step TP decode v2 finish engine changed".into());
7928 }
7929 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7934
7935 for rank in 0..ranks {
7938 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7939 if rank == 0 {
7940 continue;
7943 }
7944 let engine = &self.ranks[rank];
7945 let _main = engine.gpu.enter_main()?;
7946 ws.ev_rank[rank].record(&engine.stream())?;
7947 }
7948
7949 let root = &self.ranks[0];
7951 #[allow(unused_assignments)]
7952 let mut final_in_a = false;
7953 {
7954 let _main = root.gpu.enter_main()?;
7955 for ev in ws.ev_rank.iter().skip(1) {
7956 root.stream().wait(ev)?;
7957 }
7958 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7959 ws.ev_oproj.record(&root.stream())?;
7965 let _main = e.gpu.enter_main()?;
7966 e.stream().wait(&ws.ev_oproj)?;
7967 let mut output = e.uninit(ws.o_out)?;
7968 if oproj_tail_on() && oproj_tail_eligible() {
7969 use cudarc::driver::DevicePtr;
7972 let stream = e.stream();
7973 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7974 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7975 set_oproj_tail((p0, p1));
7976 return Ok(output);
7977 }
7978 e.add(
7979 &ws.o_partials[0][0],
7980 &ws.o_partials[1][0],
7981 &mut output,
7982 ws.o_out,
7983 )?;
7984 return Ok(output);
7985 }
7986 if o_fused {
7987 self.decode_v2_finish_root_fused(ws)?;
7988 ws.ev_oproj.record(&root.stream())?;
7989 let _main = e.gpu.enter_main()?;
7990 e.stream().wait(&ws.ev_oproj)?;
7991 let mut output = e.uninit(ws.o_out)?;
7992 e.stream().memcpy_dtod(
7993 &ws.reduce_a.slice(0..ws.o_out),
7994 &mut output.slice_mut(0..ws.o_out),
7995 )?;
7996 return Ok(output);
7997 }
7998 let mut first = true;
7999 let mut current_is_a = false;
8000 for rank in 0..ranks {
8001 for block in 0..ws.blocks_per_rank {
8002 let use_peer = rank != 0;
8003 if use_peer {
8004 raw_copy_bytes(
8005 ws.raw_peer_partial,
8006 ws.raw_o_partials[rank][block],
8007 ws.o_out * std::mem::size_of::<f32>(),
8008 root,
8009 )?;
8010 }
8011 match (first, current_is_a, use_peer) {
8013 (true, _, true) => {
8014 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
8015 }
8016 (true, _, false) => root.add(
8017 &ws.zeros,
8018 &ws.o_partials[0][block],
8019 &mut ws.reduce_a,
8020 ws.o_out,
8021 )?,
8022 (false, true, true) => {
8023 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
8024 }
8025 (false, true, false) => root.add(
8026 &ws.reduce_a,
8027 &ws.o_partials[0][block],
8028 &mut ws.reduce_b,
8029 ws.o_out,
8030 )?,
8031 (false, false, true) => {
8032 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
8033 }
8034 (false, false, false) => root.add(
8035 &ws.reduce_b,
8036 &ws.o_partials[0][block],
8037 &mut ws.reduce_a,
8038 ws.o_out,
8039 )?,
8040 }
8041 current_is_a = first || !current_is_a;
8042 first = false;
8043 }
8044 }
8045 final_in_a = current_is_a;
8046
8047 if !no_local_shadow_on() {
8048 let bytes = ws.local_kv_dim * std::mem::size_of::<f32>();
8049 for rank in 0..ranks {
8050 let offset = rank * bytes;
8051 raw_copy_bytes(ws.raw_k_shadow + offset as u64, ws.raw_k[rank], bytes, root)?;
8052 raw_copy_bytes(
8053 ws.raw_v_shadow + offset as u64,
8054 ws.raw_v_raw[rank],
8055 bytes,
8056 root,
8057 )?;
8058 }
8059 }
8060 ws.ev_oproj.record(&root.stream())?;
8061 }
8062
8063 let _main = e.gpu.enter_main()?;
8067 e.stream().wait(&ws.ev_oproj)?;
8068 let mut output = e.uninit(ws.o_out)?;
8069 let source = if final_in_a {
8070 &ws.reduce_a
8071 } else {
8072 &ws.reduce_b
8073 };
8074 e.stream().memcpy_dtod(
8075 &source.slice(0..ws.o_out),
8076 &mut output.slice_mut(0..ws.o_out),
8077 )?;
8078 Ok(output)
8079 }
8080
8081 #[allow(clippy::too_many_arguments)] pub fn run_routed_experts(
8083 &self,
8084 experts: &ResidentExpertParallel,
8085 input: &[f32],
8086 tokens: usize,
8087 selected: &[usize],
8088 route_weights: &[f32],
8089 experts_per_token: usize,
8090 activation_limit: Option<f32>,
8091 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8092 validate_step_expert_activation_limit(activation_limit)?;
8093 validate_ep_residency(&self.ranks, experts)?;
8094 validate_activations(input, tokens, experts.input_width)?;
8095 let pairs = tokens
8096 .checked_mul(experts_per_token)
8097 .ok_or("EP route count overflow")?;
8098 if selected.len() != pairs || route_weights.len() != pairs {
8099 return Err(format!(
8100 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
8101 {experts_per_token} ({pairs})",
8102 selected.len(),
8103 route_weights.len(),
8104 )
8105 .into());
8106 }
8107 if !route_weights.iter().all(|weight| weight.is_finite()) {
8108 return Err("EP route weights contain a non-finite value".into());
8109 }
8110 if self.native_p2p {
8111 return self.run_routed_experts_native(
8112 experts,
8113 input,
8114 tokens,
8115 selected,
8116 route_weights,
8117 experts_per_token,
8118 activation_limit,
8119 );
8120 }
8121
8122 let mut output = vec![0.0f32; tokens * experts.input_width];
8123 let per_rank = experts.expert_count / experts.ranks.len();
8124 for token in 0..tokens {
8125 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8126 for slot in 0..experts_per_token {
8127 let pair = token * experts_per_token + slot;
8128 let expert = selected[pair];
8129 if expert >= experts.expert_count {
8130 return Err(format!(
8131 "EP selected expert {expert} outside 0..{}",
8132 experts.expert_count
8133 )
8134 .into());
8135 }
8136 let owner = expert / per_rank;
8137 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8138 let rank = &experts.ranks[owner];
8139 let engine = &self.ranks[owner];
8140 let gate =
8141 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
8142 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
8143 let activated: Vec<f32> = gate
8144 .iter()
8145 .zip(&up)
8146 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8147 .collect();
8148 debug_assert_eq!(activated.len(), experts.expert_width);
8149 let down =
8150 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
8151 let weight = route_weights[pair];
8152 for (sum, value) in output
8153 [token * experts.input_width..(token + 1) * experts.input_width]
8154 .iter_mut()
8155 .zip(down)
8156 {
8157 *sum += weight * value;
8158 }
8159 }
8160 }
8161 Ok(output)
8162 }
8163
8164 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native(
8166 &self,
8167 experts: &ResidentExpertParallel,
8168 input: &[f32],
8169 tokens: usize,
8170 selected: &[usize],
8171 route_weights: &[f32],
8172 experts_per_token: usize,
8173 activation_limit: Option<f32>,
8174 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8175 if !self.native_p2p || self.ranks.len() < 2 {
8176 return Err("native EP execution requires at least two P2P ranks".into());
8177 }
8178 if self.ep_device_arithmetic {
8179 return self.run_routed_experts_native_device(
8180 experts,
8181 input,
8182 tokens,
8183 selected,
8184 route_weights,
8185 experts_per_token,
8186 activation_limit,
8187 );
8188 }
8189 let mut output = vec![0.0f32; tokens * experts.input_width];
8190 let per_rank = experts.expert_count / experts.ranks.len();
8191 for token in 0..tokens {
8192 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8193 let mut rank_inputs = (0..self.ranks.len())
8194 .map(|_| None)
8195 .collect::<Vec<Option<CudaSlice<f32>>>>();
8196 rank_inputs[0] = Some({
8197 let root = &self.ranks[0];
8198 let _main = root.gpu.enter_main()?;
8199 root.htod(input_row)?
8200 });
8201
8202 for slot in 0..experts_per_token {
8203 let pair = token * experts_per_token + slot;
8204 let expert = selected[pair];
8205 if expert >= experts.expert_count {
8206 return Err(format!(
8207 "EP selected expert {expert} outside 0..{}",
8208 experts.expert_count
8209 )
8210 .into());
8211 }
8212 let owner = expert / per_rank;
8213 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8214 if rank_inputs[owner].is_none() {
8215 let peer_input = {
8216 let root_input = rank_inputs[0]
8217 .as_ref()
8218 .ok_or("native EP lost its root input")?;
8219 let engine = &self.ranks[owner];
8220 let _main = engine.gpu.enter_main()?;
8221 let mut peer_input = engine.uninit(experts.input_width)?;
8222 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8223 peer_input
8224 };
8225 rank_inputs[owner] = Some(peer_input);
8226 }
8227
8228 let rank = &experts.ranks[owner];
8229 let engine = &self.ranks[owner];
8230 let owner_input = rank_inputs[owner]
8231 .as_ref()
8232 .ok_or("native EP owner input is absent after dispatch")?;
8233 let gate = run_resident_bank_expert_device(
8234 engine,
8235 &rank.gate,
8236 local_expert,
8237 owner_input,
8238 1,
8239 )?;
8240 let up = run_resident_bank_expert_device(
8241 engine,
8242 &rank.up,
8243 local_expert,
8244 owner_input,
8245 1,
8246 )?;
8247 let (gate, up) = {
8248 let _main = engine.gpu.enter_main()?;
8249 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
8250 };
8251 let activated = gate
8252 .iter()
8253 .zip(&up)
8254 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
8255 .collect::<Vec<_>>();
8256 debug_assert_eq!(activated.len(), experts.expert_width);
8257 let activated = {
8258 let _main = engine.gpu.enter_main()?;
8259 engine.htod(&activated)?
8260 };
8261 let down = run_resident_bank_expert_device(
8262 engine,
8263 &rank.down,
8264 local_expert,
8265 &activated,
8266 1,
8267 )?;
8268 let down = if owner == 0 {
8269 let _main = engine.gpu.enter_main()?;
8270 engine.dtoh(&down)?
8271 } else {
8272 let root = &self.ranks[0];
8273 let _main = root.gpu.enter_main()?;
8274 let mut root_down = root.uninit(experts.input_width)?;
8275 root.stream().memcpy_dtod(&down, &mut root_down)?;
8276 root.dtoh(&root_down)?
8277 };
8278 let weight = route_weights[pair];
8279 for (sum, value) in output
8280 [token * experts.input_width..(token + 1) * experts.input_width]
8281 .iter_mut()
8282 .zip(down)
8283 {
8284 *sum += weight * value;
8285 }
8286 }
8287 }
8288 Ok(output)
8289 }
8290
8291 #[allow(clippy::too_many_arguments)] fn run_routed_experts_native_device(
8293 &self,
8294 experts: &ResidentExpertParallel,
8295 input: &[f32],
8296 tokens: usize,
8297 selected: &[usize],
8298 route_weights: &[f32],
8299 experts_per_token: usize,
8300 activation_limit: Option<f32>,
8301 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8302 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
8303 return Err(
8304 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
8305 );
8306 }
8307 let mut output = Vec::with_capacity(tokens * experts.input_width);
8308 let per_rank = experts.expert_count / experts.ranks.len();
8309 let root = &self.ranks[0];
8310 for token in 0..tokens {
8311 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
8312 let mut rank_inputs = (0..self.ranks.len())
8313 .map(|_| None)
8314 .collect::<Vec<Option<CudaSlice<f32>>>>();
8315 rank_inputs[0] = Some({
8316 let _main = root.gpu.enter_main()?;
8317 root.htod(input_row)?
8318 });
8319 let mut root_output = {
8320 let _main = root.gpu.enter_main()?;
8321 root.zeros(experts.input_width)?
8322 };
8323 let mut remote_down_keepalive = Vec::new();
8324
8325 for slot in 0..experts_per_token {
8326 let pair = token * experts_per_token + slot;
8327 let expert = selected[pair];
8328 if expert >= experts.expert_count {
8329 return Err(format!(
8330 "EP selected expert {expert} outside 0..{}",
8331 experts.expert_count
8332 )
8333 .into());
8334 }
8335 let owner = expert / per_rank;
8336 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
8337 if rank_inputs[owner].is_none() {
8338 let peer_input = {
8339 let root_input = rank_inputs[0]
8340 .as_ref()
8341 .ok_or("native EP lost its root input")?;
8342 let engine = &self.ranks[owner];
8343 let _main = engine.gpu.enter_main()?;
8344 let mut peer_input = engine.uninit(experts.input_width)?;
8345 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
8346 peer_input
8347 };
8348 rank_inputs[owner] = Some(peer_input);
8349 }
8350
8351 let rank = &experts.ranks[owner];
8352 let engine = &self.ranks[owner];
8353 let owner_input = rank_inputs[owner]
8354 .as_ref()
8355 .ok_or("native EP owner input is absent after dispatch")?;
8356 let gate = run_resident_bank_expert_device(
8357 engine,
8358 &rank.gate,
8359 local_expert,
8360 owner_input,
8361 1,
8362 )?;
8363 let up = run_resident_bank_expert_device(
8364 engine,
8365 &rank.up,
8366 local_expert,
8367 owner_input,
8368 1,
8369 )?;
8370 let activated = {
8371 let _main = engine.gpu.enter_main()?;
8372 let mut activated = engine.uninit(experts.expert_width)?;
8373 if let Some(limit) = activation_limit {
8374 engine.silu_clamped_mul_host_expf(
8375 &gate,
8376 &up,
8377 limit,
8378 &mut activated,
8379 experts.expert_width,
8380 )?;
8381 } else {
8382 engine.silu_mul_host_expf(
8383 &gate,
8384 &up,
8385 &mut activated,
8386 experts.expert_width,
8387 )?;
8388 }
8389 activated
8390 };
8391 let down = run_resident_bank_expert_device(
8392 engine,
8393 &rank.down,
8394 local_expert,
8395 &activated,
8396 1,
8397 )?;
8398 let root_down = if owner == 0 {
8399 down
8400 } else {
8401 let _main = root.gpu.enter_main()?;
8402 let mut root_down = root.uninit(experts.input_width)?;
8403 root.stream().memcpy_dtod(&down, &mut root_down)?;
8404 remote_down_keepalive.push(down);
8408 root_down
8409 };
8410 let _main = root.gpu.enter_main()?;
8411 let mut destination = root_output.slice_mut(0..experts.input_width);
8412 root.axpy_host_into(
8413 &root_down.slice(0..root_down.len()),
8414 route_weights[pair],
8415 &mut destination,
8416 experts.input_width,
8417 )?;
8418 }
8419
8420 let _main = root.gpu.enter_main()?;
8421 let root_output = root.dtoh(&root_output)?;
8422 drop(remote_down_keepalive);
8423 output.extend(root_output);
8424 }
8425 Ok(output)
8426 }
8427}
8428
8429#[allow(clippy::manual_is_multiple_of)] fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8431 if matrix.out_features % tp != 0 {
8432 return Err(format!(
8433 "column-parallel out_features {} is not divisible by TP={tp}",
8434 matrix.out_features
8435 ));
8436 }
8437 let local_out = matrix.out_features / tp;
8438 if !local_out.is_multiple_of(FP8_BLOCK) {
8439 return Err(format!(
8440 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
8441 E4M3 scale block"
8442 ));
8443 }
8444 Ok(())
8445}
8446
8447#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
8449 if !matches!(tp, 1 | 2 | 4 | 8) {
8450 return Err(format!(
8451 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8452 ));
8453 }
8454 if out_features == 0 || !out_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8455 return Err(format!(
8456 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
8457 ));
8458 }
8459 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
8460 let local_out = out_features / tp;
8461 if local_out % canonical_rows != 0 {
8462 return Err(format!(
8463 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
8464 {canonical_rows}-row chunks"
8465 ));
8466 }
8467 Ok(canonical_rows)
8468}
8469
8470#[allow(clippy::manual_is_multiple_of)] fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
8472 if !matches!(tp, 1 | 2 | 4 | 8) {
8473 return Err(format!(
8474 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
8475 ));
8476 }
8477 if in_features == 0 || !in_features.is_multiple_of(PRODUCT_MAX_CARDS) {
8478 return Err(format!(
8479 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
8480 ));
8481 }
8482 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
8483 let local_in = in_features / tp;
8484 if local_in % canonical_cols != 0 {
8485 return Err(format!(
8486 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
8487 {canonical_cols}-column chunks"
8488 ));
8489 }
8490 Ok(canonical_cols)
8491}
8492
8493#[allow(clippy::manual_is_multiple_of)] fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
8495 if matrix.in_features % tp != 0 {
8496 return Err(format!(
8497 "row-parallel in_features {} is not divisible by TP={tp}",
8498 matrix.in_features
8499 ));
8500 }
8501 let local_in = matrix.in_features / tp;
8502 if !local_in.is_multiple_of(FP8_BLOCK) {
8503 return Err(format!(
8504 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
8505 E4M3 scale block"
8506 ));
8507 }
8508 Ok(())
8509}
8510
8511fn upload_rank(
8512 engine: &Engine,
8513 matrix: E4m3BlockMatrix<'_>,
8514) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
8515 let _main = engine.gpu.enter_main()?;
8516 matrix.validate()?;
8517 Ok(ResidentE4m3Rank {
8518 codes: engine.htod_bytes(matrix.codes)?,
8519 scales: engine.htod(matrix.scales)?,
8520 out_features: matrix.out_features,
8521 in_features: matrix.in_features,
8522 })
8523}
8524
8525fn upload_bf16_rank(
8526 engine: &Engine,
8527 matrix: Bf16Matrix<'_>,
8528 f32_mirror: bool,
8529) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
8530 let _main = engine.gpu.enter_main()?;
8531 matrix.validate()?;
8532 let bytes = engine.htod_bytes(matrix.bytes)?;
8533 let weight = if f32_mirror {
8534 let values = matrix
8535 .out_features
8536 .checked_mul(matrix.in_features)
8537 .ok_or("resident BF16 mirror element count overflow")?;
8538 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
8539 } else {
8540 ResidentBf16Weight::Bf16(bytes)
8541 };
8542 let q8 = if crate::step_tp_w8_on() && matrix.in_features.is_multiple_of(32) {
8546 if let ResidentBf16Weight::Bf16(bytes) = &weight {
8547 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
8554 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
8555 engine.encode_q8_0_from_bf16(
8556 bytes,
8557 &mut interleaved,
8558 matrix.in_features,
8559 matrix.out_features,
8560 )?;
8561 let mirror =
8562 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
8563 Some(mirror)
8564 } else {
8565 None
8566 }
8567 } else {
8568 None
8569 };
8570 Ok(ResidentBf16Rank {
8571 weight,
8572 out_features: matrix.out_features,
8573 in_features: matrix.in_features,
8574 q8,
8575 })
8576}
8577
8578fn upload_expert_bank_rank(
8579 engine: &Engine,
8580 bank: E4m3ExpertBank<'_>,
8581 expert_range: Range<usize>,
8582) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8583 let _main = engine.gpu.enter_main()?;
8584 bank.validate()?;
8585 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
8586 return Err(format!(
8587 "invalid EP expert range {expert_range:?} for {} experts",
8588 bank.expert_count
8589 )
8590 .into());
8591 }
8592 let code_stride = bank.out_features * bank.in_features;
8593 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
8594 Ok(ResidentE4m3ExpertBankRank {
8595 codes: engine.htod_bytes(
8596 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
8597 )?,
8598 scales: engine.htod(
8599 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
8600 )?,
8601 expert_range,
8602 out_features: bank.out_features,
8603 in_features: bank.in_features,
8604 code_stride,
8605 scale_stride,
8606 k_blocks: None,
8607 })
8608}
8609
8610#[allow(clippy::manual_is_multiple_of)] fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8612 if bank.out_features % tp != 0 {
8613 return Err(format!(
8614 "TP expert output width {} is not divisible by TP={tp}",
8615 bank.out_features
8616 ));
8617 }
8618 let local_out = bank.out_features / tp;
8619 if !local_out.is_multiple_of(FP8_BLOCK) {
8620 return Err(format!(
8621 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
8622 ));
8623 }
8624 Ok(())
8625}
8626
8627#[allow(clippy::manual_is_multiple_of)] fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
8629 if bank.in_features % tp != 0 {
8630 return Err(format!(
8631 "TP expert input width {} is not divisible by TP={tp}",
8632 bank.in_features
8633 ));
8634 }
8635 let local_in = bank.in_features / tp;
8636 if !local_in.is_multiple_of(FP8_BLOCK) {
8637 return Err(format!(
8638 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
8639 ));
8640 }
8641 Ok(())
8642}
8643
8644fn upload_column_bank_rank(
8645 engine: &Engine,
8646 bank: E4m3ExpertBank<'_>,
8647 tp: usize,
8648 rank: usize,
8649) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8650 let _main = engine.gpu.enter_main()?;
8651 let packed = pack_column_bank_rank(bank, tp, rank)?;
8652 Ok(ResidentE4m3ExpertBankRank {
8653 codes: engine.htod_bytes(&packed.codes)?,
8654 scales: engine.htod(&packed.scales)?,
8655 expert_range: packed.expert_range,
8656 out_features: packed.out_features,
8657 in_features: packed.in_features,
8658 code_stride: packed.code_stride,
8659 scale_stride: packed.scale_stride,
8660 k_blocks: packed.k_blocks,
8661 })
8662}
8663
8664fn pack_column_bank_rank(
8665 bank: E4m3ExpertBank<'_>,
8666 tp: usize,
8667 rank: usize,
8668) -> Result<PackedE4m3ExpertBankRank, String> {
8669 bank.validate()?;
8670 validate_column_bank_shape(bank, tp)?;
8671 if rank >= tp {
8672 return Err(format!("TP rank {rank} outside 0..{tp}"));
8673 }
8674 let local_out = bank.out_features / tp;
8675 let full_code_stride = bank.out_features * bank.in_features;
8676 let local_code_stride = local_out * bank.in_features;
8677 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8678 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8679 let local_scale_rows = local_out / FP8_BLOCK;
8680 let local_scale_stride = local_scale_rows * scale_cols;
8681 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8682 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8683 let row_start = rank * local_out;
8684 let scale_row_start = rank * local_scale_rows;
8685 for expert in 0..bank.expert_count {
8686 let code_start = expert * full_code_stride + row_start * bank.in_features;
8687 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8688 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8689 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8690 }
8691 Ok(PackedE4m3ExpertBankRank {
8692 codes,
8693 scales,
8694 expert_range: 0..bank.expert_count,
8695 out_features: local_out,
8696 in_features: bank.in_features,
8697 code_stride: local_code_stride,
8698 scale_stride: local_scale_stride,
8699 k_blocks: None,
8700 })
8701}
8702
8703fn upload_row_bank_rank(
8704 engine: &Engine,
8705 bank: E4m3ExpertBank<'_>,
8706 tp: usize,
8707 rank: usize,
8708) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8709 let _main = engine.gpu.enter_main()?;
8710 let packed = pack_row_bank_rank(bank, tp, rank)?;
8711 Ok(ResidentE4m3ExpertBankRank {
8712 codes: engine.htod_bytes(&packed.codes)?,
8713 scales: engine.htod(&packed.scales)?,
8714 expert_range: packed.expert_range,
8715 out_features: packed.out_features,
8716 in_features: packed.in_features,
8717 code_stride: packed.code_stride,
8718 scale_stride: packed.scale_stride,
8719 k_blocks: packed.k_blocks,
8720 })
8721}
8722
8723fn pack_row_bank_rank(
8724 bank: E4m3ExpertBank<'_>,
8725 tp: usize,
8726 rank: usize,
8727) -> Result<PackedE4m3ExpertBankRank, String> {
8728 bank.validate()?;
8729 validate_row_bank_shape(bank, tp)?;
8730 if rank >= tp {
8731 return Err(format!("TP rank {rank} outside 0..{tp}"));
8732 }
8733 let local_in = bank.in_features / tp;
8734 let full_code_stride = bank.out_features * bank.in_features;
8735 let local_code_stride = bank.out_features * local_in;
8736 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8737 let local_scale_cols = local_in / FP8_BLOCK;
8738 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8739 let full_scale_stride = scale_rows * full_scale_cols;
8740 let local_scale_stride = scale_rows * local_scale_cols;
8741 let global_block_start = rank * local_scale_cols;
8742 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8743 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8744 for expert in 0..bank.expert_count {
8745 let expert_code_start = expert * full_code_stride;
8746 let expert_scale_start = expert * full_scale_stride;
8747 for local_block in 0..local_scale_cols {
8748 let global_block = global_block_start + local_block;
8749 let column_start = global_block * FP8_BLOCK;
8750 for row in 0..bank.out_features {
8751 let start = expert_code_start + row * bank.in_features + column_start;
8752 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8753 }
8754 for row in 0..scale_rows {
8755 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8756 }
8757 }
8758 }
8759 Ok(PackedE4m3ExpertBankRank {
8760 codes,
8761 scales,
8762 expert_range: 0..bank.expert_count,
8763 out_features: bank.out_features,
8764 in_features: local_in,
8765 code_stride: local_code_stride,
8766 scale_stride: local_scale_stride,
8767 k_blocks: Some(local_scale_cols),
8768 })
8769}
8770
8771fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8772 if engines.len() != ranks.len() {
8773 return Err(format!(
8774 "resident TP rank count {} != runtime rank count {}",
8775 ranks.len(),
8776 engines.len()
8777 ));
8778 }
8779 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8780 let device = engine.ctx().ordinal();
8781 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8782 return Err(format!(
8783 "resident TP rank {rank} is not owned by runtime device {device}"
8784 ));
8785 }
8786 }
8787 Ok(())
8788}
8789
8790fn validate_tp_bank_residency(
8791 engines: &[Engine],
8792 experts: &ResidentTpExpertBank,
8793) -> Result<(), String> {
8794 if engines.len() != experts.gate.len()
8795 || engines.len() != experts.up.len()
8796 || engines.len() != experts.down.len()
8797 {
8798 return Err(format!(
8799 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8800 experts.gate.len(),
8801 experts.up.len(),
8802 experts.down.len(),
8803 engines.len()
8804 ));
8805 }
8806 for (rank, engine) in engines.iter().enumerate() {
8807 let device = engine.ctx().ordinal();
8808 for (projection, bank) in [
8809 ("gate", &experts.gate[rank]),
8810 ("up", &experts.up[rank]),
8811 ("down", &experts.down[rank]),
8812 ] {
8813 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8814 return Err(format!(
8815 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8816 {device}"
8817 ));
8818 }
8819 }
8820 }
8821 Ok(())
8822}
8823
8824fn validate_ep_residency(
8825 engines: &[Engine],
8826 experts: &ResidentExpertParallel,
8827) -> Result<(), String> {
8828 if engines.len() != experts.ranks.len() {
8829 return Err(format!(
8830 "resident EP rank count {} != runtime rank count {}",
8831 experts.ranks.len(),
8832 engines.len()
8833 ));
8834 }
8835 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
8836 let device = engine.ctx().ordinal();
8837 for (projection, bank) in [
8838 ("gate", &resident.gate),
8839 ("up", &resident.up),
8840 ("down", &resident.down),
8841 ] {
8842 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8843 return Err(format!(
8844 "resident EP rank {rank} {projection} bank is not owned by runtime device \
8845 {device}"
8846 ));
8847 }
8848 }
8849 }
8850 Ok(())
8851}
8852
8853fn run_rank(
8854 engine: &Engine,
8855 matrix: E4m3BlockMatrix<'_>,
8856 activations: &[f32],
8857 tokens: usize,
8858) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8859 let _main = engine.gpu.enter_main()?;
8860 let codes = engine.htod_bytes(matrix.codes)?;
8861 let scales = engine.htod(matrix.scales)?;
8862 let activations = engine.htod(activations)?;
8863 let output = engine.qmatvec_mmq_fp8_blk(
8864 &codes,
8865 &scales,
8866 &activations,
8867 tokens,
8868 matrix.in_features,
8869 matrix.out_features,
8870 )?;
8871 engine.dtoh(&output)
8872}
8873
8874fn run_resident_rank(
8875 engine: &Engine,
8876 matrix: &ResidentE4m3Rank,
8877 activations: &[f32],
8878 tokens: usize,
8879) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8880 let _main = engine.gpu.enter_main()?;
8881 let activations = engine.htod(activations)?;
8882 let output = engine.qmatvec_mmq_fp8_blk(
8883 &matrix.codes,
8884 &matrix.scales,
8885 &activations,
8886 tokens,
8887 matrix.in_features,
8888 matrix.out_features,
8889 )?;
8890 engine.dtoh(&output)
8891}
8892
8893fn run_resident_bf16_rank(
8894 engine: &Engine,
8895 matrix: &ResidentBf16Rank,
8896 activations: &[f32],
8897 tokens: usize,
8898 canonical_chunk_rows: Option<usize>,
8899) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8900 let _main = engine.gpu.enter_main()?;
8901 let activations = engine.htod(activations)?;
8902 let output = run_resident_bf16_rank_device(
8903 engine,
8904 matrix,
8905 &activations,
8906 tokens,
8907 canonical_chunk_rows,
8908 false,
8909 )?;
8910 engine.dtoh(&output)
8911}
8912
8913fn run_resident_bf16_rank_device(
8914 engine: &Engine,
8915 matrix: &ResidentBf16Rank,
8916 activations: &CudaSlice<f32>,
8917 tokens: usize,
8918 canonical_chunk_rows: Option<usize>,
8919 strided_chunk_output: bool,
8920) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8921 let _main = engine.gpu.enter_main()?;
8922 if activations.ordinal() != engine.ctx().ordinal() {
8923 return Err(format!(
8924 "resident BF16 activation device {} != rank device {}",
8925 activations.ordinal(),
8926 engine.ctx().ordinal()
8927 )
8928 .into());
8929 }
8930 if activations.len() != tokens * matrix.in_features {
8931 return Err(format!(
8932 "resident BF16 activation count {} != {tokens}x{}",
8933 activations.len(),
8934 matrix.in_features
8935 )
8936 .into());
8937 }
8938 match (&matrix.weight, canonical_chunk_rows) {
8939 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8940 .linear_bf16_resident_canonical_rows(
8941 activations,
8942 bytes,
8943 tokens,
8944 matrix.in_features,
8945 matrix.out_features,
8946 rows,
8947 ),
8948 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8949 activations,
8950 bytes,
8951 tokens,
8952 matrix.in_features,
8953 matrix.out_features,
8954 ),
8955 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8956 .linear_f32_resident_canonical_rows_strided(
8957 activations,
8958 values,
8959 tokens,
8960 matrix.in_features,
8961 matrix.out_features,
8962 rows,
8963 ),
8964 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8965 activations,
8966 values,
8967 tokens,
8968 matrix.in_features,
8969 matrix.out_features,
8970 rows,
8971 ),
8972 (ResidentBf16Weight::F32(values), None) => engine.linear(
8973 activations,
8974 values,
8975 tokens,
8976 matrix.in_features,
8977 matrix.out_features,
8978 ),
8979 }
8980}
8981
8982fn validate_resident_bf16_ranks(
8983 engines: &[Engine],
8984 ranks: &[ResidentBf16Rank],
8985) -> Result<(), String> {
8986 if engines.len() != ranks.len() {
8987 return Err(format!(
8988 "resident BF16 TP rank count {} != runtime rank count {}",
8989 ranks.len(),
8990 engines.len(),
8991 ));
8992 }
8993 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8994 let device = engine.ctx().ordinal();
8995 if matrix.weight.ordinal() != device {
8996 return Err(format!(
8997 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8998 ));
8999 }
9000 }
9001 Ok(())
9002}
9003
9004fn validate_step_bf16_row_residency(
9005 engines: &[Engine],
9006 matrix: &ResidentStepBf16RowParallel,
9007) -> Result<(), String> {
9008 if engines.len() != matrix.ranks.len() {
9009 return Err(format!(
9010 "resident Step BF16 row rank count {} != runtime rank count {}",
9011 matrix.ranks.len(),
9012 engines.len(),
9013 ));
9014 }
9015 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
9016 if matrix.canonical_chunk_cols != canonical_cols {
9017 return Err(format!(
9018 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
9019 matrix.canonical_chunk_cols
9020 ));
9021 }
9022 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
9023 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
9024 if blocks.len() != blocks_per_rank {
9025 return Err(format!(
9026 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
9027 blocks.len()
9028 ));
9029 }
9030 let device = engine.ctx().ordinal();
9031 for (block, resident) in blocks.iter().enumerate() {
9032 if resident.weight.ordinal() != device
9033 || resident.in_features != canonical_cols
9034 || resident.out_features != matrix.out_features
9035 {
9036 return Err(format!(
9037 "resident Step BF16 row rank {rank} block {block} has inconsistent \
9038 device or geometry"
9039 ));
9040 }
9041 }
9042 }
9043 Ok(())
9044}
9045
9046fn validate_replicated_device_rows(
9047 engines: &[Engine],
9048 rows: &ResidentReplicatedDeviceRows,
9049) -> Result<(), String> {
9050 let rank_lengths = rows
9051 .ranks
9052 .iter()
9053 .map(|rank_rows| rank_rows.len())
9054 .collect::<Vec<_>>();
9055 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
9056 if rows
9057 .ranks
9058 .iter()
9059 .zip(engines)
9060 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
9061 {
9062 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
9063 }
9064 Ok(())
9065}
9066
9067fn replicated_device_row_values(
9068 tokens: usize,
9069 width: usize,
9070 expected_ranks: usize,
9071 rank_lengths: &[usize],
9072) -> Result<usize, String> {
9073 let values = tokens
9074 .checked_mul(width)
9075 .ok_or("replicated device row size overflow")?;
9076 if tokens == 0
9077 || width == 0
9078 || expected_ranks == 0
9079 || rank_lengths.len() != expected_ranks
9080 || rank_lengths.iter().any(|&rank_len| rank_len != values)
9081 {
9082 return Err(format!(
9083 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
9084 tokens,
9085 width,
9086 rank_lengths.len(),
9087 expected_ranks
9088 ));
9089 }
9090 Ok(values)
9091}
9092
9093fn replicated_device_row_source_values(
9094 tokens: usize,
9095 width: usize,
9096 source_len: usize,
9097 source_device: usize,
9098 root_device: usize,
9099) -> Result<usize, String> {
9100 let values = tokens
9101 .checked_mul(width)
9102 .ok_or("replicated device row size overflow")?;
9103 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
9104 return Err(format!(
9105 "replicated device row source has inconsistent geometry/device \
9106 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
9107 ));
9108 }
9109 Ok(values)
9110}
9111
9112#[allow(clippy::manual_is_multiple_of)] fn bf16_column_shard(
9114 matrix: Bf16Matrix<'_>,
9115 tp: usize,
9116 rank: usize,
9117) -> Result<Bf16Matrix<'_>, String> {
9118 matrix.validate()?;
9119 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
9120 return Err(format!(
9121 "invalid BF16 column shard out={} TP={tp} rank={rank}",
9122 matrix.out_features
9123 ));
9124 }
9125 let local_out = matrix.out_features / tp;
9126 let row_bytes = matrix.in_features * 2;
9127 let start = rank * local_out * row_bytes;
9128 Ok(Bf16Matrix {
9129 bytes: &matrix.bytes[start..start + local_out * row_bytes],
9130 out_features: local_out,
9131 in_features: matrix.in_features,
9132 })
9133}
9134
9135#[allow(clippy::manual_is_multiple_of)] fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
9137 matrix.validate()?;
9138 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
9139 return Err(format!(
9140 "invalid BF16 row shard in={} TP={tp} rank={rank}",
9141 matrix.in_features
9142 ));
9143 }
9144 let local_in = matrix.in_features / tp;
9145 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
9146 for row in 0..matrix.out_features {
9147 let start = (row * matrix.in_features + rank * local_in) * 2;
9148 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
9149 }
9150 Ok(bytes)
9151}
9152
9153fn bf16_row_block(
9154 matrix: Bf16Matrix<'_>,
9155 col_start: usize,
9156 block_cols: usize,
9157) -> Result<Vec<u8>, String> {
9158 matrix.validate()?;
9159 let col_end = col_start
9160 .checked_add(block_cols)
9161 .ok_or("BF16 row block column overflow")?;
9162 if block_cols == 0 || col_end > matrix.in_features {
9163 return Err(format!(
9164 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
9165 matrix.in_features
9166 ));
9167 }
9168 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
9169 for row in 0..matrix.out_features {
9170 let start = (row * matrix.in_features + col_start) * 2;
9171 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
9172 }
9173 Ok(bytes)
9174}
9175
9176fn run_resident_bank_expert(
9177 engine: &Engine,
9178 bank: &ResidentE4m3ExpertBankRank,
9179 local_expert: usize,
9180 activations: &[f32],
9181 tokens: usize,
9182) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9183 let _main = engine.gpu.enter_main()?;
9184 if bank.k_blocks.is_some() {
9185 return Err("block-major TP row bank requires canonical block execution".into());
9186 }
9187 let local_count = bank.expert_range.end - bank.expert_range.start;
9188 if local_expert >= local_count {
9189 return Err(format!(
9190 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
9191 bank.expert_range
9192 )
9193 .into());
9194 }
9195 validate_activations(activations, tokens, bank.in_features)?;
9196 let activations = engine.htod(activations)?;
9197 let weight = bank
9198 .codes
9199 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9200 let scales = bank
9201 .scales
9202 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9203 let input = activations.slice(0..activations.len());
9204 let output = engine.qmatvec_mmq_fp8_blk_view(
9205 &weight,
9206 &scales,
9207 &input,
9208 tokens,
9209 bank.in_features,
9210 bank.out_features,
9211 )?;
9212 engine.dtoh(&output)
9213}
9214
9215fn run_resident_bank_expert_block(
9216 engine: &Engine,
9217 bank: &ResidentE4m3ExpertBankRank,
9218 local_expert: usize,
9219 block: usize,
9220 activations: &[f32],
9221) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9222 let _main = engine.gpu.enter_main()?;
9223 let local_count = bank.expert_range.end - bank.expert_range.start;
9224 if local_expert >= local_count {
9225 return Err(format!(
9226 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9227 bank.expert_range
9228 )
9229 .into());
9230 }
9231 let blocks = bank
9232 .k_blocks
9233 .ok_or("TP row bank is not packed in native K-block order")?;
9234 if block >= blocks {
9235 return Err(format!("TP row block {block} outside 0..{blocks}").into());
9236 }
9237 validate_activations(activations, 1, FP8_BLOCK)?;
9238 let block_code_stride = bank.out_features * FP8_BLOCK;
9239 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9240 if bank.in_features != blocks * FP8_BLOCK
9241 || bank.code_stride != blocks * block_code_stride
9242 || bank.scale_stride != blocks * block_scale_stride
9243 {
9244 return Err("TP row bank block-major geometry is inconsistent".into());
9245 }
9246
9247 let expert_code_start = local_expert * bank.code_stride;
9248 let expert_scale_start = local_expert * bank.scale_stride;
9249 let weight = bank.codes.slice(
9250 expert_code_start + block * block_code_stride
9251 ..expert_code_start + (block + 1) * block_code_stride,
9252 );
9253 let scales = bank.scales.slice(
9254 expert_scale_start + block * block_scale_stride
9255 ..expert_scale_start + (block + 1) * block_scale_stride,
9256 );
9257 let activations = engine.htod(activations)?;
9258 let input = activations.slice(0..activations.len());
9259 let output = engine.qmatvec_mmq_fp8_blk_view(
9260 &weight,
9261 &scales,
9262 &input,
9263 1,
9264 FP8_BLOCK,
9265 bank.out_features,
9266 )?;
9267 engine.dtoh(&output)
9268}
9269
9270fn run_resident_bank_expert_device(
9271 engine: &Engine,
9272 bank: &ResidentE4m3ExpertBankRank,
9273 local_expert: usize,
9274 activations: &CudaSlice<f32>,
9275 tokens: usize,
9276) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9277 let _main = engine.gpu.enter_main()?;
9278 if bank.k_blocks.is_some() {
9279 return Err("block-major TP row bank requires canonical block execution".into());
9280 }
9281 let local_count = bank.expert_range.end - bank.expert_range.start;
9282 if local_expert >= local_count {
9283 return Err(format!(
9284 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9285 bank.expert_range
9286 )
9287 .into());
9288 }
9289 let expected = tokens
9290 .checked_mul(bank.in_features)
9291 .ok_or("native TP activation size overflow")?;
9292 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
9293 return Err(format!(
9294 "native TP activation len/device {}/{} != expected {expected}/{}",
9295 activations.len(),
9296 activations.ordinal(),
9297 engine.ctx().ordinal()
9298 )
9299 .into());
9300 }
9301 let weight = bank
9302 .codes
9303 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
9304 let scales = bank
9305 .scales
9306 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
9307 let input = activations.slice(0..activations.len());
9308 engine.qmatvec_mmq_fp8_blk_view(
9309 &weight,
9310 &scales,
9311 &input,
9312 tokens,
9313 bank.in_features,
9314 bank.out_features,
9315 )
9316}
9317
9318fn run_resident_bank_expert_block_device(
9319 engine: &Engine,
9320 bank: &ResidentE4m3ExpertBankRank,
9321 local_expert: usize,
9322 block: usize,
9323 activations: &cudarc::driver::CudaView<'_, f32>,
9324) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9325 let _main = engine.gpu.enter_main()?;
9326 let local_count = bank.expert_range.end - bank.expert_range.start;
9327 if local_expert >= local_count {
9328 return Err(format!(
9329 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
9330 bank.expert_range
9331 )
9332 .into());
9333 }
9334 let blocks = bank
9335 .k_blocks
9336 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
9337 if block >= blocks {
9338 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
9339 }
9340 let activation_device = activations.stream().context().ordinal();
9341 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
9342 return Err(format!(
9343 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
9344 activations.len(),
9345 activation_device,
9346 engine.ctx().ordinal()
9347 )
9348 .into());
9349 }
9350 let block_code_stride = bank.out_features * FP8_BLOCK;
9351 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
9352 if bank.in_features != blocks * FP8_BLOCK
9353 || bank.code_stride != blocks * block_code_stride
9354 || bank.scale_stride != blocks * block_scale_stride
9355 {
9356 return Err("native TP row bank block-major geometry is inconsistent".into());
9357 }
9358 let expert_code_start = local_expert * bank.code_stride;
9359 let expert_scale_start = local_expert * bank.scale_stride;
9360 let weight = bank.codes.slice(
9361 expert_code_start + block * block_code_stride
9362 ..expert_code_start + (block + 1) * block_code_stride,
9363 );
9364 let scales = bank.scales.slice(
9365 expert_scale_start + block * block_scale_stride
9366 ..expert_scale_start + (block + 1) * block_scale_stride,
9367 );
9368 engine.qmatvec_mmq_fp8_blk_view(
9369 &weight,
9370 &scales,
9371 activations,
9372 1,
9373 FP8_BLOCK,
9374 bank.out_features,
9375 )
9376}
9377
9378pub(crate) fn grant_peer_access(
9397 accessor: &Engine,
9398 owner: &Engine,
9399 label: &str,
9400) -> Result<(), Box<dyn std::error::Error>> {
9401 let (a_dev, o_dev) = (accessor.ctx().ordinal(), owner.ctx().ordinal());
9402 let mut can_access = 0;
9403 unsafe {
9404 cudarc::driver::sys::cuDeviceCanAccessPeer(
9405 &mut can_access,
9406 accessor.ctx().cu_device(),
9407 owner.ctx().cu_device(),
9408 )
9409 .result()?;
9410 }
9411 if can_access == 0 {
9412 return Err(
9413 format!("{label} requires P2P, but dev{a_dev} cannot access dev{o_dev}").into(),
9414 );
9415 }
9416 accessor.ctx().bind_to_thread()?;
9417 let rc = unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(owner.ctx().cu_ctx(), 0) };
9418 use cudarc::driver::sys::cudaError_enum as E;
9419 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
9420 return Err(format!(
9421 "{label} cuCtxEnablePeerAccess(dev{a_dev} -> dev{o_dev}) failed: {rc:?}"
9422 )
9423 .into());
9424 }
9425 let device = cudarc::driver::result::device::get(o_dev as i32)?;
9426 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
9427 unsafe {
9428 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
9429 }
9430 let desc = cudarc::driver::sys::CUmemAccessDesc {
9431 location: cudarc::driver::sys::CUmemLocation {
9432 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
9433 id: a_dev as i32,
9434 },
9435 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
9436 };
9437 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
9438 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
9439 return Err(format!(
9440 "{label} cuMemPoolSetAccess(dev{o_dev} pool -> dev{a_dev}) failed: {rc:?}"
9441 )
9442 .into());
9443 }
9444 Ok(())
9445}
9446
9447fn configure_native_p2p(
9448 ranks: &[Engine],
9449 devices: &[usize],
9450) -> Result<(), Box<dyn std::error::Error>> {
9451 if ranks.len() != devices.len() || ranks.len() < 2 {
9452 return Err("native TP P2P setup requires matching multi-rank devices".into());
9453 }
9454 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
9455 if engine.ctx().ordinal() != device {
9456 return Err(format!(
9457 "native TP rank {rank} context device {} != requested device {device}",
9458 engine.ctx().ordinal()
9459 )
9460 .into());
9461 }
9462 }
9463
9464 for src in 0..ranks.len() {
9465 for dst in 0..ranks.len() {
9466 if src == dst {
9467 continue;
9468 }
9469 grant_peer_access(&ranks[src], &ranks[dst], "native TP")?;
9470 }
9471 }
9472
9473 for src in 0..ranks.len() {
9474 for dst in 0..ranks.len() {
9475 if src == dst {
9476 continue;
9477 }
9478 for &words in NATIVE_P2P_PROBE_WORDS {
9479 let expected = (0..words)
9480 .map(|index| {
9481 (index as u32)
9482 .wrapping_mul(0x9e37_79b9)
9483 .wrapping_add(((src as u32) << 16) | dst as u32)
9484 })
9485 .collect::<Vec<_>>();
9486 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
9487 let source = ranks[src].htod_u32_v(&expected)?;
9488 let mut destination = ranks[dst].htod_u32_v(&poison)?;
9489 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
9490 let actual = ranks[dst].dtoh_u32(&destination)?;
9491 if actual != expected {
9492 let mismatches = actual
9493 .iter()
9494 .zip(&expected)
9495 .filter(|(actual, expected)| actual != expected)
9496 .count();
9497 return Err(format!(
9498 "native TP peer probe dev{}->dev{} failed at {} bytes: \
9499 {mismatches}/{} words differ",
9500 devices[src],
9501 devices[dst],
9502 words * std::mem::size_of::<u32>(),
9503 expected.len()
9504 )
9505 .into());
9506 }
9507 }
9508 }
9509 }
9510 ranks[0].ctx().bind_to_thread()?;
9511 eprintln!(
9512 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
9513 directions={} byte_ladder={:?} mismatches=0",
9514 ranks.len() * (ranks.len() - 1),
9515 NATIVE_P2P_PROBE_WORDS
9516 .iter()
9517 .map(|words| words * std::mem::size_of::<u32>())
9518 .collect::<Vec<_>>(),
9519 );
9520 Ok(())
9521}
9522
9523fn validate_activations(
9524 activations: &[f32],
9525 tokens: usize,
9526 in_features: usize,
9527) -> Result<(), String> {
9528 let expected = tokens
9529 .checked_mul(in_features)
9530 .ok_or_else(|| "activation size overflow".to_string())?;
9531 if activations.len() != expected {
9532 return Err(format!(
9533 "activation count {} != {tokens}x{in_features} ({expected})",
9534 activations.len()
9535 ));
9536 }
9537 if !activations.iter().all(|value| value.is_finite()) {
9538 return Err("activations contain a non-finite value".to_string());
9539 }
9540 Ok(())
9541}
9542
9543fn column_shard(
9544 matrix: E4m3BlockMatrix<'_>,
9545 tp: usize,
9546 rank: usize,
9547) -> Result<E4m3BlockMatrix<'_>, String> {
9548 let local_out = matrix.out_features / tp;
9549 let row_start = rank * local_out;
9550 let code_start = row_start * matrix.in_features;
9551 let code_end = code_start + local_out * matrix.in_features;
9552 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9553 let local_scale_rows = local_out / FP8_BLOCK;
9554 let scale_start = rank * local_scale_rows * scale_cols;
9555 let scale_end = scale_start + local_scale_rows * scale_cols;
9556 Ok(E4m3BlockMatrix {
9557 codes: &matrix.codes[code_start..code_end],
9558 scales: &matrix.scales[scale_start..scale_end],
9559 out_features: local_out,
9560 in_features: matrix.in_features,
9561 })
9562}
9563
9564fn row_shard(
9565 matrix: E4m3BlockMatrix<'_>,
9566 tp: usize,
9567 rank: usize,
9568) -> Result<(Vec<u8>, Vec<f32>), String> {
9569 let local_in = matrix.in_features / tp;
9570 let col_start = rank * local_in;
9571 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
9572 for row in 0..matrix.out_features {
9573 let start = row * matrix.in_features + col_start;
9574 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
9575 }
9576
9577 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
9578 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
9579 let local_scale_cols = local_in / FP8_BLOCK;
9580 let scale_col_start = rank * local_scale_cols;
9581 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
9582 for row in 0..scale_rows {
9583 let start = row * scale_cols + scale_col_start;
9584 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
9585 }
9586 Ok((codes, scales))
9587}
9588
9589fn activation_shard(
9590 activations: &[f32],
9591 tokens: usize,
9592 in_features: usize,
9593 tp: usize,
9594 rank: usize,
9595) -> Vec<f32> {
9596 let local_in = in_features / tp;
9597 let col_start = rank * local_in;
9598 let mut shard = Vec::with_capacity(tokens * local_in);
9599 for token in 0..tokens {
9600 let start = token * in_features + col_start;
9601 shard.extend_from_slice(&activations[start..start + local_in]);
9602 }
9603 shard
9604}
9605
9606#[derive(Clone, Copy)]
9626pub struct Nvfp4BlockMatrix<'a> {
9627 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
9631 pub in_features: usize,
9632}
9633
9634impl Nvfp4BlockMatrix<'_> {
9635 pub fn validate(&self) -> Result<(), String> {
9636 if self.in_features == 0 || self.out_features == 0 {
9637 return Err("NVFP4 matrix has a zero dimension".to_string());
9638 }
9639 if !self.in_features.is_multiple_of(64) {
9640 return Err(format!(
9641 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
9642 self.in_features
9643 ));
9644 }
9645 if self.codes.len() != self.out_features * self.in_features / 2 {
9646 return Err(format!(
9647 "NVFP4 code bytes {} != {}x{}/2",
9648 self.codes.len(),
9649 self.out_features,
9650 self.in_features
9651 ));
9652 }
9653 if self.scales.len() != self.out_features * self.in_features / 16 {
9654 return Err(format!(
9655 "NVFP4 scale bytes {} != {}x{}/16",
9656 self.scales.len(),
9657 self.out_features,
9658 self.in_features
9659 ));
9660 }
9661 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9662 return Err(format!(
9663 "NVFP4 macro scale {} is not finite-positive",
9664 self.macro_scale
9665 ));
9666 }
9667 Ok(())
9668 }
9669}
9670
9671#[derive(Clone, Copy)]
9673pub struct Nvfp4ExpertBank<'a> {
9674 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9678 pub out_features: usize,
9679 pub in_features: usize,
9680}
9681
9682impl Nvfp4ExpertBank<'_> {
9683 pub fn validate(&self) -> Result<(), String> {
9684 if self.expert_count == 0 {
9685 return Err("NVFP4 expert bank is empty".to_string());
9686 }
9687 if self.macros.len() != self.expert_count {
9688 return Err(format!(
9689 "NVFP4 bank macros {} != expert count {}",
9690 self.macros.len(),
9691 self.expert_count
9692 ));
9693 }
9694 self.expert(0).map(|_| ())
9695 }
9696
9697 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9698 if expert >= self.expert_count {
9699 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9700 }
9701 let code_stride = self.out_features * self.in_features / 2;
9702 let scale_stride = self.out_features * self.in_features / 16;
9703 if self.codes.len() != self.expert_count * code_stride
9704 || self.scales.len() != self.expert_count * scale_stride
9705 {
9706 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9707 }
9708 let matrix = Nvfp4BlockMatrix {
9709 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9710 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9711 macro_scale: self.macros[expert],
9712 out_features: self.out_features,
9713 in_features: self.in_features,
9714 };
9715 matrix.validate()?;
9716 Ok(matrix)
9717 }
9718}
9719
9720pub struct ResidentNvfp4Rank {
9722 blocks: crate::CudaSlice<u8>,
9723 macro_scale: f32,
9724 out_features: usize,
9725 in_features: usize,
9726 row_bytes: usize,
9727}
9728
9729pub struct ResidentNvfp4ColumnParallel {
9730 ranks: Vec<ResidentNvfp4Rank>,
9731 pub out_features: usize,
9732 pub in_features: usize,
9733}
9734
9735pub struct ResidentNvfp4RowParallel {
9736 ranks: Vec<ResidentNvfp4Rank>,
9737 pub out_features: usize,
9738 pub in_features: usize,
9739}
9740
9741pub struct ResidentTpNvfp4Expert {
9742 gate: ResidentNvfp4ColumnParallel,
9743 up: ResidentNvfp4ColumnParallel,
9744 down: ResidentNvfp4RowParallel,
9745 pub input_width: usize,
9746 pub expert_width: usize,
9747}
9748
9749pub struct ResidentNvfp4ColumnBankRank {
9753 bank: crate::CudaSlice<u8>,
9757 expert_bytes: usize,
9758 local_out: usize,
9759 in_features: usize,
9760 row_bytes: usize,
9761 slot_major: bool,
9768}
9769
9770impl ResidentNvfp4ColumnBankRank {
9771 fn host_canonical_expert(
9776 &self,
9777 engine: &Engine,
9778 expert: usize,
9779 activations: &crate::CudaSlice<f32>,
9780 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9781 let w = self.expert(expert);
9782 if self.slot_major {
9783 engine.qmatvec_nvfp4_fast_v2(
9784 &w,
9785 activations,
9786 1,
9787 self.in_features,
9788 self.local_out,
9789 self.row_bytes,
9790 )
9791 } else {
9792 engine.qmatvec_nvfp4_fast(
9793 &w,
9794 activations,
9795 1,
9796 self.in_features,
9797 self.local_out,
9798 self.row_bytes,
9799 )
9800 }
9801 }
9802
9803 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9804 self.bank
9805 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9806 }
9807}
9808
9809pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9815
9816pub struct ResidentNvfp4RowBankRank {
9817 bank: crate::CudaSlice<u8>,
9819 expert_bytes: usize,
9820 device_rank: usize, out_features: usize,
9822 local_in: usize,
9823 row_bytes: usize,
9824 slot_major: bool,
9826}
9827
9828impl ResidentNvfp4RowBankRank {
9829 fn host_canonical_expert(
9832 &self,
9833 engine: &Engine,
9834 expert: usize,
9835 activations: &crate::CudaSlice<f32>,
9836 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9837 let w = self.expert(expert);
9838 if self.slot_major {
9839 engine.qmatvec_nvfp4_fast_v2(
9840 &w,
9841 activations,
9842 1,
9843 self.local_in,
9844 self.out_features,
9845 self.row_bytes,
9846 )
9847 } else {
9848 engine.qmatvec_nvfp4_fast(
9849 &w,
9850 activations,
9851 1,
9852 self.local_in,
9853 self.out_features,
9854 self.row_bytes,
9855 )
9856 }
9857 }
9858
9859 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9860 self.bank
9861 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9862 }
9863}
9864
9865impl ResidentNvfp4TensorParallel {
9866 pub(crate) fn device_workspace_handle(
9867 &self,
9868 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
9869 &self.device_workspace
9870 }
9871}
9872
9873pub struct ResidentNvfp4TensorParallel {
9874 gate: Vec<ResidentNvfp4ColumnBankRank>,
9875 up: Vec<ResidentNvfp4ColumnBankRank>,
9876 down: Vec<ResidentNvfp4RowBankRank>,
9877 macros_gate: Vec<f32>,
9878 macros_up: Vec<f32>,
9879 macros_down: Vec<f32>,
9880 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
9884 macros_up_dev: Vec<crate::CudaSlice<f32>>,
9885 macros_down_dev: Vec<crate::CudaSlice<f32>>,
9886 pub expert_count: usize,
9887 pub input_width: usize,
9888 pub expert_width: usize,
9889 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
9892 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
9896}
9897
9898struct RoutesGraph {
9905 exec: cudarc::driver::sys::CUgraphExec,
9906 parent: cudarc::driver::sys::CUgraph,
9907 _children: Vec<cudarc::driver::CudaGraph>,
9908}
9909unsafe impl Send for RoutesGraph {}
9912
9913impl Drop for RoutesGraph {
9914 fn drop(&mut self) {
9915 unsafe {
9916 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
9917 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
9918 }
9919 }
9920}
9921
9922impl Nvfp4DeviceRoutesWorkspace {
9923 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
9924 self.in_stage_e.as_ref()
9925 }
9926 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9927 self.in_stage_e.as_mut()
9928 }
9929 #[allow(dead_code)] pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9931 self.out_stage_e.as_mut()
9932 }
9933 pub(crate) fn arm_stages(
9935 &mut self,
9936 e: &Engine,
9937 width: usize,
9938 n_sel: usize,
9939 ) -> Result<(), Box<dyn std::error::Error>> {
9940 let _main = e.gpu.enter_main()?;
9941 if self.in_stage_e.is_none() {
9942 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9943 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9944 }
9945 if self.dev_route_e.is_none() {
9946 self.dev_route_e = Some((
9947 e.htod_i32(&vec![0i32; n_sel])?,
9948 e.htod(&vec![0.0f32; n_sel])?,
9949 ));
9950 }
9951 Ok(())
9952 }
9953
9954 pub(crate) fn in_and_out_stages_mut(
9956 &mut self,
9957 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9958 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9959 (Some(input), Some(output)) => Some((input, output)),
9960 _ => None,
9961 }
9962 }
9963 pub(crate) fn dev_route_e_mut(
9964 &mut self,
9965 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9966 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9967 }
9968}
9969
9970pub struct Nvfp4DeviceRoutesWorkspace {
9971 gate_out: Vec<crate::CudaSlice<f32>>,
9974 up_out: Vec<crate::CudaSlice<f32>>,
9975 act_q: Vec<crate::CudaSlice<i8>>,
9976 act_d: Vec<crate::CudaSlice<f32>>,
9977 sel: Vec<crate::CudaSlice<i32>>,
9978 partial: Vec<crate::CudaSlice<f32>>,
9979 accumulator: Vec<crate::CudaSlice<f32>>,
9980 combine_w: Vec<crate::CudaSlice<f32>>,
9982 route_w: Vec<crate::CudaSlice<f32>>,
9985 in_q: Vec<crate::CudaSlice<i8>>,
9988 in_d: Vec<crate::CudaSlice<f32>>,
9989 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9993 prestaged: bool,
9996 rank1_routed: bool,
9999 fence_flags_raw: u64,
10003 fence_ticket: u32,
10004 ev_input: Option<(CudaEvent, usize)>,
10006 in_stage_e: Option<crate::CudaSlice<f32>>,
10009 out_stage_e: Option<crate::CudaSlice<f32>>,
10010 routes_graph: Option<RoutesGraph>,
10011 raw_dev_route_e: Option<(u64, u64)>,
10013 raw_combine: Option<(u64, u64, u64, u64)>,
10014 raw_input: Vec<u64>,
10015 raw_sel: Vec<u64>,
10016 raw_route_w: Vec<u64>,
10017 remote: crate::CudaSlice<f32>,
10018 combined: crate::CudaSlice<f32>,
10019 n_sel: usize,
10020 input: Vec<crate::CudaSlice<f32>>,
10024 ev_rank: Vec<CudaEvent>,
10025 ev_done: Option<CudaEvent>,
10026 ev_entry: Option<(CudaEvent, usize)>,
10027}
10028
10029struct ResidentNvfp4EpRank {
10031 gate: crate::CudaSlice<u8>,
10032 up: crate::CudaSlice<u8>,
10033 down: crate::CudaSlice<u8>,
10034 gate_expert_bytes: usize,
10035 down_expert_bytes: usize,
10036 macros_gate: crate::CudaSlice<f32>,
10037 macros_up: crate::CudaSlice<f32>,
10038 macros_down: crate::CudaSlice<f32>,
10039 expert_range: Range<usize>,
10040}
10041
10042struct Nvfp4EpDeviceWorkspace {
10043 input: Vec<crate::CudaSlice<f32>>,
10044 input_bf16: Vec<crate::CudaSlice<u8>>,
10045 input_q8: Vec<crate::CudaSlice<i8>>,
10046 input_q8_scales: Vec<crate::CudaSlice<f32>>,
10047 sel: Vec<crate::CudaSlice<i32>>,
10048 token_rows: Vec<crate::CudaSlice<i32>>,
10049 global_pairs: Vec<crate::CudaSlice<i32>>,
10050 route_w: Vec<crate::CudaSlice<f32>>,
10051 gate_out: Vec<crate::CudaSlice<f32>>,
10052 up_out: Vec<crate::CudaSlice<f32>>,
10053 activation_bf16: Vec<crate::CudaSlice<u8>>,
10054 activation_q8: Vec<crate::CudaSlice<i8>>,
10055 activation_q8_scales: Vec<crate::CudaSlice<f32>>,
10056 slot_rows: crate::CudaSlice<f32>,
10057 slot_rows_raw: u64,
10058 route_weights: crate::CudaSlice<f32>,
10059 ev_entry: CudaEvent,
10060 ev_entry_device: usize,
10061 ev_rank: Vec<CudaEvent>,
10062 phase_events: Option<Nvfp4EpPhaseEvents>,
10063 capacity_tokens: usize,
10064 experts_per_token: usize,
10065}
10066
10067struct Nvfp4EpPhaseEvents {
10068 head: Vec<CudaEvent>,
10069 copy_done: Vec<CudaEvent>,
10070 gate_up_done: Vec<CudaEvent>,
10071 activation_done: Vec<CudaEvent>,
10072 down_done: Vec<CudaEvent>,
10073}
10074
10075pub(crate) const NVFP4_EP_DEVICE_BATCH_CAP: usize = 128;
10076pub(crate) const NVFP4_EP_DEVICE_ROUTER_BATCH_CAP: usize = 32;
10077pub(crate) const NVFP4_EP_Q8_BATCH_CAP: usize = 32;
10078
10079fn nvfp4_ep_active_input_values(
10080 input_values: usize,
10081 tokens: usize,
10082 input_width: usize,
10083) -> Result<usize, String> {
10084 if !(1..=NVFP4_EP_DEVICE_BATCH_CAP).contains(&tokens) {
10085 return Err(format!(
10086 "W4A16 NVFP4 device EP batch {tokens} is outside 1..={NVFP4_EP_DEVICE_BATCH_CAP}"
10087 ));
10088 }
10089 let active_values = tokens
10090 .checked_mul(input_width)
10091 .ok_or("W4A16 NVFP4 device EP active input size overflows usize")?;
10092 if input_values < active_values {
10093 return Err(format!(
10094 "W4A16 NVFP4 device EP input {input_values} is smaller than active \
10095 tokens {tokens} x width {input_width} ({active_values})"
10096 ));
10097 }
10098 Ok(active_values)
10099}
10100
10101pub struct ResidentNvfp4ExpertParallel {
10102 ranks: Vec<ResidentNvfp4EpRank>,
10103 macros_gate: Vec<f32>,
10104 macros_up: Vec<f32>,
10105 macros_down: Vec<f32>,
10106 pub expert_count: usize,
10107 pub input_width: usize,
10108 pub expert_width: usize,
10109 gate_row_bytes: usize,
10110 down_row_bytes: usize,
10111 device_workspace: std::sync::Mutex<Option<Nvfp4EpDeviceWorkspace>>,
10112}
10113
10114fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
10115 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
10116 matrix.codes,
10117 matrix.scales,
10118 matrix.out_features,
10119 matrix.in_features,
10120 )
10121}
10122
10123fn nvfp4_row_bytes(in_features: usize) -> usize {
10124 in_features / 64 * 36 }
10126
10127pub(crate) fn fuse_rope_append_on() -> bool {
10133 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10134 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
10135}
10136
10137pub(crate) fn no_local_shadow_on() -> bool {
10138 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10139 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
10140}
10141
10142pub fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
10158 assert_eq!(
10164 in_features % 64,
10165 0,
10166 "v2 permute needs whole 64-element superblocks, got in_features={in_features}"
10167 );
10168 let row_bytes = nvfp4_row_bytes(in_features);
10169 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
10170 let n_slots = in_features / 32;
10171 let mut out = Vec::with_capacity(v1.len());
10172 for row in 0..out_features {
10173 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
10174 for g in 0..n_slots {
10175 let (sblk, h) = (g / 2, g % 2);
10176 let b = &r[sblk * 36..sblk * 36 + 36];
10177 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
10178 }
10179 for g in 0..n_slots {
10180 let (sblk, h) = (g / 2, g % 2);
10181 let b = &r[sblk * 36..sblk * 36 + 36];
10182 out.push(b[2 * h]);
10183 out.push(b[2 * h + 1]);
10184 }
10185 }
10186 out
10187}
10188
10189fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
10193 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
10194 let v1 = nvfp4_repack_matrix(matrix);
10195 if slot_major {
10196 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
10197 } else {
10198 v1
10199 }
10200}
10201
10202#[allow(clippy::manual_is_multiple_of)] fn nvfp4_column_shard<'a>(
10206 matrix: Nvfp4BlockMatrix<'a>,
10207 tp: usize,
10208 rank: usize,
10209) -> Result<Nvfp4BlockMatrix<'a>, String> {
10210 if matrix.out_features % tp != 0 {
10211 return Err(format!(
10212 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
10213 matrix.out_features
10214 ));
10215 }
10216 let local_out = matrix.out_features / tp;
10217 let code_row = matrix.in_features / 2;
10218 let scale_row = matrix.in_features / 16;
10219 Ok(Nvfp4BlockMatrix {
10220 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
10221 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
10222 macro_scale: matrix.macro_scale,
10223 out_features: local_out,
10224 in_features: matrix.in_features,
10225 })
10226}
10227
10228#[allow(clippy::manual_is_multiple_of)] fn nvfp4_row_shard(
10232 matrix: Nvfp4BlockMatrix<'_>,
10233 tp: usize,
10234 rank: usize,
10235) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
10236 if matrix.in_features % tp != 0 {
10237 return Err(format!(
10238 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
10239 matrix.in_features
10240 ));
10241 }
10242 let local_in = matrix.in_features / tp;
10243 if !local_in.is_multiple_of(64) {
10244 return Err(format!(
10245 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
10246 ));
10247 }
10248 let code_row = matrix.in_features / 2;
10249 let scale_row = matrix.in_features / 16;
10250 let local_code = local_in / 2;
10251 let local_scale = local_in / 16;
10252 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
10253 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
10254 for row in 0..matrix.out_features {
10255 let code_start = row * code_row + rank * local_code;
10256 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
10257 let scale_start = row * scale_row + rank * local_scale;
10258 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
10259 }
10260 Ok((codes, scales, local_in))
10261}
10262
10263fn run_rank_nvfp4(
10267 engine: &Engine,
10268 matrix: Nvfp4BlockMatrix<'_>,
10269 activations: &[f32],
10270 tokens: usize,
10271) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10272 matrix.validate()?;
10273 validate_activations(activations, tokens, matrix.in_features)?;
10274 let _main = engine.gpu.enter_main()?;
10275 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
10276 let activations = engine.htod(activations)?;
10277 let output = engine.qmatvec_nvfp4_fast(
10278 &blocks.slice(0..blocks.len()),
10279 &activations,
10280 tokens,
10281 matrix.in_features,
10282 matrix.out_features,
10283 nvfp4_row_bytes(matrix.in_features),
10284 )?;
10285 engine.dtoh(&output)
10286}
10287
10288fn upload_rank_nvfp4(
10289 engine: &Engine,
10290 matrix: Nvfp4BlockMatrix<'_>,
10291) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
10292 matrix.validate()?;
10293 let _main = engine.gpu.enter_main()?;
10294 Ok(ResidentNvfp4Rank {
10295 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
10296 macro_scale: matrix.macro_scale,
10297 out_features: matrix.out_features,
10298 in_features: matrix.in_features,
10299 row_bytes: nvfp4_row_bytes(matrix.in_features),
10300 })
10301}
10302
10303fn run_resident_rank_nvfp4(
10304 engine: &Engine,
10305 rank: &ResidentNvfp4Rank,
10306 activations: &[f32],
10307 tokens: usize,
10308) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10309 validate_activations(activations, tokens, rank.in_features)?;
10310 let _main = engine.gpu.enter_main()?;
10311 let activations = engine.htod(activations)?;
10312 let output = engine.qmatvec_nvfp4_fast(
10313 &rank.blocks.slice(0..rank.blocks.len()),
10314 &activations,
10315 tokens,
10316 rank.in_features,
10317 rank.out_features,
10318 rank.row_bytes,
10319 )?;
10320 engine.dtoh(&output)
10321}
10322
10323fn apply_macro(values: &mut [f32], macro_scale: f32) {
10324 for value in values.iter_mut() {
10325 *value *= macro_scale;
10326 }
10327}
10328
10329impl TpE4m3HostBounce {
10330 pub fn full_nvfp4(
10332 &self,
10333 matrix: Nvfp4BlockMatrix<'_>,
10334 activations: &[f32],
10335 tokens: usize,
10336 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10337 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
10338 apply_macro(&mut output, matrix.macro_scale);
10339 Ok(output)
10340 }
10341
10342 pub fn column_parallel_nvfp4(
10345 &self,
10346 matrix: Nvfp4BlockMatrix<'_>,
10347 activations: &[f32],
10348 tokens: usize,
10349 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
10350 matrix.validate()?;
10351 validate_activations(activations, tokens, matrix.in_features)?;
10352 let tp = self.ranks.len();
10353 let local_out = matrix.out_features / tp;
10354 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10355 let mut rank_outputs = Vec::with_capacity(tp);
10356 for (rank_index, rank) in self.ranks.iter().enumerate() {
10357 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
10358 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
10359 let row_start = rank_index * local_out;
10360 for token in 0..tokens {
10361 gathered[token * matrix.out_features + row_start
10362 ..token * matrix.out_features + row_start + local_out]
10363 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10364 }
10365 rank_outputs.push(output);
10366 }
10367 apply_macro(&mut gathered, matrix.macro_scale);
10368 Ok(ColumnParallelResult {
10369 gathered,
10370 rank_outputs,
10371 })
10372 }
10373
10374 pub fn row_parallel_nvfp4(
10377 &self,
10378 matrix: Nvfp4BlockMatrix<'_>,
10379 activations: &[f32],
10380 tokens: usize,
10381 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
10382 matrix.validate()?;
10383 validate_activations(activations, tokens, matrix.in_features)?;
10384 let tp = self.ranks.len();
10385 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10386 let mut rank_partials = Vec::with_capacity(tp);
10387 for (rank_index, rank) in self.ranks.iter().enumerate() {
10388 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
10389 let local_activations =
10390 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10391 let shard = Nvfp4BlockMatrix {
10392 codes: &codes,
10393 scales: &scales,
10394 macro_scale: matrix.macro_scale,
10395 out_features: matrix.out_features,
10396 in_features: local_in,
10397 };
10398 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
10399 for (sum, value) in reduced.iter_mut().zip(&partial) {
10400 *sum += *value;
10401 }
10402 rank_partials.push(partial);
10403 }
10404 apply_macro(&mut reduced, matrix.macro_scale);
10405 Ok(RowParallelResult {
10406 reduced,
10407 rank_partials,
10408 })
10409 }
10410
10411 pub fn upload_expert_nvfp4(
10412 &self,
10413 gate: Nvfp4BlockMatrix<'_>,
10414 up: Nvfp4BlockMatrix<'_>,
10415 down: Nvfp4BlockMatrix<'_>,
10416 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
10417 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10418 return Err("NVFP4 TP expert gate/up dimensions differ".into());
10419 }
10420 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10421 return Err(format!(
10422 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
10423 down.out_features, down.in_features, gate.out_features, gate.in_features
10424 )
10425 .into());
10426 }
10427 let tp = self.ranks.len();
10428 let mut gate_ranks = Vec::with_capacity(tp);
10429 let mut up_ranks = Vec::with_capacity(tp);
10430 let mut down_ranks = Vec::with_capacity(tp);
10431 for (rank_index, engine) in self.ranks.iter().enumerate() {
10432 gate_ranks.push(upload_rank_nvfp4(
10433 engine,
10434 nvfp4_column_shard(gate, tp, rank_index)?,
10435 )?);
10436 up_ranks.push(upload_rank_nvfp4(
10437 engine,
10438 nvfp4_column_shard(up, tp, rank_index)?,
10439 )?);
10440 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
10441 down_ranks.push(upload_rank_nvfp4(
10442 engine,
10443 Nvfp4BlockMatrix {
10444 codes: &codes,
10445 scales: &scales,
10446 macro_scale: down.macro_scale,
10447 out_features: down.out_features,
10448 in_features: local_in,
10449 },
10450 )?);
10451 }
10452 Ok(ResidentTpNvfp4Expert {
10453 gate: ResidentNvfp4ColumnParallel {
10454 ranks: gate_ranks,
10455 out_features: gate.out_features,
10456 in_features: gate.in_features,
10457 },
10458 up: ResidentNvfp4ColumnParallel {
10459 ranks: up_ranks,
10460 out_features: up.out_features,
10461 in_features: up.in_features,
10462 },
10463 down: ResidentNvfp4RowParallel {
10464 ranks: down_ranks,
10465 out_features: down.out_features,
10466 in_features: down.in_features,
10467 },
10468 input_width: gate.in_features,
10469 expert_width: gate.out_features,
10470 })
10471 }
10472
10473 fn column_parallel_resident_nvfp4(
10474 &self,
10475 matrix: &ResidentNvfp4ColumnParallel,
10476 activations: &[f32],
10477 tokens: usize,
10478 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10479 validate_activations(activations, tokens, matrix.in_features)?;
10480 let local_out = matrix.out_features / self.ranks.len();
10481 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
10482 let mut macro_scale = None;
10483 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10484 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
10485 let row_start = rank_index * local_out;
10486 for token in 0..tokens {
10487 gathered[token * matrix.out_features + row_start
10488 ..token * matrix.out_features + row_start + local_out]
10489 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
10490 }
10491 macro_scale = Some(shard.macro_scale);
10492 }
10493 apply_macro(
10494 &mut gathered,
10495 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
10496 );
10497 Ok(gathered)
10498 }
10499
10500 fn row_parallel_resident_nvfp4(
10501 &self,
10502 matrix: &ResidentNvfp4RowParallel,
10503 activations: &[f32],
10504 tokens: usize,
10505 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10506 validate_activations(activations, tokens, matrix.in_features)?;
10507 let tp = self.ranks.len();
10508 let local_in = matrix.in_features / tp;
10509 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
10510 let mut macro_scale = None;
10511 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
10512 if shard.in_features != local_in {
10513 return Err(format!(
10514 "NVFP4 resident row shard in_features {} != expected {local_in}",
10515 shard.in_features
10516 )
10517 .into());
10518 }
10519 let local_activations =
10520 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
10521 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
10522 for (sum, value) in reduced.iter_mut().zip(&partial) {
10523 *sum += *value;
10524 }
10525 macro_scale = Some(shard.macro_scale);
10526 }
10527 apply_macro(
10528 &mut reduced,
10529 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
10530 );
10531 Ok(reduced)
10532 }
10533
10534 pub fn run_expert_nvfp4(
10535 &self,
10536 expert: &ResidentTpNvfp4Expert,
10537 input: &[f32],
10538 tokens: usize,
10539 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10540 validate_activations(input, tokens, expert.input_width)?;
10541 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
10542 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
10543 let activated: Vec<f32> = gate
10544 .iter()
10545 .zip(&up)
10546 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
10547 .collect();
10548 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
10549 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
10550 }
10551
10552 #[allow(clippy::manual_is_multiple_of)] pub fn upload_tensor_parallel_nvfp4(
10555 &self,
10556 gate: Nvfp4ExpertBank<'_>,
10557 up: Nvfp4ExpertBank<'_>,
10558 down: Nvfp4ExpertBank<'_>,
10559 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
10560 gate.validate()?;
10561 up.validate()?;
10562 down.validate()?;
10563 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10564 return Err("NVFP4 TP gate/up/down expert counts differ".into());
10565 }
10566 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10567 return Err("NVFP4 TP gate/up dimensions differ".into());
10568 }
10569 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10570 return Err(format!(
10571 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
10572 down.out_features, down.in_features, gate.out_features, gate.in_features
10573 )
10574 .into());
10575 }
10576 let tp = self.ranks.len();
10577 if gate.out_features % tp != 0 {
10578 return Err(format!(
10579 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
10580 gate.out_features
10581 )
10582 .into());
10583 }
10584 if !down.in_features.is_multiple_of(NVFP4_CANONICAL_ROW_SHARDS)
10585 || !(down.in_features / NVFP4_CANONICAL_ROW_SHARDS).is_multiple_of(64)
10586 {
10587 return Err(format!(
10588 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
10589 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
10590 down.in_features
10591 )
10592 .into());
10593 }
10594 if tp > NVFP4_CANONICAL_ROW_SHARDS {
10595 return Err(format!(
10596 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
10597 ({NVFP4_CANONICAL_ROW_SHARDS})"
10598 )
10599 .into());
10600 }
10601
10602 let slot_major = bank_slot_major_on();
10606 eprintln!(
10614 "[nvfp4-bank] layout={} source={} tp={tp} experts={} in_f={} out_f={}",
10615 if slot_major {
10616 "slot-major"
10617 } else {
10618 "block-nvfp4-v1"
10619 },
10620 bank_slot_major_source().1,
10625 gate.expert_count,
10626 gate.in_features,
10627 gate.out_features
10628 );
10629 let mut gate_ranks = Vec::with_capacity(tp);
10630 let mut up_ranks = Vec::with_capacity(tp);
10631 let mut macros_gate_dev = Vec::with_capacity(tp);
10632 let mut macros_up_dev = Vec::with_capacity(tp);
10633 let mut macros_down_dev = Vec::with_capacity(tp);
10634 for (rank_index, engine) in self.ranks.iter().enumerate() {
10635 let _main = engine.gpu.enter_main()?;
10636 let mut gate_host: Vec<u8> = Vec::new();
10642 let mut up_host: Vec<u8> = Vec::new();
10643 for expert in 0..gate.expert_count {
10644 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
10645 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, slot_major));
10646 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
10647 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, slot_major));
10648 }
10649 let bank_experts = gate.expert_count;
10650 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
10651 let up_expert_bytes = up_host.len() / bank_experts.max(1);
10652 let local_out = gate.out_features / tp;
10653 gate_ranks.push(ResidentNvfp4ColumnBankRank {
10654 bank: engine.htod_bytes(&gate_host)?,
10655 expert_bytes: gate_expert_bytes,
10656 local_out,
10657 in_features: gate.in_features,
10658 row_bytes: nvfp4_row_bytes(gate.in_features),
10659 slot_major,
10660 });
10661 up_ranks.push(ResidentNvfp4ColumnBankRank {
10662 bank: engine.htod_bytes(&up_host)?,
10663 expert_bytes: up_expert_bytes,
10664 local_out,
10665 in_features: up.in_features,
10666 row_bytes: nvfp4_row_bytes(up.in_features),
10667 slot_major,
10668 });
10669 macros_gate_dev.push(engine.htod(gate.macros)?);
10670 macros_up_dev.push(engine.htod(up.macros)?);
10671 macros_down_dev.push(engine.htod(down.macros)?);
10672 }
10673 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
10677 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
10678 let device_rank = shard_index % tp;
10679 let engine = &self.ranks[device_rank];
10680 let _main = engine.gpu.enter_main()?;
10681 let mut down_host: Vec<u8> = Vec::new();
10682 for expert in 0..down.expert_count {
10683 let down_matrix = down.expert(expert)?;
10684 let (codes, scales, local_in) =
10685 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
10686 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
10687 Nvfp4BlockMatrix {
10688 codes: &codes,
10689 scales: &scales,
10690 macro_scale: down_matrix.macro_scale,
10691 out_features: down_matrix.out_features,
10692 in_features: local_in,
10693 },
10694 slot_major,
10695 ));
10696 }
10697 let bank_experts = down.expert_count;
10698 let down_expert_bytes = down_host.len() / bank_experts.max(1);
10699 let local_in = down.in_features / NVFP4_CANONICAL_ROW_SHARDS;
10700 down_ranks.push(ResidentNvfp4RowBankRank {
10701 bank: engine.htod_bytes(&down_host)?,
10702 expert_bytes: down_expert_bytes,
10703 device_rank,
10704 out_features: down.out_features,
10705 local_in,
10706 row_bytes: nvfp4_row_bytes(local_in),
10707 slot_major,
10708 });
10709 }
10710 Ok(ResidentNvfp4TensorParallel {
10711 gate: gate_ranks,
10712 up: up_ranks,
10713 down: down_ranks,
10714 macros_gate: gate.macros.to_vec(),
10715 macros_up: up.macros.to_vec(),
10716 macros_down: down.macros.to_vec(),
10717 macros_gate_dev,
10718 macros_up_dev,
10719 macros_down_dev,
10720 expert_count: gate.expert_count,
10721 input_width: gate.in_features,
10722 expert_width: gate.out_features,
10723 device_workspace: std::sync::Mutex::new(None),
10724 prime_tables: std::sync::Mutex::new(Vec::new()),
10725 })
10726 }
10727
10728 fn run_column_bank_expert_nvfp4(
10729 &self,
10730 ranks: &[ResidentNvfp4ColumnBankRank],
10731 macros: &[f32],
10732 expert: usize,
10733 input: &[f32],
10734 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10735 let local_out = ranks
10736 .first()
10737 .ok_or("NVFP4 TP column bank has no ranks")?
10738 .local_out;
10739 let mut gathered = vec![0.0f32; local_out * ranks.len()];
10740 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
10741 let _main = engine.gpu.enter_main()?;
10742 let activations = engine.htod(input)?;
10743 let output = bank.host_canonical_expert(engine, expert, &activations)?;
10744 let output = engine.dtoh(&output)?;
10745 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
10746 }
10747 apply_macro(&mut gathered, macros[expert]);
10748 Ok(gathered)
10749 }
10750
10751 fn run_row_bank_expert_nvfp4(
10755 &self,
10756 shards: &[ResidentNvfp4RowBankRank],
10757 macros: &[f32],
10758 expert: usize,
10759 input: &[f32],
10760 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10761 let out_features = shards
10762 .first()
10763 .ok_or("NVFP4 TP row bank has no canonical shards")?
10764 .out_features;
10765 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
10766 let mut reduced = vec![0.0f32; out_features];
10767 for (shard_index, shard) in shards.iter().enumerate() {
10768 let engine = self
10769 .ranks
10770 .get(shard.device_rank)
10771 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
10772 let _main = engine.gpu.enter_main()?;
10773 let local_activations =
10774 activation_shard(input, 1, in_features, shards.len(), shard_index);
10775 let activations = engine.htod(&local_activations)?;
10776 let output = shard.host_canonical_expert(engine, expert, &activations)?;
10777 let partial = engine.dtoh(&output)?;
10778 for (sum, value) in reduced.iter_mut().zip(&partial) {
10779 *sum += *value;
10780 }
10781 }
10782 apply_macro(&mut reduced, macros[expert]);
10783 Ok(reduced)
10784 }
10785
10786 #[allow(clippy::manual_is_multiple_of)] pub fn upload_expert_parallel_nvfp4(
10791 &self,
10792 gate: Nvfp4ExpertBank<'_>,
10793 up: Nvfp4ExpertBank<'_>,
10794 down: Nvfp4ExpertBank<'_>,
10795 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10796 gate.validate()?;
10797 up.validate()?;
10798 down.validate()?;
10799 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10800 return Err("NVFP4 EP gate/up/down expert counts differ".into());
10801 }
10802 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10803 return Err("NVFP4 EP gate/up dimensions differ".into());
10804 }
10805 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10806 return Err(format!(
10807 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
10808 down.out_features, down.in_features, gate.out_features, gate.in_features
10809 )
10810 .into());
10811 }
10812 let world = self.ranks.len();
10813 if gate.expert_count % world != 0 {
10814 return Err(format!(
10815 "NVFP4 EP expert count {} is not divisible by {world} ranks",
10816 gate.expert_count
10817 )
10818 .into());
10819 }
10820 let experts_per_rank = gate.expert_count / world;
10821 let mut ranks = Vec::with_capacity(world);
10822 for (rank_index, engine) in self.ranks.iter().enumerate() {
10823 let _main = engine.gpu.enter_main()?;
10824 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10825 let mut gate_host = Vec::new();
10826 let mut up_host = Vec::new();
10827 let mut down_host = Vec::new();
10828 for expert in expert_range.clone() {
10829 gate_host.extend_from_slice(&nvfp4_repack_matrix(gate.expert(expert)?));
10830 up_host.extend_from_slice(&nvfp4_repack_matrix(up.expert(expert)?));
10831 down_host.extend_from_slice(&nvfp4_repack_matrix(down.expert(expert)?));
10832 }
10833 let gate_expert_bytes = gate_host.len() / experts_per_rank;
10834 let up_expert_bytes = up_host.len() / experts_per_rank;
10835 if gate_expert_bytes != up_expert_bytes {
10836 return Err("NVFP4 EP gate/up packed expert bytes differ".into());
10837 }
10838 let down_expert_bytes = down_host.len() / experts_per_rank;
10839 ranks.push(ResidentNvfp4EpRank {
10840 gate: engine.htod_bytes(&gate_host)?,
10841 up: engine.htod_bytes(&up_host)?,
10842 down: engine.htod_bytes(&down_host)?,
10843 gate_expert_bytes,
10844 down_expert_bytes,
10845 macros_gate: engine.htod(&gate.macros[expert_range.clone()])?,
10846 macros_up: engine.htod(&up.macros[expert_range.clone()])?,
10847 macros_down: engine.htod(&down.macros[expert_range.clone()])?,
10848 expert_range,
10849 });
10850 }
10851 Ok(ResidentNvfp4ExpertParallel {
10852 ranks,
10853 macros_gate: gate.macros.to_vec(),
10854 macros_up: up.macros.to_vec(),
10855 macros_down: down.macros.to_vec(),
10856 expert_count: gate.expert_count,
10857 input_width: gate.in_features,
10858 expert_width: gate.out_features,
10859 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
10860 down_row_bytes: nvfp4_row_bytes(down.in_features),
10861 device_workspace: std::sync::Mutex::new(None),
10862 })
10863 }
10864
10865 pub fn upload_expert_parallel_nvfp4_normalized(
10871 &self,
10872 gate: &crate::model::HostExps,
10873 up: &crate::model::HostExps,
10874 down: &crate::model::HostExps,
10875 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10876 for (label, bank) in [("gate", gate), ("up", up), ("down", down)] {
10877 if bank.qtype != crate::QT_NVFP4 || !bank.is_uniform_layout() {
10878 return Err(format!(
10879 "NVFP4 EP normalized {label} bank requires one uniform NVFP4 layout, \
10880 got qtype={} uniform={}",
10881 bank.qtype,
10882 bank.is_uniform_layout()
10883 )
10884 .into());
10885 }
10886 if bank.n_expert == 0
10887 || bank.expert_stride != bank.out_f * bank.row_bytes
10888 || (0..bank.n_expert)
10889 .any(|expert| bank.expert_bytes(expert).len() != bank.expert_stride)
10890 {
10891 return Err(format!("NVFP4 EP normalized {label} bank geometry is invalid").into());
10892 }
10893 }
10894 if gate.n_expert != up.n_expert || gate.n_expert != down.n_expert {
10895 return Err("NVFP4 EP normalized gate/up/down expert counts differ".into());
10896 }
10897 if gate.in_f != up.in_f || gate.out_f != up.out_f {
10898 return Err("NVFP4 EP normalized gate/up dimensions differ".into());
10899 }
10900 if down.in_f != gate.out_f || down.out_f != gate.in_f {
10901 return Err(format!(
10902 "NVFP4 EP normalized down {}x{} does not invert gate/up {}x{}",
10903 down.out_f, down.in_f, gate.out_f, gate.in_f
10904 )
10905 .into());
10906 }
10907 let macros = |bank: &crate::model::HostExps| -> Result<Vec<f32>, String> {
10908 let values = bank
10909 .macros
10910 .clone()
10911 .unwrap_or_else(|| vec![1.0; bank.n_expert]);
10912 if values.len() != bank.n_expert
10913 || !values.iter().all(|value| value.is_finite() && *value > 0.0)
10914 {
10915 return Err("NVFP4 EP normalized macro row is not finite-positive".to_string());
10916 }
10917 Ok(values)
10918 };
10919 let macros_gate = macros(gate)?;
10920 let macros_up = macros(up)?;
10921 let macros_down = macros(down)?;
10922 let world = self.ranks.len();
10923 if !gate.n_expert.is_multiple_of(world) {
10924 return Err(format!(
10925 "NVFP4 EP normalized expert count {} is not divisible by {world} ranks",
10926 gate.n_expert
10927 )
10928 .into());
10929 }
10930 let experts_per_rank = gate.n_expert / world;
10931 let mut ranks = Vec::with_capacity(world);
10932 for (rank_index, engine) in self.ranks.iter().enumerate() {
10933 let _main = engine.gpu.enter_main()?;
10934 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10935 let mut gate_host = Vec::with_capacity(experts_per_rank * gate.expert_stride);
10936 let mut up_host = Vec::with_capacity(experts_per_rank * up.expert_stride);
10937 let mut down_host = Vec::with_capacity(experts_per_rank * down.expert_stride);
10938 for expert in expert_range.clone() {
10939 gate_host.extend_from_slice(gate.expert_bytes(expert));
10940 up_host.extend_from_slice(up.expert_bytes(expert));
10941 down_host.extend_from_slice(down.expert_bytes(expert));
10942 }
10943 ranks.push(ResidentNvfp4EpRank {
10944 gate: engine.htod_bytes(&gate_host)?,
10945 up: engine.htod_bytes(&up_host)?,
10946 down: engine.htod_bytes(&down_host)?,
10947 gate_expert_bytes: gate.expert_stride,
10948 down_expert_bytes: down.expert_stride,
10949 macros_gate: engine.htod(¯os_gate[expert_range.clone()])?,
10950 macros_up: engine.htod(¯os_up[expert_range.clone()])?,
10951 macros_down: engine.htod(¯os_down[expert_range.clone()])?,
10952 expert_range,
10953 });
10954 }
10955 Ok(ResidentNvfp4ExpertParallel {
10956 ranks,
10957 macros_gate,
10958 macros_up,
10959 macros_down,
10960 expert_count: gate.n_expert,
10961 input_width: gate.in_f,
10962 expert_width: gate.out_f,
10963 gate_row_bytes: gate.row_bytes,
10964 down_row_bytes: down.row_bytes,
10965 device_workspace: std::sync::Mutex::new(None),
10966 })
10967 }
10968
10969 #[allow(clippy::too_many_arguments)]
10975 pub fn run_routed_experts_nvfp4(
10976 &self,
10977 experts: &ResidentNvfp4ExpertParallel,
10978 input: &[f32],
10979 tokens: usize,
10980 selected: &[usize],
10981 route_weights: &[f32],
10982 experts_per_token: usize,
10983 activation_limit: Option<f32>,
10984 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10985 validate_activations(input, tokens, experts.input_width)?;
10986 let pairs = tokens
10987 .checked_mul(experts_per_token)
10988 .ok_or("NVFP4 EP route count overflow")?;
10989 if selected.len() != pairs || route_weights.len() != pairs {
10990 return Err(format!(
10991 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
10992 {experts_per_token} ({pairs})",
10993 selected.len(),
10994 route_weights.len(),
10995 )
10996 .into());
10997 }
10998 if !route_weights.iter().all(|weight| weight.is_finite()) {
10999 return Err("NVFP4 EP route weights contain a non-finite value".into());
11000 }
11001 let experts_per_rank = experts.expert_count / experts.ranks.len();
11002 let mut output = vec![0.0f32; tokens * experts.input_width];
11003 for token in 0..tokens {
11004 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11005 for slot in 0..experts_per_token {
11006 let pair = token * experts_per_token + slot;
11007 let expert = selected[pair];
11008 if expert >= experts.expert_count {
11009 return Err(format!(
11010 "NVFP4 EP selected expert {expert} outside 0..{}",
11011 experts.expert_count
11012 )
11013 .into());
11014 }
11015 let owner = expert / experts_per_rank;
11016 let local = expert - owner * experts_per_rank;
11017 let rank = &experts.ranks[owner];
11018 let engine = &self.ranks[owner];
11019 let _main = engine.gpu.enter_main()?;
11020 let device_input = engine.htod(input_row)?;
11021 let gate_out = engine.qmatvec_nvfp4_fast(
11022 &rank.gate.slice(
11023 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
11024 ),
11025 &device_input,
11026 1,
11027 experts.input_width,
11028 experts.expert_width,
11029 experts.gate_row_bytes,
11030 )?;
11031 let up_out = engine.qmatvec_nvfp4_fast(
11032 &rank.up.slice(
11033 local * rank.gate_expert_bytes..(local + 1) * rank.gate_expert_bytes,
11034 ),
11035 &device_input,
11036 1,
11037 experts.input_width,
11038 experts.expert_width,
11039 experts.gate_row_bytes,
11040 )?;
11041 let mut gate_host = engine.dtoh(&gate_out)?;
11042 let mut up_host = engine.dtoh(&up_out)?;
11043 apply_macro(&mut gate_host, experts.macros_gate[expert]);
11044 apply_macro(&mut up_host, experts.macros_up[expert]);
11045 let activated: Vec<f32> = gate_host
11046 .iter()
11047 .zip(&up_host)
11048 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11049 .collect();
11050 let device_activated = engine.htod(&activated)?;
11051 let down_out = engine.qmatvec_nvfp4_fast(
11052 &rank.down.slice(
11053 local * rank.down_expert_bytes..(local + 1) * rank.down_expert_bytes,
11054 ),
11055 &device_activated,
11056 1,
11057 experts.expert_width,
11058 experts.input_width,
11059 experts.down_row_bytes,
11060 )?;
11061 let mut down_host = engine.dtoh(&down_out)?;
11062 apply_macro(&mut down_host, experts.macros_down[expert]);
11063 let weight = route_weights[pair];
11064 for (sum, value) in output
11065 [token * experts.input_width..(token + 1) * experts.input_width]
11066 .iter_mut()
11067 .zip(down_host)
11068 {
11069 *sum += weight * value;
11070 }
11071 }
11072 }
11073 Ok(output)
11074 }
11075
11076 #[allow(clippy::too_many_arguments)]
11085 pub fn run_routed_experts_nvfp4_w4a16_device_io(
11086 &self,
11087 experts: &ResidentNvfp4ExpertParallel,
11088 e: &Engine,
11089 input_dev: &crate::CudaSlice<f32>,
11090 tokens: usize,
11091 selected: &[usize],
11092 route_weights: &[f32],
11093 experts_per_token: usize,
11094 activation_limit: Option<f32>,
11095 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11096 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11100 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11101 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11102 let started = timing.then(std::time::Instant::now);
11103 if !self.native_p2p {
11104 return Err("W4A16 NVFP4 device EP requires native P2P".into());
11105 }
11106 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11107 return Err(format!(
11108 "W4A16 NVFP4 device EP root device {:?} != model engine device {}",
11109 self.devices.first(),
11110 e.ctx().ordinal()
11111 )
11112 .into());
11113 }
11114 let active_input_values =
11118 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11119 let pairs = tokens
11120 .checked_mul(experts_per_token)
11121 .ok_or("W4A16 NVFP4 device EP route count overflow")?;
11122 if selected.len() != pairs || route_weights.len() != pairs {
11123 return Err(format!(
11124 "W4A16 NVFP4 device EP routes selected={} weights={} != tokens {tokens} x \
11125 experts/token {experts_per_token} ({pairs})",
11126 selected.len(),
11127 route_weights.len(),
11128 )
11129 .into());
11130 }
11131 if !route_weights.iter().all(|weight| weight.is_finite()) {
11132 return Err("W4A16 NVFP4 device EP route weights contain a non-finite value".into());
11133 }
11134 let world = self.ranks.len();
11135 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11136 return Err(format!(
11137 "W4A16 NVFP4 device EP runtime ranks {world} != bank ranks {}",
11138 experts.ranks.len()
11139 )
11140 .into());
11141 }
11142 let owner_routes = partition_expert_owner_routes(
11143 experts.expert_count,
11144 world,
11145 tokens,
11146 experts_per_token,
11147 selected,
11148 )?;
11149
11150 let mut workspace_guard = experts
11151 .device_workspace
11152 .lock()
11153 .map_err(|_| "W4A16 NVFP4 device EP workspace lock is poisoned")?;
11154 if workspace_guard.is_none() {
11155 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11156 let capacity_pairs = capacity_tokens * experts_per_token;
11157 let mut input = Vec::with_capacity(world);
11158 let mut input_bf16 = Vec::with_capacity(world);
11159 let mut input_q8 = Vec::with_capacity(world);
11160 let mut input_q8_scales = Vec::with_capacity(world);
11161 let mut sel = Vec::with_capacity(world);
11162 let mut token_rows = Vec::with_capacity(world);
11163 let mut global_pairs = Vec::with_capacity(world);
11164 let mut route_w = Vec::with_capacity(world);
11165 let mut gate_out = Vec::with_capacity(world);
11166 let mut up_out = Vec::with_capacity(world);
11167 let mut activation_bf16 = Vec::with_capacity(world);
11168 let mut activation_q8 = Vec::with_capacity(world);
11169 let mut activation_q8_scales = Vec::with_capacity(world);
11170 let mut ev_rank = Vec::with_capacity(world);
11171 for engine in &self.ranks {
11172 let _main = engine.gpu.enter_main()?;
11173 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11174 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11175 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11176 input_q8_scales
11177 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11178 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11179 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11180 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11181 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11182 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11183 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11184 activation_bf16
11185 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11186 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11187 activation_q8_scales
11188 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11189 ev_rank.push(engine.ctx().new_event(None)?);
11190 }
11191 let _main = e.gpu.enter_main()?;
11192 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11193 let slot_rows_raw = {
11194 use cudarc::driver::DevicePtr;
11195 let stream = e.stream();
11196 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11197 pointer
11198 };
11199 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11200 input,
11201 input_bf16,
11202 input_q8,
11203 input_q8_scales,
11204 sel,
11205 token_rows,
11206 global_pairs,
11207 route_w,
11208 gate_out,
11209 up_out,
11210 activation_bf16,
11211 activation_q8,
11212 activation_q8_scales,
11213 slot_rows,
11214 slot_rows_raw,
11215 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11216 ev_entry: e.ctx().new_event(None)?,
11217 ev_entry_device: e.ctx().ordinal(),
11218 ev_rank,
11219 phase_events: None,
11220 capacity_tokens,
11221 experts_per_token,
11222 });
11223 }
11224 let workspace = workspace_guard
11225 .as_mut()
11226 .expect("W4A16 NVFP4 device EP workspace initialized above");
11227 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11228 return Err(format!(
11229 "W4A16 NVFP4 device EP workspace tokens={} experts/token={} cannot serve \
11230 tokens={tokens} experts/token={experts_per_token}",
11231 workspace.capacity_tokens, workspace.experts_per_token,
11232 )
11233 .into());
11234 }
11235 if workspace.ev_entry_device != e.ctx().ordinal() {
11236 return Err("W4A16 NVFP4 device EP model engine changed".into());
11237 }
11238
11239 {
11240 let _main = e.gpu.enter_main()?;
11241 let mut destination = workspace.route_weights.slice_mut(0..pairs);
11242 e.stream()
11243 .memcpy_htod(&route_weights[..pairs], &mut destination)?;
11244 workspace.ev_entry.record(&e.stream())?;
11245 }
11246 for (rank_index, engine) in self.ranks.iter().enumerate() {
11247 let _main = engine.gpu.enter_main()?;
11248 engine.stream().wait(&workspace.ev_entry)?;
11249 {
11250 let mut destination = workspace.input[rank_index].slice_mut(0..active_input_values);
11251 engine
11252 .stream()
11253 .memcpy_dtod(&input_dev.slice(0..active_input_values), &mut destination)?;
11254 }
11255 engine.f32_to_bf16_into(
11256 &workspace.input[rank_index],
11257 &mut workspace.input_bf16[rank_index],
11258 tokens * experts.input_width,
11259 )?;
11260 let owner = &owner_routes[rank_index];
11261 debug_assert_eq!(owner.rank, rank_index);
11262 let local_count = owner.selected.len();
11263 if local_count > 0 {
11264 let local_selected = owner
11265 .selected
11266 .iter()
11267 .map(|&expert| expert as i32)
11268 .collect::<Vec<_>>();
11269 let local_token_rows = owner
11270 .token_rows
11271 .iter()
11272 .map(|&token| token as i32)
11273 .collect::<Vec<_>>();
11274 let local_global_pairs = owner
11275 .global_pairs
11276 .iter()
11277 .map(|&pair| pair as i32)
11278 .collect::<Vec<_>>();
11279 {
11280 let mut destination = workspace.sel[rank_index].slice_mut(0..local_count);
11281 engine
11282 .stream()
11283 .memcpy_htod(&local_selected, &mut destination)?;
11284 }
11285 {
11286 let mut destination =
11287 workspace.token_rows[rank_index].slice_mut(0..local_count);
11288 engine
11289 .stream()
11290 .memcpy_htod(&local_token_rows, &mut destination)?;
11291 }
11292 {
11293 let mut destination =
11294 workspace.global_pairs[rank_index].slice_mut(0..local_count);
11295 engine
11296 .stream()
11297 .memcpy_htod(&local_global_pairs, &mut destination)?;
11298 }
11299 let rank = &experts.ranks[rank_index];
11300 engine.qmatvec_nvfp4_bf16_sel_dual_rows_into(
11301 &rank.gate,
11302 &rank.up,
11303 &workspace.sel[rank_index],
11304 &workspace.token_rows[rank_index],
11305 &workspace.input_bf16[rank_index],
11306 &mut workspace.gate_out[rank_index],
11307 &mut workspace.up_out[rank_index],
11308 local_count,
11309 experts.input_width,
11310 experts.expert_width,
11311 experts.gate_row_bytes,
11312 rank.gate_expert_bytes,
11313 tokens,
11314 )?;
11315 engine.silu_mul_scaled_host_expf_bf16_sel_into(
11316 &workspace.gate_out[rank_index],
11317 &workspace.up_out[rank_index],
11318 &rank.macros_gate,
11319 &rank.macros_up,
11320 &workspace.sel[rank_index],
11321 activation_limit,
11322 &mut workspace.activation_bf16[rank_index],
11323 experts.expert_width,
11324 local_count,
11325 )?;
11326 engine.qmatvec_nvfp4_bf16_sel_down_rows_raw(
11327 &rank.down,
11328 &workspace.sel[rank_index],
11329 &workspace.global_pairs[rank_index],
11330 &workspace.activation_bf16[rank_index],
11331 &rank.macros_down,
11332 workspace.slot_rows_raw,
11333 local_count,
11334 experts.expert_width,
11335 experts.input_width,
11336 experts.down_row_bytes,
11337 rank.down_expert_bytes,
11338 pairs,
11339 )?;
11340 }
11341 workspace.ev_rank[rank_index].record(&engine.stream())?;
11342 }
11343
11344 let output = {
11345 let _main = e.gpu.enter_main()?;
11346 for event in &workspace.ev_rank {
11347 e.stream().wait(event)?;
11348 }
11349 let mut output = e.uninit(tokens * experts.input_width)?;
11350 e.axpy_rows_seq_tokens_into(
11351 &workspace.slot_rows,
11352 &workspace.route_weights,
11353 &mut output,
11354 experts.input_width,
11355 experts_per_token,
11356 tokens,
11357 )?;
11358 output
11359 };
11360 if let Some(started) = started {
11361 use std::sync::atomic::Ordering;
11362 e.stream().synchronize()?;
11363 let elapsed = started.elapsed().as_nanos() as u64;
11364 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11365 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11366 if calls.is_multiple_of(430) {
11367 eprintln!(
11368 "[nvfp4-ep-w4a16-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11369 ns as f64 / 1.0e6,
11370 ns as f64 / calls as f64 / 1.0e3,
11371 );
11372 }
11373 }
11374 Ok(output)
11375 }
11376
11377 #[allow(clippy::too_many_arguments)]
11383 pub fn run_routed_experts_nvfp4_w4a16_device_routed(
11384 &self,
11385 experts: &ResidentNvfp4ExpertParallel,
11386 e: &Engine,
11387 input_dev: &crate::CudaSlice<f32>,
11388 selected_dev: &crate::CudaSlice<i32>,
11389 route_weights_dev: &crate::CudaSlice<f32>,
11390 tokens: usize,
11391 experts_per_token: usize,
11392 activation_limit: Option<f32>,
11393 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11394 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11395 experts,
11396 e,
11397 input_dev,
11398 selected_dev,
11399 route_weights_dev,
11400 tokens,
11401 experts_per_token,
11402 activation_limit,
11403 None,
11404 )
11405 }
11406
11407 #[allow(clippy::too_many_arguments)]
11411 pub fn run_routed_experts_nvfp4_w4a16_device_routed_prejoin(
11412 &self,
11413 experts: &ResidentNvfp4ExpertParallel,
11414 e: &Engine,
11415 input_dev: &crate::CudaSlice<f32>,
11416 selected_dev: &crate::CudaSlice<i32>,
11417 route_weights_dev: &crate::CudaSlice<f32>,
11418 tokens: usize,
11419 experts_per_token: usize,
11420 activation_limit: Option<f32>,
11421 mut pre_join: impl FnMut() -> Result<(), Box<dyn std::error::Error>>,
11422 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11423 self.run_routed_experts_nvfp4_w4a16_device_routed_inner(
11424 experts,
11425 e,
11426 input_dev,
11427 selected_dev,
11428 route_weights_dev,
11429 tokens,
11430 experts_per_token,
11431 activation_limit,
11432 Some(&mut pre_join),
11433 )
11434 }
11435
11436 #[allow(clippy::too_many_arguments)]
11437 fn run_routed_experts_nvfp4_w4a16_device_routed_inner(
11438 &self,
11439 experts: &ResidentNvfp4ExpertParallel,
11440 e: &Engine,
11441 input_dev: &crate::CudaSlice<f32>,
11442 selected_dev: &crate::CudaSlice<i32>,
11443 route_weights_dev: &crate::CudaSlice<f32>,
11444 tokens: usize,
11445 experts_per_token: usize,
11446 activation_limit: Option<f32>,
11447 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11448 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11449 if !self.native_p2p {
11450 return Err("W4A16 device-routed EP requires native P2P".into());
11451 }
11452 if self.devices.first().copied() != Some(e.ctx().ordinal()) {
11453 return Err(format!(
11454 "W4A16 device-routed EP root device {:?} != model engine device {}",
11455 self.devices.first(),
11456 e.ctx().ordinal()
11457 )
11458 .into());
11459 }
11460 let active_input_values =
11461 nvfp4_ep_active_input_values(input_dev.len(), tokens, experts.input_width)?;
11462 let pairs = tokens
11463 .checked_mul(experts_per_token)
11464 .ok_or("W4A16 device-routed EP route count overflow")?;
11465 if selected_dev.len() < pairs || route_weights_dev.len() < pairs {
11466 return Err(format!(
11467 "W4A16 device-routed EP metadata selected={} weights={} < pairs={pairs}",
11468 selected_dev.len(),
11469 route_weights_dev.len(),
11470 )
11471 .into());
11472 }
11473 let world = self.ranks.len();
11474 if world != experts.ranks.len() || !(2..=PRODUCT_MAX_CARDS).contains(&world) {
11475 return Err(format!(
11476 "W4A16 device-routed EP runtime ranks {world} != bank ranks {}",
11477 experts.ranks.len()
11478 )
11479 .into());
11480 }
11481
11482 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11483 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11484 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11485 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11486 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11487 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11488 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11489 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11490 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11491 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11492 let started = timing.then(std::time::Instant::now);
11493 let pair_down_enabled = parallel_ep_pair_down_enabled()?;
11494
11495 let mut workspace_guard = experts
11496 .device_workspace
11497 .lock()
11498 .map_err(|_| "W4A16 device-routed EP workspace lock is poisoned")?;
11499 if workspace_guard.is_none() {
11500 let capacity_tokens = NVFP4_EP_DEVICE_BATCH_CAP;
11501 let capacity_pairs = capacity_tokens * experts_per_token;
11502 let mut input = Vec::with_capacity(world);
11503 let mut input_bf16 = Vec::with_capacity(world);
11504 let mut input_q8 = Vec::with_capacity(world);
11505 let mut input_q8_scales = Vec::with_capacity(world);
11506 let mut sel = Vec::with_capacity(world);
11507 let mut token_rows = Vec::with_capacity(world);
11508 let mut global_pairs = Vec::with_capacity(world);
11509 let mut route_w = Vec::with_capacity(world);
11510 let mut gate_out = Vec::with_capacity(world);
11511 let mut up_out = Vec::with_capacity(world);
11512 let mut activation_bf16 = Vec::with_capacity(world);
11513 let mut activation_q8 = Vec::with_capacity(world);
11514 let mut activation_q8_scales = Vec::with_capacity(world);
11515 let mut ev_rank = Vec::with_capacity(world);
11516 let mut phase_head = Vec::with_capacity(world);
11517 let mut phase_copy_done = Vec::with_capacity(world);
11518 let mut phase_gate_up_done = Vec::with_capacity(world);
11519 let mut phase_activation_done = Vec::with_capacity(world);
11520 let mut phase_down_done = Vec::with_capacity(world);
11521 for engine in &self.ranks {
11522 let _main = engine.gpu.enter_main()?;
11523 input.push(engine.uninit(capacity_tokens * experts.input_width)?);
11524 input_bf16.push(engine.alloc_u8_uninit(2 * capacity_tokens * experts.input_width)?);
11525 input_q8.push(engine.alloc_i8_uninit(capacity_tokens * experts.input_width)?);
11526 input_q8_scales
11527 .push(engine.uninit(capacity_tokens * experts.input_width.div_ceil(32))?);
11528 sel.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11529 token_rows.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11530 global_pairs.push(engine.htod_i32(&vec![0i32; capacity_pairs])?);
11531 route_w.push(engine.htod(&vec![0.0f32; capacity_pairs])?);
11532 gate_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11533 up_out.push(engine.uninit(capacity_pairs * experts.expert_width)?);
11534 activation_bf16
11535 .push(engine.alloc_u8_uninit(2 * capacity_pairs * experts.expert_width)?);
11536 activation_q8.push(engine.alloc_i8_uninit(capacity_pairs * experts.expert_width)?);
11537 activation_q8_scales
11538 .push(engine.uninit(capacity_pairs * experts.expert_width.div_ceil(32))?);
11539 ev_rank.push(engine.ctx().new_event(None)?);
11540 if timing {
11541 phase_head.push(
11542 engine.ctx().new_event(Some(
11543 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11544 ))?,
11545 );
11546 phase_copy_done.push(
11547 engine.ctx().new_event(Some(
11548 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11549 ))?,
11550 );
11551 phase_gate_up_done.push(
11552 engine.ctx().new_event(Some(
11553 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11554 ))?,
11555 );
11556 phase_activation_done.push(
11557 engine.ctx().new_event(Some(
11558 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11559 ))?,
11560 );
11561 phase_down_done.push(
11562 engine.ctx().new_event(Some(
11563 cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT,
11564 ))?,
11565 );
11566 }
11567 }
11568 let _main = e.gpu.enter_main()?;
11569 let slot_rows = e.uninit(capacity_pairs * experts.input_width)?;
11570 let slot_rows_raw = {
11571 use cudarc::driver::DevicePtr;
11572 let stream = e.stream();
11573 let (pointer, _guard) = slot_rows.device_ptr(&stream);
11574 pointer
11575 };
11576 *workspace_guard = Some(Nvfp4EpDeviceWorkspace {
11577 input,
11578 input_bf16,
11579 input_q8,
11580 input_q8_scales,
11581 sel,
11582 token_rows,
11583 global_pairs,
11584 route_w,
11585 gate_out,
11586 up_out,
11587 activation_bf16,
11588 activation_q8,
11589 activation_q8_scales,
11590 slot_rows,
11591 slot_rows_raw,
11592 route_weights: e.htod(&vec![0.0f32; capacity_pairs])?,
11593 ev_entry: e.ctx().new_event(None)?,
11594 ev_entry_device: e.ctx().ordinal(),
11595 ev_rank,
11596 phase_events: timing.then_some(Nvfp4EpPhaseEvents {
11597 head: phase_head,
11598 copy_done: phase_copy_done,
11599 gate_up_done: phase_gate_up_done,
11600 activation_done: phase_activation_done,
11601 down_done: phase_down_done,
11602 }),
11603 capacity_tokens,
11604 experts_per_token,
11605 });
11606 }
11607 let workspace = workspace_guard
11608 .as_mut()
11609 .expect("W4A16 device-routed EP workspace initialized above");
11610 if workspace.experts_per_token != experts_per_token || tokens > workspace.capacity_tokens {
11611 return Err(format!(
11612 "W4A16 device-routed EP workspace tokens={} experts/token={} cannot serve \
11613 tokens={tokens} experts/token={experts_per_token}",
11614 workspace.capacity_tokens, workspace.experts_per_token,
11615 )
11616 .into());
11617 }
11618
11619 if tokens <= NVFP4_EP_Q8_BATCH_CAP && parallel_ep_q8_act_enabled()? {
11620 return self.run_routed_experts_nvfp4_w4a8_device_routed(
11621 experts,
11622 e,
11623 input_dev,
11624 selected_dev,
11625 route_weights_dev,
11626 workspace,
11627 tokens,
11628 experts_per_token,
11629 activation_limit,
11630 pre_join,
11631 );
11632 }
11633
11634 {
11635 let _main = e.gpu.enter_main()?;
11636 e.memset_zeros_view(
11637 &mut workspace
11638 .slot_rows
11639 .slice_mut(0..pairs * experts.input_width),
11640 )?;
11641 workspace.ev_entry.record(&e.stream())?;
11642 }
11643
11644 for (rank_index, engine) in self.ranks.iter().enumerate() {
11645 let _main = engine.gpu.enter_main()?;
11646 if let Some(events) = workspace.phase_events.as_ref() {
11647 events.head[rank_index].record(&engine.stream())?;
11648 }
11649 engine.stream().wait(&workspace.ev_entry)?;
11650 let Nvfp4EpDeviceWorkspace {
11651 input_bf16,
11652 sel,
11653 route_w,
11654 ..
11655 } = &mut *workspace;
11656 engine.nvfp4_ep_stage_inputs(
11657 input_dev,
11658 selected_dev,
11659 route_weights_dev,
11660 &mut input_bf16[rank_index],
11661 &mut sel[rank_index],
11662 &mut route_w[rank_index],
11663 active_input_values,
11664 pairs,
11665 false,
11666 )?;
11667 if let Some(events) = workspace.phase_events.as_ref() {
11668 events.copy_done[rank_index].record(&engine.stream())?;
11669 }
11670 let rank = &experts.ranks[rank_index];
11671 let owner_start = rank.expert_range.start;
11672 let owner_end = rank.expert_range.end;
11673 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
11674 &rank.gate,
11675 &rank.up,
11676 &workspace.sel[rank_index],
11677 &workspace.input_bf16[rank_index],
11678 &mut workspace.gate_out[rank_index],
11679 &mut workspace.up_out[rank_index],
11680 pairs,
11681 experts_per_token,
11682 experts.input_width,
11683 experts.expert_width,
11684 owner_start,
11685 owner_end,
11686 experts.gate_row_bytes,
11687 rank.gate_expert_bytes,
11688 )?;
11689 if let Some(events) = workspace.phase_events.as_ref() {
11690 events.gate_up_done[rank_index].record(&engine.stream())?;
11691 }
11692 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
11693 &workspace.gate_out[rank_index],
11694 &workspace.up_out[rank_index],
11695 &rank.macros_gate,
11696 &rank.macros_up,
11697 &workspace.sel[rank_index],
11698 owner_start,
11699 owner_end,
11700 activation_limit,
11701 &mut workspace.activation_bf16[rank_index],
11702 experts.expert_width,
11703 pairs,
11704 )?;
11705 if let Some(events) = workspace.phase_events.as_ref() {
11706 events.activation_done[rank_index].record(&engine.stream())?;
11707 }
11708 if tokens > 1 && pair_down_enabled {
11709 engine.qmatvec_nvfp4_bf16_ep_down_pairs_raw(
11710 &rank.down,
11711 &workspace.sel[rank_index],
11712 &workspace.activation_bf16[rank_index],
11713 &rank.macros_down,
11714 workspace.slot_rows_raw,
11715 pairs,
11716 experts.expert_width,
11717 experts.input_width,
11718 owner_start,
11719 owner_end,
11720 experts.down_row_bytes,
11721 rank.down_expert_bytes,
11722 )?;
11723 } else {
11724 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
11725 &rank.down,
11726 &workspace.sel[rank_index],
11727 &workspace.activation_bf16[rank_index],
11728 &rank.macros_down,
11729 workspace.slot_rows_raw,
11730 pairs,
11731 experts.expert_width,
11732 experts.input_width,
11733 owner_start,
11734 owner_end,
11735 experts.down_row_bytes,
11736 rank.down_expert_bytes,
11737 )?;
11738 }
11739 if let Some(events) = workspace.phase_events.as_ref() {
11740 events.down_done[rank_index].record(&engine.stream())?;
11741 }
11742 workspace.ev_rank[rank_index].record(&engine.stream())?;
11743 }
11744
11745 if let Some(pre_join) = pre_join.as_mut() {
11746 pre_join()?;
11747 }
11748 let issue_ns_this = started
11749 .as_ref()
11750 .map(|started| started.elapsed().as_nanos() as u64);
11751 let join_started = timing.then(std::time::Instant::now);
11752 let output = {
11753 let _main = e.gpu.enter_main()?;
11754 for event in &workspace.ev_rank {
11755 e.stream().wait(event)?;
11756 }
11757 let mut output = e.uninit(tokens * experts.input_width)?;
11758 e.axpy_rows_seq_tokens_into(
11759 &workspace.slot_rows,
11760 route_weights_dev,
11761 &mut output,
11762 experts.input_width,
11763 experts_per_token,
11764 tokens,
11765 )?;
11766 output
11767 };
11768
11769 if let Some(started) = started {
11770 use std::sync::atomic::Ordering;
11771 e.stream().synchronize()?;
11772 let elapsed = started.elapsed().as_nanos() as u64;
11773 let join_ns_this = join_started
11774 .expect("timing join starts with total timing")
11775 .elapsed()
11776 .as_nanos() as u64;
11777 let mut phase_max_ms = [0.0f32; 5];
11778 if let Some(events) = workspace.phase_events.as_ref() {
11779 for rank_index in 0..world {
11780 let engine = &self.ranks[rank_index];
11781 let _main = engine.gpu.enter_main()?;
11782 phase_max_ms[0] = phase_max_ms[0]
11783 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
11784 phase_max_ms[1] = phase_max_ms[1].max(
11785 events.copy_done[rank_index]
11786 .elapsed_ms(&events.gate_up_done[rank_index])?,
11787 );
11788 phase_max_ms[2] = phase_max_ms[2].max(
11789 events.gate_up_done[rank_index]
11790 .elapsed_ms(&events.activation_done[rank_index])?,
11791 );
11792 phase_max_ms[3] = phase_max_ms[3].max(
11793 events.activation_done[rank_index]
11794 .elapsed_ms(&events.down_done[rank_index])?,
11795 );
11796 phase_max_ms[4] = phase_max_ms[4]
11797 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
11798 }
11799 }
11800 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
11801 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
11802 let issue_ns = ISSUE_NS.fetch_add(
11803 issue_ns_this.expect("timing issue starts with total timing"),
11804 Ordering::Relaxed,
11805 ) + issue_ns_this.expect("timing issue starts with total timing");
11806 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
11807 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
11808 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
11809 let activation_ns =
11810 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
11811 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
11812 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
11813 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11814 if calls.is_multiple_of(430) {
11815 eprintln!(
11816 "[nvfp4-ep-device-router-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11817 ns as f64 / 1.0e6,
11818 ns as f64 / calls as f64 / 1.0e3,
11819 );
11820 eprintln!(
11821 "[nvfp4-ep-device-router-phases] calls={calls} issue_us={:.1} \
11822 join_us={:.1} rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
11823 activation_us={:.1} down_us={:.1}",
11824 issue_ns as f64 / calls as f64 / 1.0e3,
11825 join_ns as f64 / calls as f64 / 1.0e3,
11826 rank_span_ns as f64 / calls as f64 / 1.0e3,
11827 copy_ns as f64 / calls as f64 / 1.0e3,
11828 gate_up_ns as f64 / calls as f64 / 1.0e3,
11829 activation_ns as f64 / calls as f64 / 1.0e3,
11830 down_ns as f64 / calls as f64 / 1.0e3,
11831 );
11832 }
11833 }
11834 Ok(output)
11835 }
11836
11837 #[allow(clippy::too_many_arguments)]
11838 fn run_routed_experts_nvfp4_w4a8_device_routed(
11839 &self,
11840 experts: &ResidentNvfp4ExpertParallel,
11841 e: &Engine,
11842 input_dev: &crate::CudaSlice<f32>,
11843 selected_dev: &crate::CudaSlice<i32>,
11844 route_weights_dev: &crate::CudaSlice<f32>,
11845 workspace: &mut Nvfp4EpDeviceWorkspace,
11846 tokens: usize,
11847 experts_per_token: usize,
11848 activation_limit: Option<f32>,
11849 mut pre_join: Option<&mut dyn FnMut() -> Result<(), Box<dyn std::error::Error>>>,
11850 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11851 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11852 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11853 static ISSUE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11854 static JOIN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11855 static COPY_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11856 static GATE_UP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11857 static ACTIVATION_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11858 static DOWN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11859 static RANK_SPAN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11860 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11861 let started = timing.then(std::time::Instant::now);
11862 let pairs = tokens
11863 .checked_mul(experts_per_token)
11864 .ok_or("W4A8 device-routed EP route count overflow")?;
11865 let input_values = tokens
11866 .checked_mul(experts.input_width)
11867 .ok_or("W4A8 device-routed EP input size overflow")?;
11868 let scope = parallel_ep_q8_scope()?.unwrap_or(ParallelEpQ8Scope::All);
11869 let gate_up_paired = parallel_ep_q8_gu_paired_enabled(true, Some(scope));
11870
11871 {
11872 let _main = e.gpu.enter_main()?;
11873 e.memset_zeros_view(
11874 &mut workspace
11875 .slot_rows
11876 .slice_mut(0..pairs * experts.input_width),
11877 )?;
11878 workspace.ev_entry.record(&e.stream())?;
11879 }
11880 for (rank_index, engine) in self.ranks.iter().enumerate() {
11881 let _main = engine.gpu.enter_main()?;
11882 if let Some(events) = workspace.phase_events.as_ref() {
11883 events.head[rank_index].record(&engine.stream())?;
11884 }
11885 engine.stream().wait(&workspace.ev_entry)?;
11886 let rank = &experts.ranks[rank_index];
11887 let owner_start = rank.expert_range.start;
11888 let owner_end = rank.expert_range.end;
11889 match scope {
11890 ParallelEpQ8Scope::All | ParallelEpQ8Scope::GateUp => {
11891 engine.quantize_q8_1_into(
11892 input_dev,
11893 tokens,
11894 experts.input_width,
11895 &mut workspace.input_q8[rank_index],
11896 &mut workspace.input_q8_scales[rank_index],
11897 )?;
11898 engine.moe_sel_w_mirror(
11899 selected_dev,
11900 route_weights_dev,
11901 &mut workspace.sel[rank_index],
11902 &mut workspace.route_w[rank_index],
11903 pairs,
11904 )?;
11905 if let Some(events) = workspace.phase_events.as_ref() {
11906 events.copy_done[rank_index].record(&engine.stream())?;
11907 }
11908 if gate_up_paired {
11909 engine.qmatvec_nvfp4_q8_ep_paired_slots_into(
11910 &rank.gate,
11911 &rank.up,
11912 &workspace.sel[rank_index],
11913 &workspace.input_q8[rank_index],
11914 &workspace.input_q8_scales[rank_index],
11915 &mut workspace.gate_out[rank_index],
11916 &mut workspace.up_out[rank_index],
11917 pairs,
11918 experts_per_token,
11919 experts.input_width,
11920 experts.expert_width,
11921 owner_start,
11922 owner_end,
11923 experts.gate_row_bytes,
11924 rank.gate_expert_bytes,
11925 )?;
11926 } else {
11927 engine.qmatvec_nvfp4_q8_ep_dual_slots_into(
11928 &rank.gate,
11929 &rank.up,
11930 &workspace.sel[rank_index],
11931 &workspace.input_q8[rank_index],
11932 &workspace.input_q8_scales[rank_index],
11933 &mut workspace.gate_out[rank_index],
11934 &mut workspace.up_out[rank_index],
11935 pairs,
11936 experts_per_token,
11937 experts.input_width,
11938 experts.expert_width,
11939 owner_start,
11940 owner_end,
11941 experts.gate_row_bytes,
11942 rank.gate_expert_bytes,
11943 )?;
11944 }
11945 }
11946 ParallelEpQ8Scope::Down => {
11947 engine.nvfp4_ep_stage_inputs(
11948 input_dev,
11949 selected_dev,
11950 route_weights_dev,
11951 &mut workspace.input_bf16[rank_index],
11952 &mut workspace.sel[rank_index],
11953 &mut workspace.route_w[rank_index],
11954 input_values,
11955 pairs,
11956 false,
11957 )?;
11958 if let Some(events) = workspace.phase_events.as_ref() {
11959 events.copy_done[rank_index].record(&engine.stream())?;
11960 }
11961 engine.qmatvec_nvfp4_bf16_ep_dual_slots_into(
11962 &rank.gate,
11963 &rank.up,
11964 &workspace.sel[rank_index],
11965 &workspace.input_bf16[rank_index],
11966 &mut workspace.gate_out[rank_index],
11967 &mut workspace.up_out[rank_index],
11968 pairs,
11969 experts_per_token,
11970 experts.input_width,
11971 experts.expert_width,
11972 owner_start,
11973 owner_end,
11974 experts.gate_row_bytes,
11975 rank.gate_expert_bytes,
11976 )?;
11977 }
11978 }
11979 if let Some(events) = workspace.phase_events.as_ref() {
11980 events.gate_up_done[rank_index].record(&engine.stream())?;
11981 }
11982 match scope {
11983 ParallelEpQ8Scope::All | ParallelEpQ8Scope::Down => {
11984 engine.silu_mul_scaled_host_expf_q8_ep_slots_into(
11985 &workspace.gate_out[rank_index],
11986 &workspace.up_out[rank_index],
11987 &rank.macros_gate,
11988 &rank.macros_up,
11989 &workspace.sel[rank_index],
11990 owner_start,
11991 owner_end,
11992 activation_limit,
11993 &mut workspace.activation_q8[rank_index],
11994 &mut workspace.activation_q8_scales[rank_index],
11995 experts.expert_width,
11996 pairs,
11997 )?;
11998 if let Some(events) = workspace.phase_events.as_ref() {
11999 events.activation_done[rank_index].record(&engine.stream())?;
12000 }
12001 engine.qmatvec_nvfp4_q8_ep_down_slots_raw(
12002 &rank.down,
12003 &workspace.sel[rank_index],
12004 &workspace.activation_q8[rank_index],
12005 &workspace.activation_q8_scales[rank_index],
12006 &rank.macros_down,
12007 workspace.slot_rows_raw,
12008 pairs,
12009 experts.expert_width,
12010 experts.input_width,
12011 owner_start,
12012 owner_end,
12013 experts.down_row_bytes,
12014 rank.down_expert_bytes,
12015 )?;
12016 }
12017 ParallelEpQ8Scope::GateUp => {
12018 engine.silu_mul_scaled_host_expf_bf16_ep_slots_into(
12019 &workspace.gate_out[rank_index],
12020 &workspace.up_out[rank_index],
12021 &rank.macros_gate,
12022 &rank.macros_up,
12023 &workspace.sel[rank_index],
12024 owner_start,
12025 owner_end,
12026 activation_limit,
12027 &mut workspace.activation_bf16[rank_index],
12028 experts.expert_width,
12029 pairs,
12030 )?;
12031 if let Some(events) = workspace.phase_events.as_ref() {
12032 events.activation_done[rank_index].record(&engine.stream())?;
12033 }
12034 engine.qmatvec_nvfp4_bf16_ep_down_slots_raw(
12035 &rank.down,
12036 &workspace.sel[rank_index],
12037 &workspace.activation_bf16[rank_index],
12038 &rank.macros_down,
12039 workspace.slot_rows_raw,
12040 pairs,
12041 experts.expert_width,
12042 experts.input_width,
12043 owner_start,
12044 owner_end,
12045 experts.down_row_bytes,
12046 rank.down_expert_bytes,
12047 )?;
12048 }
12049 }
12050 if let Some(events) = workspace.phase_events.as_ref() {
12051 events.down_done[rank_index].record(&engine.stream())?;
12052 }
12053 workspace.ev_rank[rank_index].record(&engine.stream())?;
12054 }
12055
12056 if let Some(pre_join) = pre_join.as_mut() {
12057 pre_join()?;
12058 }
12059 let issue_ns_this = started
12060 .as_ref()
12061 .map(|started| started.elapsed().as_nanos() as u64);
12062 let join_started = timing.then(std::time::Instant::now);
12063 let output = {
12064 let _main = e.gpu.enter_main()?;
12065 for event in &workspace.ev_rank {
12066 e.stream().wait(event)?;
12067 }
12068 let mut output = e.uninit(input_values)?;
12069 e.axpy_rows_seq_tokens_into(
12070 &workspace.slot_rows,
12071 route_weights_dev,
12072 &mut output,
12073 experts.input_width,
12074 experts_per_token,
12075 tokens,
12076 )?;
12077 output
12078 };
12079 if let Some(started) = started {
12080 use std::sync::atomic::Ordering;
12081 e.stream().synchronize()?;
12082 let elapsed = started.elapsed().as_nanos() as u64;
12083 let join_ns_this = join_started
12084 .expect("timing join starts with total timing")
12085 .elapsed()
12086 .as_nanos() as u64;
12087 let mut phase_max_ms = [0.0f32; 5];
12088 if let Some(events) = workspace.phase_events.as_ref() {
12089 for rank_index in 0..self.ranks.len() {
12090 let engine = &self.ranks[rank_index];
12091 let _main = engine.gpu.enter_main()?;
12092 phase_max_ms[0] = phase_max_ms[0]
12093 .max(events.head[rank_index].elapsed_ms(&events.copy_done[rank_index])?);
12094 phase_max_ms[1] = phase_max_ms[1].max(
12095 events.copy_done[rank_index]
12096 .elapsed_ms(&events.gate_up_done[rank_index])?,
12097 );
12098 phase_max_ms[2] = phase_max_ms[2].max(
12099 events.gate_up_done[rank_index]
12100 .elapsed_ms(&events.activation_done[rank_index])?,
12101 );
12102 phase_max_ms[3] = phase_max_ms[3].max(
12103 events.activation_done[rank_index]
12104 .elapsed_ms(&events.down_done[rank_index])?,
12105 );
12106 phase_max_ms[4] = phase_max_ms[4]
12107 .max(events.head[rank_index].elapsed_ms(&events.down_done[rank_index])?);
12108 }
12109 }
12110 let phase_ns = phase_max_ms.map(|ms| (ms as f64 * 1.0e6) as u64);
12111 let ns = TIMING_NS.fetch_add(elapsed, Ordering::Relaxed) + elapsed;
12112 let issue_ns = ISSUE_NS.fetch_add(
12113 issue_ns_this.expect("timing issue starts with total timing"),
12114 Ordering::Relaxed,
12115 ) + issue_ns_this.expect("timing issue starts with total timing");
12116 let join_ns = JOIN_NS.fetch_add(join_ns_this, Ordering::Relaxed) + join_ns_this;
12117 let copy_ns = COPY_NS.fetch_add(phase_ns[0], Ordering::Relaxed) + phase_ns[0];
12118 let gate_up_ns = GATE_UP_NS.fetch_add(phase_ns[1], Ordering::Relaxed) + phase_ns[1];
12119 let activation_ns =
12120 ACTIVATION_NS.fetch_add(phase_ns[2], Ordering::Relaxed) + phase_ns[2];
12121 let down_ns = DOWN_NS.fetch_add(phase_ns[3], Ordering::Relaxed) + phase_ns[3];
12122 let rank_span_ns = RANK_SPAN_NS.fetch_add(phase_ns[4], Ordering::Relaxed) + phase_ns[4];
12123 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12124 if calls.is_multiple_of(430) {
12125 eprintln!(
12126 "[nvfp4-ep-q8-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12127 ns as f64 / 1.0e6,
12128 ns as f64 / calls as f64 / 1.0e3,
12129 );
12130 eprintln!(
12131 "[nvfp4-ep-q8-phases] calls={calls} issue_us={:.1} join_us={:.1} \
12132 rank_span_us={:.1} copy_us={:.1} gate_up_us={:.1} \
12133 activation_us={:.1} down_us={:.1}",
12134 issue_ns as f64 / calls as f64 / 1.0e3,
12135 join_ns as f64 / calls as f64 / 1.0e3,
12136 rank_span_ns as f64 / calls as f64 / 1.0e3,
12137 copy_ns as f64 / calls as f64 / 1.0e3,
12138 gate_up_ns as f64 / calls as f64 / 1.0e3,
12139 activation_ns as f64 / calls as f64 / 1.0e3,
12140 down_ns as f64 / calls as f64 / 1.0e3,
12141 );
12142 }
12143 }
12144 static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
12145 if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
12146 let (expert_input, post_activation, numeric_class) = match scope {
12147 ParallelEpQ8Scope::All => ("q8_1", "q8_1", "w4a8-internal"),
12148 ParallelEpQ8Scope::GateUp => ("q8_1", "bf16", "w4a8-gate-up-internal"),
12149 ParallelEpQ8Scope::Down => ("bf16", "q8_1", "w4a8-down-internal"),
12150 };
12151 eprintln!(
12152 "[parallel-ep-q8] devices={:?} tokens={tokens} scope={} \
12153 expert_input={expert_input} post_activation={post_activation} \
12154 gate_up_schedule={} \
12155 external_boundary=bf16 numeric_class={numeric_class} \
12156 host_expf=true accumulation=token-slot-order performance_claim=false",
12157 self.devices,
12158 scope.label(),
12159 if gate_up_paired {
12160 "paired-cta"
12161 } else {
12162 "separate-cta"
12163 },
12164 );
12165 }
12166 Ok(output)
12167 }
12168
12169 pub fn run_tensor_parallel_routes_nvfp4_device(
12183 &self,
12184 experts: &ResidentNvfp4TensorParallel,
12185 input: &[f32],
12186 selected: &[usize],
12187 route_weights: &[f32],
12188 experts_per_token: usize,
12189 activation_limit: Option<f32>,
12190 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12191 validate_activations(input, 1, experts.input_width)?;
12192 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12193 return Err(format!(
12194 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
12195 selected.len(),
12196 route_weights.len(),
12197 )
12198 .into());
12199 }
12200 if !route_weights.iter().all(|weight| weight.is_finite()) {
12201 return Err("NVFP4 device route weights contain a non-finite value".into());
12202 }
12203 let world = self.ranks.len();
12204 if world != NVFP4_CANONICAL_ROW_SHARDS {
12205 return Err(format!(
12206 "NVFP4 device routes require world == canonical shard grid \
12207 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12208 )
12209 .into());
12210 }
12211 let local_out = experts.expert_width / world;
12212
12213 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12217 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12218 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12219 let started = timing.then(std::time::Instant::now);
12220
12221 let n_sel = experts_per_token;
12222 let mut workspace_guard = experts
12223 .device_workspace
12224 .lock()
12225 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12226 if workspace_guard.is_none() {
12227 let mut gate_out = Vec::with_capacity(world);
12228 let mut up_out = Vec::with_capacity(world);
12229 let mut act_q = Vec::with_capacity(world);
12230 let mut act_d = Vec::with_capacity(world);
12231 let mut sel = Vec::with_capacity(world);
12232 let mut partial = Vec::with_capacity(world);
12233 let mut accumulator = Vec::with_capacity(world);
12234 let mut combine_w = Vec::with_capacity(world);
12235 let mut route_w = Vec::with_capacity(world);
12236 let mut in_q = Vec::with_capacity(world);
12237 let mut in_d = Vec::with_capacity(world);
12238 let mut input = Vec::with_capacity(world);
12239 let mut ev_rank = Vec::with_capacity(world);
12240 let moe_direct = moe_direct_on();
12241 for (rank, engine) in self.ranks.iter().enumerate() {
12242 let _main = engine.gpu.enter_main()?;
12243 gate_out.push(engine.uninit(n_sel * local_out)?);
12244 up_out.push(engine.uninit(n_sel * local_out)?);
12245 act_q.push(engine.uninit_i8(n_sel * local_out)?);
12246 act_d.push(engine.uninit(n_sel * local_out / 32)?);
12247 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
12248 partial.push(engine.uninit(n_sel * experts.input_width)?);
12249 if moe_direct && rank != 0 {
12251 let root = &self.ranks[0];
12252 let _root_main = root.gpu.enter_main()?;
12253 accumulator.push(root.zeros(experts.input_width)?);
12254 } else {
12255 accumulator.push(engine.zeros(experts.input_width)?);
12256 }
12257 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12258 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
12259 in_q.push(engine.uninit_i8(experts.input_width)?);
12260 in_d.push(engine.uninit(experts.input_width / 32)?);
12261 input.push(engine.uninit(experts.input_width)?);
12262 ev_rank.push(engine.ctx().new_event(None)?);
12263 }
12264 let root = &self.ranks[0];
12265 let _main = root.gpu.enter_main()?;
12266 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
12267 prestaged: false,
12268 rank1_routed: false,
12269 ev_input: None,
12270 fence_flags_raw: 0,
12271 fence_ticket: 0,
12272 gate_out,
12273 up_out,
12274 act_q,
12275 act_d,
12276 sel,
12277 partial,
12278 accumulator,
12279 combine_w,
12280 route_w,
12281 in_q,
12282 in_d,
12283 dev_route_e: None,
12284 in_stage_e: None,
12285 out_stage_e: None,
12286 routes_graph: None,
12287 raw_dev_route_e: None,
12288 raw_combine: None,
12289 raw_input: Vec::new(),
12290 raw_sel: Vec::new(),
12291 raw_route_w: Vec::new(),
12292 remote: root.uninit(experts.input_width)?,
12293 combined: root.uninit(experts.input_width)?,
12294 n_sel,
12295 input,
12296 ev_rank,
12297 ev_done: Some(root.ctx().new_event(None)?),
12298 ev_entry: None,
12299 });
12300 }
12301 let workspace = workspace_guard
12302 .as_mut()
12303 .expect("NVFP4 device routes workspace initialized above");
12304 if workspace.n_sel != n_sel {
12305 return Err(format!(
12306 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12307 workspace.n_sel
12308 )
12309 .into());
12310 }
12311 for &expert in selected {
12312 if expert >= experts.expert_count {
12313 return Err(format!(
12314 "NVFP4 device selected expert {expert} outside 0..{}",
12315 experts.expert_count
12316 )
12317 .into());
12318 }
12319 }
12320 let sel_i32 = selected
12321 .iter()
12322 .map(|&expert| expert as i32)
12323 .collect::<Vec<_>>();
12324
12325 for (rank_index, engine) in self.ranks.iter().enumerate() {
12332 let _main = engine.gpu.enter_main()?;
12333 let device_input = engine.htod(input)?;
12334 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
12335 engine.quantize_q8_1_into(
12336 &device_input,
12337 1,
12338 experts.input_width,
12339 &mut in_q[rank_index],
12340 &mut in_d[rank_index],
12341 )?;
12342 }
12344 self.nvfp4_routes_batched_sweeps(
12345 experts,
12346 workspace,
12347 selected,
12348 route_weights,
12349 &sel_i32,
12350 local_out,
12351 n_sel,
12352 activation_limit,
12353 false,
12354 )?;
12355
12356 let root = &self.ranks[0];
12359 for engine in &self.ranks[1..] {
12360 let _main = engine.gpu.enter_main()?;
12361 engine.stream().synchronize()?;
12362 }
12363 let _main = root.gpu.enter_main()?;
12364 root.stream()
12365 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12366 root.add(
12367 &workspace.accumulator[0],
12368 &workspace.remote,
12369 &mut workspace.combined,
12370 experts.input_width,
12371 )?;
12372 let output = root.dtoh(&workspace.combined)?;
12373 if let Some(started) = started {
12374 use std::sync::atomic::Ordering;
12375 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12376 + started.elapsed().as_nanos() as u64;
12377 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12378 if calls.is_multiple_of(430) {
12379 eprintln!(
12380 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12381 ns as f64 / 1.0e6,
12382 ns as f64 / calls as f64 / 1.0e3,
12383 );
12384 }
12385 }
12386 Ok(output)
12387 }
12388
12389 #[allow(clippy::too_many_arguments)]
12394 fn nvfp4_routes_batched_sweeps(
12395 &self,
12396 experts: &ResidentNvfp4TensorParallel,
12397 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12398 selected: &[usize],
12399 route_weights: &[f32],
12400 sel_i32: &[i32],
12401 local_out: usize,
12402 n_sel: usize,
12403 activation_limit: Option<f32>,
12404 device_routed: bool,
12405 ) -> Result<(), Box<dyn std::error::Error>> {
12406 for rank_index in 0..self.ranks.len() {
12407 self.nvfp4_routes_batched_sweeps_rank(
12408 experts,
12409 workspace,
12410 selected,
12411 route_weights,
12412 sel_i32,
12413 local_out,
12414 n_sel,
12415 activation_limit,
12416 device_routed,
12417 rank_index,
12418 )?;
12419 }
12420 Ok(())
12421 }
12422
12423 #[allow(clippy::too_many_arguments)]
12426 fn nvfp4_routes_batched_sweeps_rank(
12427 &self,
12428 experts: &ResidentNvfp4TensorParallel,
12429 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12430 selected: &[usize],
12431 route_weights: &[f32],
12432 sel_i32: &[i32],
12433 local_out: usize,
12434 n_sel: usize,
12435 activation_limit: Option<f32>,
12436 device_routed: bool,
12437 rank_index: usize,
12438 ) -> Result<(), Box<dyn std::error::Error>> {
12439 {
12440 let engine = &self.ranks[rank_index];
12441 let _main = engine.gpu.enter_main()?;
12442 if !device_routed {
12443 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
12444 let folded = (0..n_sel)
12447 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
12448 .collect::<Vec<_>>();
12449 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
12450 engine.stream().memcpy_htod(&folded, &mut view)?;
12451 }
12452 let gate_bank = &experts.gate[rank_index];
12453 let up_bank = &experts.up[rank_index];
12454 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
12455 engine.qmatvec_nvfp4_sel_into(
12456 &gate_bank.bank,
12457 &workspace.sel[rank_index],
12458 aq,
12459 ad,
12460 &mut workspace.gate_out[rank_index],
12461 n_sel,
12462 gate_bank.in_features,
12463 gate_bank.local_out,
12464 gate_bank.row_bytes,
12465 gate_bank.expert_bytes,
12466 0,
12467 0,
12468 gate_bank.slot_major,
12469 )?;
12470 engine.qmatvec_nvfp4_sel_into(
12471 &up_bank.bank,
12472 &workspace.sel[rank_index],
12473 aq,
12474 ad,
12475 &mut workspace.up_out[rank_index],
12476 n_sel,
12477 up_bank.in_features,
12478 up_bank.local_out,
12479 up_bank.row_bytes,
12480 up_bank.expert_bytes,
12481 0,
12482 0,
12483 up_bank.slot_major,
12484 )?;
12485 {
12489 let Nvfp4DeviceRoutesWorkspace {
12490 gate_out,
12491 up_out,
12492 sel,
12493 act_q,
12494 act_d,
12495 ..
12496 } = &mut *workspace;
12497 engine.silu_mul_scaled_q8_1_sel_into(
12498 &gate_out[rank_index],
12499 &up_out[rank_index],
12500 &experts.macros_gate_dev[rank_index],
12501 &experts.macros_up_dev[rank_index],
12502 &sel[rank_index],
12503 activation_limit,
12504 &mut act_q[rank_index],
12505 &mut act_d[rank_index],
12506 local_out,
12507 n_sel,
12508 )?;
12509 }
12510 let shard = &experts.down[rank_index];
12511 if shard.device_rank != rank_index || shard.local_in != local_out {
12512 return Err(
12513 "NVFP4 device routes: down canonical shard placement drifted from \
12514 the gate/up column split"
12515 .into(),
12516 );
12517 }
12518 let down8 =
12526 device_routed && sel_down8_on() && shard.slot_major && (shard.local_in >> 5) <= 32;
12527 {
12532 static SEEN_D8: std::sync::Mutex<Vec<(bool, bool, bool, bool)>> =
12533 std::sync::Mutex::new(Vec::new());
12534 let combo = (down8, sel_down8_on(), device_routed, shard.slot_major);
12535 let mut seen = SEEN_D8.lock().unwrap();
12536 if !seen.contains(&combo) {
12537 seen.push(combo);
12538 eprintln!(
12544 "[nvfp4-sweep] down8={} door={} door_source={} device_routed={} \
12545 slot_major={} nsb={} in_class={} n_sel={n_sel}",
12546 down8,
12547 sel_down8_on(),
12548 sel_down8_source().1,
12549 device_routed,
12550 shard.slot_major,
12551 shard.local_in >> 5,
12552 (shard.local_in >> 5) <= 32
12553 );
12554 }
12555 }
12556 if down8 {
12557 let Nvfp4DeviceRoutesWorkspace {
12558 sel,
12559 act_q,
12560 act_d,
12561 route_w,
12562 accumulator,
12563 ..
12564 } = &mut *workspace;
12565 engine.qmatvec_nvfp4_sel_down8_into(
12566 &shard.bank,
12567 &sel[rank_index],
12568 &act_q[rank_index],
12569 &act_d[rank_index],
12570 &route_w[rank_index],
12571 &experts.macros_down_dev[rank_index],
12572 &mut accumulator[rank_index],
12573 n_sel,
12574 shard.local_in,
12575 shard.out_features,
12576 shard.row_bytes,
12577 shard.expert_bytes,
12578 local_out,
12579 local_out / 32,
12580 shard.slot_major,
12581 )?;
12582 } else {
12583 let Nvfp4DeviceRoutesWorkspace {
12584 sel,
12585 act_q,
12586 act_d,
12587 partial,
12588 ..
12589 } = &mut *workspace;
12590 engine.qmatvec_nvfp4_sel_into(
12591 &shard.bank,
12592 &sel[rank_index],
12593 &act_q[rank_index],
12594 &act_d[rank_index],
12595 &mut partial[rank_index],
12596 n_sel,
12597 shard.local_in,
12598 shard.out_features,
12599 shard.row_bytes,
12600 shard.expert_bytes,
12601 local_out,
12602 local_out / 32,
12603 shard.slot_major,
12604 )?;
12605 }
12606 if !down8 {
12611 let Nvfp4DeviceRoutesWorkspace {
12612 partial,
12613 combine_w,
12614 route_w,
12615 sel,
12616 accumulator,
12617 ..
12618 } = &mut *workspace;
12619 if device_routed {
12620 engine.axpy_rows_seq_md_into(
12621 &partial[rank_index],
12622 &route_w[rank_index],
12623 &experts.macros_down_dev[rank_index],
12624 &sel[rank_index],
12625 &mut accumulator[rank_index],
12626 experts.input_width,
12627 n_sel,
12628 )?;
12629 } else {
12630 engine.axpy_rows_seq_into(
12631 &partial[rank_index],
12632 &combine_w[rank_index],
12633 &mut accumulator[rank_index],
12634 experts.input_width,
12635 n_sel,
12636 )?;
12637 }
12638 }
12639 }
12640 Ok(())
12641 }
12642
12643 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_io(
12652 &self,
12653 experts: &ResidentNvfp4TensorParallel,
12654 e: &Engine,
12655 input_dev: &crate::CudaSlice<f32>,
12656 selected: &[usize],
12657 route_weights: &[f32],
12658 experts_per_token: usize,
12659 activation_limit: Option<f32>,
12660 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12661 if input_dev.len() != experts.input_width {
12662 return Err(format!(
12663 "NVFP4 device-io routes input {} != width {}",
12664 input_dev.len(),
12665 experts.input_width
12666 )
12667 .into());
12668 }
12669 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
12670 return Err(format!(
12671 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
12672 selected.len(),
12673 route_weights.len(),
12674 )
12675 .into());
12676 }
12677 if !route_weights.iter().all(|weight| weight.is_finite()) {
12678 return Err("NVFP4 device route weights contain a non-finite value".into());
12679 }
12680 let world = self.ranks.len();
12681 if world != NVFP4_CANONICAL_ROW_SHARDS {
12682 return Err(format!(
12683 "NVFP4 device routes require world == canonical shard grid \
12684 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12685 )
12686 .into());
12687 }
12688 let local_out = experts.expert_width / world;
12689 let n_sel = experts_per_token;
12690
12691 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12692 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12693 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12694 let started = timing.then(std::time::Instant::now);
12695
12696 let mut workspace_guard = experts
12697 .device_workspace
12698 .lock()
12699 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12700 if workspace_guard.is_none() {
12701 drop(workspace_guard);
12702 let zero = vec![0.0f32; experts.input_width];
12705 let zero_sel = vec![0usize; n_sel];
12706 let zero_w = vec![0.0f32; n_sel];
12707 let _ = self.run_tensor_parallel_routes_nvfp4_device(
12708 experts,
12709 &zero,
12710 &zero_sel,
12711 &zero_w,
12712 n_sel,
12713 activation_limit,
12714 )?;
12715 workspace_guard = experts
12716 .device_workspace
12717 .lock()
12718 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12719 }
12720 let workspace = workspace_guard
12721 .as_mut()
12722 .expect("NVFP4 device routes workspace initialized above");
12723 if workspace.n_sel != n_sel {
12724 return Err(format!(
12725 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12726 workspace.n_sel
12727 )
12728 .into());
12729 }
12730 for &expert in selected {
12731 if expert >= experts.expert_count {
12732 return Err(format!(
12733 "NVFP4 device selected expert {expert} outside 0..{}",
12734 experts.expert_count
12735 )
12736 .into());
12737 }
12738 }
12739 let sel_i32 = selected
12740 .iter()
12741 .map(|&expert| expert as i32)
12742 .collect::<Vec<_>>();
12743
12744 if let Some((_, device)) = workspace.ev_entry.as_ref() {
12748 if *device != e.ctx().ordinal() {
12749 return Err("NVFP4 device-io routes engine changed".into());
12750 }
12751 } else {
12752 let _main = e.gpu.enter_main()?;
12753 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
12754 }
12755 {
12756 let _main = e.gpu.enter_main()?;
12757 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
12758 ev_entry.record(&e.stream())?;
12759 }
12760 for (rank_index, engine) in self.ranks.iter().enumerate() {
12761 let _main = engine.gpu.enter_main()?;
12762 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
12763 engine.stream().wait(ev_entry)?;
12764 {
12765 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
12766 engine
12767 .stream()
12768 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
12769 }
12770 {
12771 let Nvfp4DeviceRoutesWorkspace {
12772 input, in_q, in_d, ..
12773 } = &mut *workspace;
12774 engine.quantize_q8_1_into(
12775 &input[rank_index],
12776 1,
12777 experts.input_width,
12778 &mut in_q[rank_index],
12779 &mut in_d[rank_index],
12780 )?;
12781 }
12782 }
12783 self.nvfp4_routes_batched_sweeps(
12784 experts,
12785 workspace,
12786 selected,
12787 route_weights,
12788 &sel_i32,
12789 local_out,
12790 n_sel,
12791 activation_limit,
12792 false,
12793 )?;
12794
12795 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
12801 let _main = engine.gpu.enter_main()?;
12802 workspace.ev_rank[rank_index].record(&engine.stream())?;
12803 }
12804 if moe_direct_on() && self.ranks.len() == 2 {
12805 {
12812 let root = &self.ranks[0];
12813 let _main = root.gpu.enter_main()?;
12814 workspace
12815 .ev_done
12816 .as_ref()
12817 .expect("device routes done event")
12818 .record(&root.stream())?;
12819 }
12820 let _main = e.gpu.enter_main()?;
12821 e.stream().wait(
12822 workspace
12823 .ev_done
12824 .as_ref()
12825 .expect("device routes done event"),
12826 )?;
12827 for ev in workspace.ev_rank.iter().skip(1) {
12828 e.stream().wait(ev)?;
12829 }
12830 let mut output = e.uninit(experts.input_width)?;
12831 e.add(
12832 &workspace.accumulator[0],
12833 &workspace.accumulator[1],
12834 &mut output,
12835 experts.input_width,
12836 )?;
12837 let output = output;
12838 if let Some(started) = started {
12839 use std::sync::atomic::Ordering;
12840 let ns = TIMING_NS
12841 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12842 + started.elapsed().as_nanos() as u64;
12843 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12844 if calls.is_multiple_of(430) {
12845 eprintln!(
12846 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12847 ns as f64 / 1.0e6,
12848 ns as f64 / calls as f64 / 1.0e3,
12849 );
12850 }
12851 }
12852 return Ok(output);
12853 }
12854 {
12855 let root = &self.ranks[0];
12856 let _main = root.gpu.enter_main()?;
12857 for ev in workspace.ev_rank.iter().skip(1) {
12858 root.stream().wait(ev)?;
12859 }
12860 root.stream()
12861 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12862 {
12863 let Nvfp4DeviceRoutesWorkspace {
12864 accumulator,
12865 remote,
12866 combined,
12867 ..
12868 } = &mut *workspace;
12869 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12870 }
12871 workspace
12872 .ev_done
12873 .as_ref()
12874 .expect("device routes done event")
12875 .record(&root.stream())?;
12876 }
12877 let output = {
12878 let _main = e.gpu.enter_main()?;
12879 e.stream().wait(
12880 workspace
12881 .ev_done
12882 .as_ref()
12883 .expect("device routes done event"),
12884 )?;
12885 let mut output = e.uninit(experts.input_width)?;
12888 e.stream().memcpy_dtod(
12889 &workspace.combined.slice(0..experts.input_width),
12890 &mut output.slice_mut(0..experts.input_width),
12891 )?;
12892 output
12893 };
12894 if let Some(started) = started {
12895 use std::sync::atomic::Ordering;
12896 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12897 + started.elapsed().as_nanos() as u64;
12898 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12899 if calls.is_multiple_of(430) {
12900 eprintln!(
12901 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12902 ns as f64 / 1.0e6,
12903 ns as f64 / calls as f64 / 1.0e3,
12904 );
12905 }
12906 }
12907 Ok(output)
12908 }
12909
12910 #[allow(clippy::too_many_arguments)]
12916 pub fn nvfp4_routes_prestage(
12921 &self,
12922 experts: &ResidentNvfp4TensorParallel,
12923 e: &Engine,
12924 input_dev: &crate::CudaSlice<f32>,
12925 ) -> Result<bool, Box<dyn std::error::Error>> {
12926 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
12927 }
12928
12929 pub fn nvfp4_routes_prestage_with(
12935 &self,
12936 experts: &ResidentNvfp4TensorParallel,
12937 e: &Engine,
12938 input_dev: &crate::CudaSlice<f32>,
12939 rank1_router: impl FnOnce(
12940 &Engine,
12941 &crate::CudaSlice<f32>,
12942 &mut crate::CudaSlice<i32>,
12943 &mut crate::CudaSlice<f32>,
12944 ) -> Result<bool, Box<dyn std::error::Error>>,
12945 ) -> Result<bool, Box<dyn std::error::Error>> {
12946 if !routes_prestage_on() || step_tp_graph_enabled()? {
12947 return Ok(false);
12948 }
12949 if input_dev.len() != experts.input_width {
12950 return Err("NVFP4 prestage input width mismatch".into());
12951 }
12952 let mut workspace_guard = experts
12953 .device_workspace
12954 .lock()
12955 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12956 let Some(workspace) = workspace_guard.as_mut() else {
12957 return Ok(false);
12958 };
12959 if workspace.ev_input.is_none() {
12960 let _main = e.gpu.enter_main()?;
12961 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
12962 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
12963 return Err("NVFP4 prestage engine changed".into());
12964 }
12965 {
12966 let _main = e.gpu.enter_main()?;
12967 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
12968 ev.record(&e.stream())?;
12969 }
12970 for (rank_index, engine) in self.ranks.iter().enumerate() {
12971 let _main = engine.gpu.enter_main()?;
12972 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
12973 engine.stream().wait(ev)?;
12974 {
12975 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
12976 engine
12977 .stream()
12978 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
12979 }
12980 {
12981 let Nvfp4DeviceRoutesWorkspace {
12982 input, in_q, in_d, ..
12983 } = &mut *workspace;
12984 engine.quantize_q8_1_into(
12985 &input[rank_index],
12986 1,
12987 experts.input_width,
12988 &mut in_q[rank_index],
12989 &mut in_d[rank_index],
12990 )?;
12991 }
12992 }
12993 if self.ranks.len() == 2 {
12994 let rank1 = &self.ranks[1];
12995 let _r1 = rank1.gpu.enter_main()?;
12996 let Nvfp4DeviceRoutesWorkspace {
12997 input,
12998 sel,
12999 route_w,
13000 ..
13001 } = &mut *workspace;
13002 let (in1, rest_sel) = (&input[1], &mut sel[1]);
13003 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
13004 workspace.rank1_routed = true;
13005 }
13006 }
13007 workspace.prestaged = true;
13008 Ok(true)
13009 }
13010
13011 #[allow(clippy::too_many_arguments)]
13028 fn determ_stage_bytes(v: &[u8]) -> u64 {
13037 v.iter().fold(0u64, |a, b| {
13038 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
13039 })
13040 }
13041
13042 fn determ_stage_i32(v: &[i32]) -> u64 {
13047 v.iter().fold(0u64, |a, b| {
13048 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
13049 })
13050 }
13051
13052 fn determ_stage_sum(v: &[f32]) -> u64 {
13053 v.iter().fold(0u64, |a, x| {
13054 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
13055 })
13056 }
13057
13058 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
13060 &self,
13061 experts: &ResidentNvfp4TensorParallel,
13062 e: &Engine,
13063 z_t: &crate::CudaSlice<f32>,
13064 t: usize,
13065 sel: &[i32],
13066 w: &[f32],
13067 n_used: usize,
13068 activation_limit: Option<f32>,
13069 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13070 let world = self.ranks.len();
13071 if world != NVFP4_CANONICAL_ROW_SHARDS {
13072 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
13073 }
13074 let slot_major = experts.gate.iter().all(|b| b.slot_major)
13088 && experts.up.iter().all(|b| b.slot_major)
13089 && experts.down.iter().all(|b| b.slot_major);
13090 let any_slot_major = experts.gate.iter().any(|b| b.slot_major)
13091 || experts.up.iter().any(|b| b.slot_major)
13092 || experts.down.iter().any(|b| b.slot_major);
13093 if any_slot_major != slot_major {
13094 return Err(
13095 "NVFP4 grouped prime: gate/up/down banks disagree on the row layout — \
13096 one grouped GEMM cannot serve two byte maps"
13097 .into(),
13098 );
13099 }
13100 let bank_qt = if slot_major {
13101 crate::QT_NVFP4_V2
13102 } else {
13103 crate::QT_NVFP4
13104 };
13105 let width = experts.input_width;
13106 let n_expert = experts.expert_count;
13107 let n_pairs = t * n_used;
13108 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
13109 return Err("NVFP4 grouped prime geometry".into());
13110 }
13111 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
13120 let g_t0 = std::time::Instant::now();
13121 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
13124 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
13125 let s_id = s_id as usize;
13126 if s_id >= n_expert {
13127 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
13128 }
13129 buckets[s_id].push(p as i32);
13130 }
13131 let mut ex_ids: Vec<i32> = Vec::new();
13132 let mut ex_off: Vec<i32> = vec![0];
13133 let mut ex_pairs: Vec<i32> = Vec::new();
13134 for (e_id, b) in buckets.iter().enumerate() {
13135 if !b.is_empty() {
13136 ex_ids.push(e_id as i32);
13137 ex_pairs.extend_from_slice(b);
13138 ex_off.push(ex_pairs.len() as i32);
13139 }
13140 }
13141 let n_active = ex_ids.len();
13142 if n_active == 0 {
13143 return e.zeros(t * width);
13144 }
13145 if n_active > 512 {
13146 return Err("grouped prime n_active > 512 (direct lane cap)".into());
13147 }
13148 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
13149 let mut inv = vec![0i32; n_pairs];
13152 for (row, &pair) in ex_pairs.iter().enumerate() {
13153 inv[pair as usize] = row as i32;
13154 }
13155 let mg: Vec<f32> = ex_pairs
13157 .iter()
13158 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
13159 .collect();
13160 let mu: Vec<f32> = ex_pairs
13161 .iter()
13162 .map(|&p| experts.macros_up[sel[p as usize] as usize])
13163 .collect();
13164 let wd: Vec<f32> = (0..n_pairs)
13165 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
13166 .collect();
13167 {
13171 let mut tabs = experts
13172 .prime_tables
13173 .lock()
13174 .map_err(|_| "grouped prime table cache is poisoned")?;
13175 if tabs.len() != world {
13176 tabs.clear();
13177 for rank in 0..world {
13178 let engine = &self.ranks[rank];
13179 let _main = engine.gpu.enter_main()?;
13180 let (gb, ub, db) =
13181 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
13182 let mut tab = vec![0u64; 3 * n_expert];
13183 {
13184 use cudarc::driver::DevicePtr;
13185 let stream = engine.stream();
13186 let (pg, _g0) = gb.bank.device_ptr(&stream);
13187 let (pu, _g1) = ub.bank.device_ptr(&stream);
13188 let (pd, _g2) = db.bank.device_ptr(&stream);
13189 for ex in 0..n_expert {
13190 tab[ex] = pg + (ex * gb.expert_bytes) as u64;
13191 tab[n_expert + ex] = pu + (ex * ub.expert_bytes) as u64;
13192 tab[2 * n_expert + ex] = pd + (ex * db.expert_bytes) as u64;
13193 }
13194 }
13195 tabs.push(engine.htod_u64(&tab)?);
13196 }
13197 }
13198 }
13199 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
13200 let g_t1 = std::time::Instant::now();
13201 {
13208 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
13209 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13210 for rank in 0..world {
13211 let e_r = &self.ranks[rank];
13212 let _m = e_r.gpu.enter_main();
13213 eprintln!(
13214 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
13215 root_stream={:?}",
13216 e_r.ctx().ordinal(),
13217 std::sync::Arc::as_ptr(e_r.ctx()),
13218 e_r.stream().cu_stream(),
13219 e.ctx().ordinal(),
13220 e.stream().cu_stream(),
13221 );
13222 }
13223 }
13224 }
13225
13226 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
13227 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
13228 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
13229 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
13230 for rank in 0..world {
13231 let engine = &self.ranks[rank];
13232 let _main = engine.gpu.enter_main()?;
13233 if gprof {
13234 let h = engine
13239 .ctx()
13240 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13241 h.record(&engine.stream())?;
13242 ev_head.push(h);
13243 }
13244 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
13247 let gb = &experts.gate[rank];
13248 let ub = &experts.up[rank];
13249 let db = &experts.down[rank];
13250 if db.device_rank != rank {
13251 return Err("grouped prime: down shard placement drifted".into());
13252 }
13253 let local_ff = gb.local_out;
13254 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
13255 return Err("grouped prime: bank width mismatch".into());
13256 }
13257 let csr_tok_d = engine.htod_i32(&csr_tok)?;
13260 let exi_d = engine.htod_i32(&ex_ids)?;
13261 let exoff_d = engine.htod_i32(&ex_off)?;
13262 let mg_d = engine.htod(&mg)?;
13263 let mu_d = engine.htod(&mu)?;
13264 let tabs_guard = experts
13266 .prime_tables
13267 .lock()
13268 .map_err(|_| "grouped prime table cache is poisoned")?;
13269 let tab_d = &tabs_guard[rank];
13270 let mut z_r = engine.uninit(t * width)?;
13271 {
13272 let mut dst = z_r.slice_mut(0..t * width);
13273 engine
13274 .stream()
13275 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
13276 }
13277 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
13278 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
13279 if dstage {
13280 let zr = engine.dtoh(&z_r)?;
13284 let zsv = engine.dtoh(&zs)?;
13285 let z16v = engine.dtoh_u8(&z16)?;
13286 eprintln!(
13287 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
13288 Self::determ_stage_sum(&zr),
13289 Self::determ_stage_sum(&zsv),
13290 Self::determ_stage_bytes(&z16v)
13291 );
13292 }
13293 if dstage {
13294 engine.stream().synchronize()?;
13302 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
13303 let exi_v = engine.dtoh_i32(&exi_d)?;
13304 let exo_v = engine.dtoh_i32(&exoff_d)?;
13305 let mg_v = engine.dtoh(&mg_d)?;
13306 let mu_v = engine.dtoh(&mu_d)?;
13307 let tab_v = engine.dtoh_u64(tab_d)?;
13308 eprintln!(
13309 "[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={}",
13310 Self::determ_stage_i32(&csr_v),
13311 Self::determ_stage_i32(&exi_v),
13312 Self::determ_stage_i32(&exo_v),
13313 Self::determ_stage_i32(&ex_off),
13314 Self::determ_stage_sum(&mg_v),
13315 Self::determ_stage_sum(&mu_v),
13316 tab_v
13317 .iter()
13318 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
13319 gb.row_bytes
13320 );
13321 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
13324 let bank_v = engine.dtoh_u8(&gb.bank)?;
13325 eprintln!(
13326 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
13327 Self::determ_stage_bytes(&bank_v),
13328 bank_v.len()
13329 );
13330 }
13331 }
13332 let mut g = engine.moe_f16_grouped(
13333 tab_d,
13334 0,
13335 n_expert,
13336 &exi_d,
13337 &ex_off,
13338 &exoff_d,
13339 &z16,
13340 &zs,
13341 width,
13342 local_ff,
13343 n_active,
13344 n_pairs,
13345 bank_qt,
13346 gb.row_bytes,
13347 )?;
13348 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
13349 let mut u = engine.moe_f16_grouped(
13350 tab_d,
13351 1,
13352 n_expert,
13353 &exi_d,
13354 &ex_off,
13355 &exoff_d,
13356 &z16,
13357 &zs,
13358 width,
13359 local_ff,
13360 n_active,
13361 n_pairs,
13362 bank_qt,
13363 ub.row_bytes,
13364 )?;
13365 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
13366 let act = match activation_limit.filter(|l| *l > 1e-6) {
13370 Some(lim) => {
13371 let mut a = engine.uninit(n_pairs * local_ff)?;
13372 engine.swiglu_clamped_mul_scaled(
13373 &g,
13374 &u,
13375 1.0,
13376 1.0,
13377 lim,
13378 &mut a,
13379 n_pairs * local_ff,
13380 )?;
13381 a
13382 }
13383 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
13384 };
13385 if dstage {
13386 let gv = engine.dtoh(&g)?;
13387 let uv = engine.dtoh(&u)?;
13388 let av = engine.dtoh(&act)?;
13389 let key = (rank, t);
13394 let mut prev_map = DETERM_PREV
13395 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
13396 .lock()
13397 .map_err(|_| "determ prev map poisoned")?;
13398 let shape = match prev_map.get(&key) {
13399 Some(prev) if prev.len() == gv.len() => {
13400 let mut md = 0.0f32;
13401 let mut n_diff = 0usize;
13402 let mut n_big = 0usize;
13403 for (a, b) in prev.iter().zip(gv.iter()) {
13404 let d = (a - b).abs();
13405 if d > 0.0 {
13406 n_diff += 1;
13407 }
13408 if d > 1e-3 {
13409 n_big += 1;
13410 }
13411 if d > md {
13412 md = d;
13413 }
13414 }
13415 format!(
13416 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
13417 gv.len()
13418 )
13419 }
13420 _ => String::new(),
13421 };
13422 prev_map.insert(key, gv.clone());
13423 drop(prev_map);
13424 eprintln!(
13425 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
13426 Self::determ_stage_sum(&gv),
13427 Self::determ_stage_sum(&uv),
13428 Self::determ_stage_sum(&av)
13429 );
13430 }
13431 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
13432 let d_csr = engine.moe_f16_grouped(
13433 tab_d,
13434 2,
13435 n_expert,
13436 &exi_d,
13437 &ex_off,
13438 &exoff_d,
13439 &a16,
13440 &a_s,
13441 local_ff,
13442 width,
13443 n_active,
13444 n_pairs,
13445 bank_qt,
13446 db.row_bytes,
13447 )?;
13448
13449 if dstage {
13452 engine.stream().synchronize()?;
13453 let a16v = engine.dtoh_u8(&a16)?;
13454 let dv = engine.dtoh(&d_csr)?;
13455 eprintln!(
13456 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
13457 Self::determ_stage_bytes(&a16v),
13458 Self::determ_stage_sum(&dv)
13459 );
13460 }
13461 let ev = engine.ctx().new_event(None)?;
13462 ev.record(&engine.stream())?;
13463 if gprof {
13464 let tp = engine
13473 .ctx()
13474 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
13475 tp.record(&engine.stream())?;
13476 ev_tail_prof.push(tp);
13477 }
13478 ev_rank.push(ev);
13479 partials.push(d_csr);
13480 }
13481 let _main = e.gpu.enter_main()?;
13482 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
13483 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
13485 let g_t2 = std::time::Instant::now();
13486 for ev in &ev_rank {
13487 e.stream().wait(ev)?;
13488 }
13489 let mut y0 = e.uninit(n_pairs * width)?;
13492 {
13493 let mut dst = y0.slice_mut(0..n_pairs * width);
13494 e.stream()
13495 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
13496 }
13497 let mut y1 = e.uninit(n_pairs * width)?;
13498 {
13499 let mut dst = y1.slice_mut(0..n_pairs * width);
13500 e.stream()
13501 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
13502 }
13503 let inv_d = e.htod_i32(&inv)?;
13504 let wd_d = e.htod(&wd)?;
13505 let mut out = e.uninit(t * width)?;
13506 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
13507 if gprof {
13508 let _ = e.stream().synchronize();
13509 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
13510 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
13518 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
13519 let guard = self.ranks[rank].gpu.enter_main();
13520 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
13521 Ok(v) => span_ms.push(v),
13522 Err(err) => {
13523 static SAID: std::sync::atomic::AtomicBool =
13524 std::sync::atomic::AtomicBool::new(false);
13525 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
13526 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
13527 }
13528 span_ms.push(-1.0);
13529 }
13530 }
13531 }
13532 eprintln!(
13533 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
13534 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
13535 span_ms.iter().sum::<f32>(),
13536 span_ms.iter().cloned().fold(0.0f32, f32::max)
13537 );
13538 }
13539 Ok(out)
13540 }
13541
13542 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4_device_routed(
13544 &self,
13545 experts: &ResidentNvfp4TensorParallel,
13546 e: &Engine,
13547 input_dev: &crate::CudaSlice<f32>,
13548 sel_d: &crate::CudaSlice<i32>,
13549 w_d: &crate::CudaSlice<f32>,
13550 experts_per_token: usize,
13551 activation_limit: Option<f32>,
13552 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13553 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
13554 experts,
13555 e,
13556 input_dev,
13557 sel_d,
13558 w_d,
13559 experts_per_token,
13560 activation_limit,
13561 || Ok(()),
13562 )
13563 }
13564
13565 #[allow(clippy::too_many_arguments)]
13571 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
13572 &self,
13573 experts: &ResidentNvfp4TensorParallel,
13574 e: &Engine,
13575 input_dev: &crate::CudaSlice<f32>,
13576 sel_d: &crate::CudaSlice<i32>,
13577 w_d: &crate::CudaSlice<f32>,
13578 experts_per_token: usize,
13579 activation_limit: Option<f32>,
13580 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
13581 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13582 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
13583 experts,
13584 e,
13585 input_dev,
13586 sel_d,
13587 w_d,
13588 experts_per_token,
13589 activation_limit,
13590 pre_join,
13591 None,
13592 )
13593 }
13594
13595 #[allow(clippy::too_many_arguments)]
13600 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
13601 &self,
13602 experts: &ResidentNvfp4TensorParallel,
13603 e: &Engine,
13604 input_dev: &crate::CudaSlice<f32>,
13605 sel_d: &crate::CudaSlice<i32>,
13606 w_d: &crate::CudaSlice<f32>,
13607 experts_per_token: usize,
13608 activation_limit: Option<f32>,
13609 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
13610 post_add: Option<(u64, u64)>,
13611 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
13612 if input_dev.len() != experts.input_width {
13613 return Err(format!(
13614 "NVFP4 device-routed input {} != width {}",
13615 input_dev.len(),
13616 experts.input_width
13617 )
13618 .into());
13619 }
13620 let n_sel = experts_per_token;
13621 if sel_d.len() < n_sel || w_d.len() < n_sel {
13622 return Err(format!(
13623 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
13624 sel_d.len(),
13625 w_d.len()
13626 )
13627 .into());
13628 }
13629 let world = self.ranks.len();
13630 if world != NVFP4_CANONICAL_ROW_SHARDS {
13631 return Err(format!(
13632 "NVFP4 device routes require world == canonical shard grid \
13633 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
13634 )
13635 .into());
13636 }
13637 let local_out = experts.expert_width / world;
13638
13639 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13640 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
13641 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
13642 let started = timing.then(std::time::Instant::now);
13643
13644 let mut workspace_guard = experts
13645 .device_workspace
13646 .lock()
13647 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13648 if workspace_guard.is_none() {
13649 drop(workspace_guard);
13650 let zero = vec![0.0f32; experts.input_width];
13651 let zero_sel = vec![0usize; n_sel];
13652 let zero_w = vec![0.0f32; n_sel];
13653 let _ = self.run_tensor_parallel_routes_nvfp4_device(
13654 experts,
13655 &zero,
13656 &zero_sel,
13657 &zero_w,
13658 n_sel,
13659 activation_limit,
13660 )?;
13661 workspace_guard = experts
13662 .device_workspace
13663 .lock()
13664 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
13665 }
13666 let workspace = workspace_guard
13667 .as_mut()
13668 .expect("NVFP4 device routes workspace initialized above");
13669 if workspace.n_sel != n_sel {
13670 return Err(format!(
13671 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
13672 workspace.n_sel
13673 )
13674 .into());
13675 }
13676
13677 if step_tp_graph_enabled()? && step_tp_graph_headroom_ok(e) {
13688 if workspace.dev_route_e.is_none() {
13689 let _main = e.gpu.enter_main()?;
13690 workspace.dev_route_e = Some((
13691 e.htod_i32(&vec![0i32; n_sel])?,
13692 e.htod(&vec![0.0f32; n_sel])?,
13693 ));
13694 }
13695 if workspace.in_stage_e.is_none() {
13696 let _main = e.gpu.enter_main()?;
13697 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
13698 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
13699 }
13700 if workspace.routes_graph.is_none() {
13701 let graph = self.nvfp4_routes_build_graph(
13702 experts,
13703 workspace,
13704 local_out,
13705 n_sel,
13706 activation_limit,
13707 )?;
13708 workspace.routes_graph = Some(graph);
13709 eprintln!(
13710 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
13711 children=3 updates=none performance_claim=false"
13712 );
13713 }
13714 let output = {
13715 let _main = e.gpu.enter_main()?;
13716 {
13717 let (sel_e, w_e) = workspace
13718 .dev_route_e
13719 .as_mut()
13720 .expect("device route staging set above");
13721 {
13722 let mut dst = sel_e.slice_mut(0..n_sel);
13723 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
13724 }
13725 {
13726 let mut dst = w_e.slice_mut(0..n_sel);
13727 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
13728 }
13729 }
13730 {
13731 let in_stage = workspace
13732 .in_stage_e
13733 .as_mut()
13734 .expect("graph staging set above");
13735 let mut dst = in_stage.slice_mut(0..experts.input_width);
13736 e.stream()
13737 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
13738 }
13739 unsafe {
13740 let r = cudarc::driver::sys::cuGraphLaunch(
13741 workspace
13742 .routes_graph
13743 .as_ref()
13744 .expect("routes graph built above")
13745 .exec,
13746 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
13747 );
13748 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13749 return Err(format!("routes graph launch: {r:?}").into());
13750 }
13751 }
13752 let mut output = e.uninit(experts.input_width)?;
13753 {
13754 let out_stage = workspace
13755 .out_stage_e
13756 .as_ref()
13757 .expect("graph staging set above");
13758 e.stream().memcpy_dtod(
13759 &out_stage.slice(0..experts.input_width),
13760 &mut output.slice_mut(0..experts.input_width),
13761 )?;
13762 }
13763 output
13764 };
13765 if let Some(started) = started {
13766 use std::sync::atomic::Ordering;
13767 let ns = TIMING_NS
13768 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
13769 + started.elapsed().as_nanos() as u64;
13770 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
13771 if calls.is_multiple_of(430) {
13772 eprintln!(
13773 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
13774 ns as f64 / 1.0e6,
13775 ns as f64 / calls as f64 / 1.0e3,
13776 );
13777 }
13778 }
13779 return Ok(output);
13780 }
13781
13782 if let Some((_, device)) = workspace.ev_entry.as_ref() {
13786 if *device != e.ctx().ordinal() {
13787 return Err("NVFP4 device-routed routes engine changed".into());
13788 }
13789 } else {
13790 let _main = e.gpu.enter_main()?;
13791 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
13792 }
13793 if workspace.dev_route_e.is_none() {
13794 let _main = e.gpu.enter_main()?;
13795 workspace.dev_route_e = Some((
13796 e.htod_i32(&vec![0i32; n_sel])?,
13797 e.htod(&vec![0.0f32; n_sel])?,
13798 ));
13799 }
13800 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
13806 let e_device = e.ctx().ordinal();
13807 let rank1_routed_peek = workspace.rank1_routed;
13809 let stage_needed = !mirror
13810 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
13811 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
13812 });
13813 {
13814 let _main = e.gpu.enter_main()?;
13815 if stage_needed {
13816 let (sel_e, w_e) = workspace
13817 .dev_route_e
13818 .as_mut()
13819 .expect("device route staging set above");
13820 {
13821 let mut dst = sel_e.slice_mut(0..n_sel);
13822 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
13823 }
13824 {
13825 let mut dst = w_e.slice_mut(0..n_sel);
13826 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
13827 }
13828 }
13829 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13830 ev_entry.record(&e.stream())?;
13831 }
13832 let prestaged = std::mem::take(&mut workspace.prestaged);
13835 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
13836 for (rank_index, engine) in self.ranks.iter().enumerate() {
13837 let _main = engine.gpu.enter_main()?;
13838 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
13839 engine.stream().wait(ev_entry)?;
13840 if !prestaged {
13841 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
13842 engine
13843 .stream()
13844 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
13845 }
13846 if !(rank1_routed && rank_index == 1) {
13847 let same_dev = engine.ctx().ordinal() == e_device;
13851 if mirror {
13852 let Nvfp4DeviceRoutesWorkspace {
13855 sel,
13856 route_w,
13857 dev_route_e,
13858 ..
13859 } = &mut *workspace;
13860 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
13861 if same_dev {
13862 (sel_d, w_d)
13863 } else {
13864 let (sel_e, w_e) = dev_route_e
13865 .as_ref()
13866 .expect("device route staging set above");
13867 (sel_e, w_e)
13868 };
13869 engine.moe_sel_w_mirror(
13870 src_sel,
13871 src_w,
13872 &mut sel[rank_index],
13873 &mut route_w[rank_index],
13874 n_sel,
13875 )?;
13876 } else {
13877 let (sel_e, w_e) = workspace
13878 .dev_route_e
13879 .as_ref()
13880 .expect("device route staging set above");
13881 {
13882 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
13883 engine
13884 .stream()
13885 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
13886 }
13887 {
13888 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
13889 engine
13890 .stream()
13891 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
13892 }
13893 }
13894 }
13895 if !prestaged {
13896 let Nvfp4DeviceRoutesWorkspace {
13897 input, in_q, in_d, ..
13898 } = &mut *workspace;
13899 engine.quantize_q8_1_into(
13900 &input[rank_index],
13901 1,
13902 experts.input_width,
13903 &mut in_q[rank_index],
13904 &mut in_d[rank_index],
13905 )?;
13906 }
13907 }
13908 self.nvfp4_routes_batched_sweeps(
13909 experts,
13910 workspace,
13911 &[],
13912 &[],
13913 &[],
13914 local_out,
13915 n_sel,
13916 activation_limit,
13917 true,
13918 )?;
13919
13920 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
13923 let _main = engine.gpu.enter_main()?;
13924 workspace.ev_rank[rank_index].record(&engine.stream())?;
13925 }
13926 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
13929 let mut ticket = 0u32;
13930 if memops {
13931 use cudarc::driver::sys;
13932 if workspace.fence_flags_raw == 0 {
13933 let root = &self.ranks[0];
13934 let _main = root.gpu.enter_main()?;
13935 let mut ptr: sys::CUdeviceptr = 0;
13936 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
13937 if r != sys::CUresult::CUDA_SUCCESS {
13938 return Err(format!("fence flag alloc: {r:?}").into());
13939 }
13940 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
13941 if r != sys::CUresult::CUDA_SUCCESS {
13942 return Err(format!("fence flag memset: {r:?}").into());
13943 }
13944 workspace.fence_flags_raw = ptr as u64;
13945 }
13946 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
13947 ticket = workspace.fence_ticket;
13948 let base = workspace.fence_flags_raw;
13949 {
13950 let root = &self.ranks[0];
13951 let _main = root.gpu.enter_main()?;
13952 let r = unsafe {
13953 sys::cuStreamWriteValue32_v2(
13954 root.stream().cu_stream() as sys::CUstream,
13955 (base + 4) as sys::CUdeviceptr,
13956 ticket,
13957 0,
13958 )
13959 };
13960 if r != sys::CUresult::CUDA_SUCCESS {
13961 return Err(format!("fence write root: {r:?}").into());
13962 }
13963 }
13964 }
13965 pre_join()?;
13968
13969 if moe_direct_on() && self.ranks.len() == 2 {
13970 let _main = e.gpu.enter_main()?;
13977 if memops {
13978 use cudarc::driver::sys;
13979 let base = workspace.fence_flags_raw;
13980 let r = unsafe {
13981 sys::cuStreamWaitValue32_v2(
13982 e.stream().cu_stream() as sys::CUstream,
13983 (base + 4) as sys::CUdeviceptr,
13984 ticket,
13985 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
13986 )
13987 };
13988 if r != sys::CUresult::CUDA_SUCCESS {
13989 return Err(format!("fence wait: {r:?}").into());
13990 }
13991 for ev in workspace.ev_rank.iter().skip(1) {
13992 e.stream().wait(ev)?;
13993 }
13994 } else {
13995 {
13996 let root = &self.ranks[0];
13997 let _rmain = root.gpu.enter_main()?;
13998 workspace
13999 .ev_done
14000 .as_ref()
14001 .expect("device routes done event")
14002 .record(&root.stream())?;
14003 }
14004 e.stream().wait(
14005 workspace
14006 .ev_done
14007 .as_ref()
14008 .expect("device routes done event"),
14009 )?;
14010 for ev in workspace.ev_rank.iter().skip(1) {
14011 e.stream().wait(ev)?;
14012 }
14013 }
14014 let mut output = e.uninit(experts.input_width)?;
14015 if let Some((sh_raw, scale_raw)) = post_add {
14016 e.add3_raw(
14019 &workspace.accumulator[0],
14020 &workspace.accumulator[1],
14021 sh_raw,
14022 scale_raw,
14023 &mut output,
14024 experts.input_width,
14025 )?;
14026 } else {
14027 e.add(
14028 &workspace.accumulator[0],
14029 &workspace.accumulator[1],
14030 &mut output,
14031 experts.input_width,
14032 )?;
14033 }
14034 let output = output;
14035 if let Some(started) = started {
14036 use std::sync::atomic::Ordering;
14037 let ns = TIMING_NS
14038 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14039 + started.elapsed().as_nanos() as u64;
14040 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14041 if calls.is_multiple_of(430) {
14042 eprintln!(
14043 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14044 ns as f64 / 1.0e6,
14045 ns as f64 / calls as f64 / 1.0e3,
14046 );
14047 }
14048 }
14049 return Ok(output);
14050 }
14051 {
14052 let root = &self.ranks[0];
14053 let _main = root.gpu.enter_main()?;
14054 for ev in workspace.ev_rank.iter().skip(1) {
14055 root.stream().wait(ev)?;
14056 }
14057 root.stream()
14058 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
14059 {
14060 let Nvfp4DeviceRoutesWorkspace {
14061 accumulator,
14062 remote,
14063 combined,
14064 ..
14065 } = &mut *workspace;
14066 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14067 }
14068 workspace
14069 .ev_done
14070 .as_ref()
14071 .expect("device routes done event")
14072 .record(&root.stream())?;
14073 }
14074 let output = {
14075 let _main = e.gpu.enter_main()?;
14076 e.stream().wait(
14077 workspace
14078 .ev_done
14079 .as_ref()
14080 .expect("device routes done event"),
14081 )?;
14082 let mut output = e.uninit(experts.input_width)?;
14085 e.stream().memcpy_dtod(
14086 &workspace.combined.slice(0..experts.input_width),
14087 &mut output.slice_mut(0..experts.input_width),
14088 )?;
14089 output
14090 };
14091 if let Some(started) = started {
14092 use std::sync::atomic::Ordering;
14093 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
14094 + started.elapsed().as_nanos() as u64;
14095 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
14096 if calls.is_multiple_of(430) {
14097 eprintln!(
14098 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
14099 ns as f64 / 1.0e6,
14100 ns as f64 / calls as f64 / 1.0e3,
14101 );
14102 }
14103 }
14104 Ok(output)
14105 }
14106
14107 pub(crate) fn decode_v2_finish_root_fused(
14111 &self,
14112 ws: &mut StepTpDecodeV2Ws,
14113 ) -> Result<(), Box<dyn std::error::Error>> {
14114 let root = &self.ranks[0];
14115 let _main = root.gpu.enter_main()?;
14116 if ws.raw_peer_partial != 0 {
14117 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
14119 } else {
14120 root.stream()
14121 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
14122 }
14123 {
14124 let StepTpDecodeV2Ws {
14125 o_partials,
14126 peer_partial,
14127 reduce_a,
14128 o_out,
14129 ..
14130 } = &mut *ws;
14131 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
14132 }
14133 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
14134 if shadows {
14135 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
14138 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
14139 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
14140 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
14141 }
14142 if shadows && ws.raw_peer_partial != 0 {
14143 raw_copy_bytes(
14144 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
14145 ws.raw_k1,
14146 ws.local_kv_dim * 4,
14147 root,
14148 )?;
14149 raw_copy_bytes(
14150 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
14151 ws.raw_v1,
14152 ws.local_kv_dim * 4,
14153 root,
14154 )?;
14155 } else if shadows {
14156 let start = ws.local_kv_dim;
14157 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
14158 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
14159 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
14160 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
14161 }
14162 if ws.raw_mixed_stage_e != 0 {
14163 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
14166 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
14167 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
14168 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
14169 }
14170 Ok(())
14171 }
14172
14173 pub(crate) fn decode_v2_arm_token_mirrors(
14176 &self,
14177 ws: &mut StepTpDecodeV2Ws,
14178 mixed_stage_e: u64,
14179 shadow_stage_e: (u64, u64),
14180 ) -> Result<(), Box<dyn std::error::Error>> {
14181 use cudarc::driver::DevicePtr;
14182 let root = &self.ranks[0];
14183 let _main = root.gpu.enter_main()?;
14184 let stream = root.stream();
14185 let (a, _g) = ws.reduce_a.device_ptr(&stream);
14186 ws.raw_reduce_a = a;
14187 ws.raw_mixed_stage_e = mixed_stage_e;
14188 ws.raw_shadow_stage_e = shadow_stage_e;
14189 Ok(())
14190 }
14191
14192 fn nvfp4_routes_build_graph(
14198 &self,
14199 experts: &ResidentNvfp4TensorParallel,
14200 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14201 local_out: usize,
14202 n_sel: usize,
14203 activation_limit: Option<f32>,
14204 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
14205 use cudarc::driver::DevicePtr;
14206 use cudarc::driver::sys;
14207 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14208 if r == sys::CUresult::CUDA_SUCCESS {
14209 Ok(())
14210 } else {
14211 Err(format!("{what}: {r:?}").into())
14212 }
14213 }
14214 let world = self.ranks.len();
14215 if world != 2 {
14216 return Err("routes graph door is built for the TP2 pair".into());
14217 }
14218 let width = experts.input_width;
14219
14220 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
14222 let stream = engine.stream();
14223 let (ptr, _g) = buf.device_ptr(&stream);
14224 ptr
14225 };
14226 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
14227 let stream = engine.stream();
14228 let (ptr, _g) = buf.device_ptr(&stream);
14229 ptr
14230 };
14231 let (sel_e, w_e) = workspace
14232 .dev_route_e
14233 .as_ref()
14234 .expect("device route staging set before graph build");
14235 let root_engine = &self.ranks[0];
14236 let p_in_stage = ptr_f32(
14237 workspace.in_stage_e.as_ref().expect("graph staging"),
14238 root_engine,
14239 );
14240 let p_out_stage = ptr_f32(
14241 workspace.out_stage_e.as_ref().expect("graph staging"),
14242 root_engine,
14243 );
14244 let p_sel_e = ptr_i32(sel_e, root_engine);
14245 let p_w_e = ptr_f32(w_e, root_engine);
14246 let p_input: Vec<u64> = (0..world)
14247 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
14248 .collect();
14249 let p_sel: Vec<u64> = (0..world)
14250 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
14251 .collect();
14252 let p_route_w: Vec<u64> = (0..world)
14253 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
14254 .collect();
14255 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
14256 let p_remote = ptr_f32(&workspace.remote, root_engine);
14257 let p_combined = ptr_f32(&workspace.combined, root_engine);
14258
14259 let raw_copy = |dst: u64,
14260 src: u64,
14261 bytes: usize,
14262 engine: &Engine|
14263 -> Result<(), Box<dyn std::error::Error>> {
14264 unsafe {
14265 cu_try(
14266 sys::cuMemcpyAsync(
14267 dst as sys::CUdeviceptr,
14268 src as sys::CUdeviceptr,
14269 bytes,
14270 engine.stream().cu_stream() as sys::CUstream,
14271 ),
14272 "routes graph cuMemcpyAsync",
14273 )
14274 }
14275 };
14276
14277 let mut children = Vec::with_capacity(3);
14278 for rank in 0..world {
14279 let engine = &self.ranks[rank];
14280 let _main = engine.gpu.enter_main()?;
14281 let (child, _retained) = engine.capture_graph_retained(|_| {
14282 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
14283 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
14284 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
14285 {
14286 let Nvfp4DeviceRoutesWorkspace {
14287 input, in_q, in_d, ..
14288 } = &mut *workspace;
14289 engine.quantize_q8_1_into(
14290 &input[rank],
14291 1,
14292 width,
14293 &mut in_q[rank],
14294 &mut in_d[rank],
14295 )?;
14296 }
14297 self.nvfp4_routes_batched_sweeps_rank(
14298 experts,
14299 workspace,
14300 &[],
14301 &[],
14302 &[],
14303 local_out,
14304 n_sel,
14305 activation_limit,
14306 true,
14307 rank,
14308 )?;
14309 Ok(())
14310 })?;
14311 children.push(child);
14312 }
14313 {
14314 let root = &self.ranks[0];
14315 let _main = root.gpu.enter_main()?;
14316 let (child, _retained) = root.capture_graph_retained(|_| {
14317 raw_copy(p_remote, p_acc1, width * 4, root)?;
14318 {
14319 let Nvfp4DeviceRoutesWorkspace {
14320 accumulator,
14321 remote,
14322 combined,
14323 ..
14324 } = &mut *workspace;
14325 root.add(&accumulator[0], remote, combined, width)?;
14326 }
14327 raw_copy(p_out_stage, p_combined, width * 4, root)?;
14328 Ok(())
14329 })?;
14330 children.push(child);
14331 }
14332
14333 let mut parent: sys::CUgraph = std::ptr::null_mut();
14334 unsafe {
14335 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
14336 }
14337 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
14338 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
14339 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
14340 unsafe {
14341 cu_try(
14342 sys::cuGraphAddChildGraphNode(
14343 &mut n0,
14344 parent,
14345 std::ptr::null(),
14346 0,
14347 children[0].cu_graph(),
14348 ),
14349 "routes child r0",
14350 )?;
14351 cu_try(
14352 sys::cuGraphAddChildGraphNode(
14353 &mut n1,
14354 parent,
14355 std::ptr::null(),
14356 0,
14357 children[1].cu_graph(),
14358 ),
14359 "routes child r1",
14360 )?;
14361 let deps = [n0, n1];
14362 cu_try(
14363 sys::cuGraphAddChildGraphNode(
14364 &mut n2,
14365 parent,
14366 deps.as_ptr(),
14367 2,
14368 children[2].cu_graph(),
14369 ),
14370 "routes child root",
14371 )?;
14372 }
14373 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
14374 unsafe {
14375 cu_try(
14376 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
14377 "routes instantiate",
14378 )?;
14379 }
14380 Ok(RoutesGraph {
14381 exec,
14382 parent,
14383 _children: children,
14384 })
14385 }
14386
14387 #[allow(clippy::too_many_arguments)]
14391 pub(crate) fn routes_rank_section(
14392 &self,
14393 experts: &ResidentNvfp4TensorParallel,
14394 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14395 raw_input_src: u64,
14396 local_out: usize,
14397 n_sel: usize,
14398 activation_limit: Option<f32>,
14399 rank_index: usize,
14400 ) -> Result<(), Box<dyn std::error::Error>> {
14401 let engine = &self.ranks[rank_index];
14402 {
14403 let _main = engine.gpu.enter_main()?;
14404 let (sel_e_ptr, w_e_ptr) = workspace
14406 .raw_dev_route_e
14407 .ok_or("routes rank section requires armed staging pointers")?;
14408 raw_copy_bytes(
14409 workspace.raw_input[rank_index],
14410 raw_input_src,
14411 experts.input_width * 4,
14412 engine,
14413 )?;
14414 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
14415 raw_copy_bytes(
14416 workspace.raw_route_w[rank_index],
14417 w_e_ptr,
14418 n_sel * 4,
14419 engine,
14420 )?;
14421 {
14422 let Nvfp4DeviceRoutesWorkspace {
14423 input, in_q, in_d, ..
14424 } = &mut *workspace;
14425 engine.quantize_q8_1_into(
14426 &input[rank_index],
14427 1,
14428 experts.input_width,
14429 &mut in_q[rank_index],
14430 &mut in_d[rank_index],
14431 )?;
14432 }
14433 }
14434 self.nvfp4_routes_batched_sweeps_rank(
14435 experts,
14436 workspace,
14437 &[],
14438 &[],
14439 &[],
14440 local_out,
14441 n_sel,
14442 activation_limit,
14443 true,
14444 rank_index,
14445 )
14446 }
14447
14448 pub(crate) fn routes_root_section(
14451 &self,
14452 experts: &ResidentNvfp4TensorParallel,
14453 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14454 ) -> Result<(), Box<dyn std::error::Error>> {
14455 let root = &self.ranks[0];
14456 let _main = root.gpu.enter_main()?;
14457 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
14458 .raw_combine
14459 .ok_or("routes root section requires armed combine pointers")?;
14460 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
14461 {
14462 let Nvfp4DeviceRoutesWorkspace {
14463 accumulator,
14464 remote,
14465 combined,
14466 ..
14467 } = &mut *workspace;
14468 root.add(&accumulator[0], remote, combined, experts.input_width)?;
14469 }
14470 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
14471 Ok(())
14472 }
14473
14474 pub(crate) fn routes_arm_raw(
14477 &self,
14478 experts: &ResidentNvfp4TensorParallel,
14479 workspace: &mut Nvfp4DeviceRoutesWorkspace,
14480 ) -> Result<(), Box<dyn std::error::Error>> {
14481 use cudarc::driver::DevicePtr;
14482 if workspace.raw_dev_route_e.is_some() {
14483 return Ok(());
14484 }
14485 let _ = experts;
14486 let (sel_e, w_e) = workspace
14487 .dev_route_e
14488 .as_ref()
14489 .ok_or("routes staging not armed")?;
14490 let root = &self.ranks[0];
14491 {
14492 let _main = root.gpu.enter_main()?;
14493 let stream = root.stream();
14494 let (a, _g) = sel_e.device_ptr(&stream);
14495 let (b, _g) = w_e.device_ptr(&stream);
14496 workspace.raw_dev_route_e = Some((a, b));
14497 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
14498 let (d, _g) = workspace.remote.device_ptr(&stream);
14499 let (f, _g) = workspace.combined.device_ptr(&stream);
14500 let out_stage = workspace
14501 .out_stage_e
14502 .as_ref()
14503 .ok_or("routes out stage not armed")?;
14504 let (g_, _g) = out_stage.device_ptr(&stream);
14505 workspace.raw_combine = Some((c, d, f, g_));
14506 }
14507 for rank in 0..self.ranks.len() {
14508 let engine = &self.ranks[rank];
14509 let _main = engine.gpu.enter_main()?;
14510 let stream = engine.stream();
14511 let (a, _g) = workspace.input[rank].device_ptr(&stream);
14512 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
14513 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
14514 workspace.raw_input.push(a);
14515 workspace.raw_sel.push(b);
14516 workspace.raw_route_w.push(c);
14517 }
14518 Ok(())
14519 }
14520
14521 #[allow(clippy::too_many_arguments)] pub fn run_tensor_parallel_routes_nvfp4(
14526 &self,
14527 experts: &ResidentNvfp4TensorParallel,
14528 input: &[f32],
14529 tokens: usize,
14530 selected: &[usize],
14531 route_weights: &[f32],
14532 experts_per_token: usize,
14533 activation_limit: Option<f32>,
14534 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
14535 validate_activations(input, tokens, experts.input_width)?;
14536 let pairs = tokens
14537 .checked_mul(experts_per_token)
14538 .ok_or("NVFP4 TP route count overflow")?;
14539 if selected.len() != pairs || route_weights.len() != pairs {
14540 return Err(format!(
14541 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
14542 {experts_per_token} ({pairs})",
14543 selected.len(),
14544 route_weights.len(),
14545 )
14546 .into());
14547 }
14548 if !route_weights.iter().all(|weight| weight.is_finite()) {
14549 return Err("NVFP4 TP route weights contain a non-finite value".into());
14550 }
14551
14552 let mut output = vec![0.0f32; tokens * experts.input_width];
14553 for token in 0..tokens {
14554 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
14555 for slot in 0..experts_per_token {
14556 let pair = token * experts_per_token + slot;
14557 let expert = selected[pair];
14558 if expert >= experts.expert_count {
14559 return Err(format!(
14560 "NVFP4 TP selected expert {expert} outside 0..{}",
14561 experts.expert_count
14562 )
14563 .into());
14564 }
14565 let gate = self.run_column_bank_expert_nvfp4(
14566 &experts.gate,
14567 &experts.macros_gate,
14568 expert,
14569 input_row,
14570 )?;
14571 let up = self.run_column_bank_expert_nvfp4(
14572 &experts.up,
14573 &experts.macros_up,
14574 expert,
14575 input_row,
14576 )?;
14577 let activated: Vec<f32> = gate
14578 .iter()
14579 .zip(&up)
14580 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
14581 .collect();
14582 debug_assert_eq!(activated.len(), experts.expert_width);
14583 let down = self.run_row_bank_expert_nvfp4(
14584 &experts.down,
14585 &experts.macros_down,
14586 expert,
14587 &activated,
14588 )?;
14589 let weight = route_weights[pair];
14590 for (sum, value) in output
14591 [token * experts.input_width..(token + 1) * experts.input_width]
14592 .iter_mut()
14593 .zip(down)
14594 {
14595 *sum += weight * value;
14596 }
14597 }
14598 }
14599 Ok(output)
14600 }
14601}
14602
14603#[cfg(test)]
14604mod default_on_door_tests {
14605 use super::door_default_on_value;
14606
14607 #[test]
14618 fn the_default_on_door_parses_every_state_and_names_its_source() {
14619 assert_eq!(
14622 door_default_on_value("MEMRA_TEST_DOOR", None),
14623 (true, "default-on")
14624 );
14625 assert_eq!(
14627 door_default_on_value("MEMRA_TEST_DOOR", Some("1")),
14628 (true, "env=1")
14629 );
14630 assert_eq!(
14632 door_default_on_value("MEMRA_TEST_DOOR", Some("0")),
14633 (false, "env=0 (rollback seam)")
14634 );
14635 for bad in [
14640 "false", "off", "no", "", " 0", "0 ", "00", "true", "2", "-1",
14641 ] {
14642 let (on, source) = door_default_on_value("MEMRA_TEST_DOOR", Some(bad));
14643 assert!(on, "value {bad:?} must NOT disarm a default-ON door");
14644 assert!(
14645 source.contains("default-on") && source.contains("unrecognized"),
14646 "value {bad:?} gave source {source:?}, which does not announce itself as an \
14647 ignored value — a receipt reader would take it for a clean default"
14648 );
14649 }
14650 }
14651}
14652
14653#[cfg(test)]
14654mod bank_v2_layout_tests {
14655 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
14656
14657 #[test]
14667 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
14668 let (out_f, in_f) = (2usize, 128usize);
14670 let row_bytes = nvfp4_row_bytes(in_f);
14671 assert_eq!(row_bytes, 72);
14672 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
14673 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
14674 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
14675 let n_slots = in_f / 32;
14676 for row in 0..out_f {
14677 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
14678 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
14679 for g in 0..n_slots {
14680 let (sblk, h) = (g / 2, g % 2);
14681 let sb = &src[sblk * 36..sblk * 36 + 36];
14682 assert_eq!(
14683 &dst[g * 16..g * 16 + 16],
14684 &sb[4 + 16 * h..4 + 16 * h + 16],
14685 "row {row} slot {g} codes"
14686 );
14687 assert_eq!(
14688 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
14689 &sb[2 * h..2 * h + 2],
14690 "row {row} slot {g} scales"
14691 );
14692 }
14693 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
14695 a.sort_unstable();
14696 b.sort_unstable();
14697 assert_eq!(a, b, "row {row} is not a byte permutation");
14698 }
14699 }
14700}
14701
14702#[cfg(test)]
14703mod tests {
14704
14705 #[test]
14706 fn replicated_row_join_is_strictly_tp2_native_and_nonempty() {
14707 assert!(super::validate_tp2_replicated_row_join(2, true, 4096).is_ok());
14708 assert!(
14709 super::validate_tp2_replicated_row_join(1, true, 4096)
14710 .unwrap_err()
14711 .contains("exactly two ranks")
14712 );
14713 assert!(
14714 super::validate_tp2_replicated_row_join(4, true, 4096)
14715 .unwrap_err()
14716 .contains("exactly two ranks")
14717 );
14718 assert!(
14719 super::validate_tp2_replicated_row_join(2, false, 4096)
14720 .unwrap_err()
14721 .contains("native P2P")
14722 );
14723 assert!(super::validate_tp2_replicated_row_join(2, true, 0).is_err());
14724 }
14725
14726 #[test]
14727 fn door_composition_refuses_first_armed_flag_by_name() {
14728 let table: [(&str, &str); 2] = [
14729 ("MEMRA_DOOR_A", "gated on the unsharded walk only"),
14730 ("MEMRA_DOOR_B", "no sharded branches"),
14731 ];
14732 super::refuse_door_composition("MEMRA_X_TP", &table, |_| false).expect("cold doors pass");
14734 let err = super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_B")
14736 .expect_err("armed door must refuse");
14737 assert_eq!(
14738 err,
14739 "MEMRA_X_TP + MEMRA_DOOR_B: unproven composition, refused (no sharded branches)"
14740 );
14741 super::refuse_door_composition("MEMRA_X_TP", &table, |f| f == "MEMRA_DOOR_C")
14743 .expect("foreign flags are not the matrix");
14744 }
14745
14746 #[test]
14753 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
14754 let (kp, bp) = (0xdead_0000u64, 0u64);
14755 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
14756 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
14757 assert_eq!(
14758 super::retired_rows_tab_key(kp, bp, 20, 2),
14759 super::retired_rows_tab_key(kp, bp, 20, 2),
14760 "same layer and t must hash the same, or the test proves nothing"
14761 );
14762 let a = super::rows_tab_host(&live, 0x9000, true, 1);
14763 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
14764 assert_ne!(a, b, "the two generations write DIFFERENT tables");
14765 assert_eq!(
14767 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
14768 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
14769 "the retired key collides across allocation generations"
14770 );
14771 }
14772
14773 #[test]
14776 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
14777 let parts = [
14778 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
14779 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
14780 ];
14781 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
14782 assert_eq!(
14783 same,
14784 vec![
14785 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
14786 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
14789 "same-session rows share one counter cell and step back t-1-r"
14790 );
14791 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
14792 assert_eq!(
14793 cross,
14794 vec![
14795 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
14796 0x00c1u64, 0x00d1u64, 0x7004, 0,
14797 ],
14798 "cross-session rows get their own counter cell and no step back"
14799 );
14800 }
14801 use super::*;
14802
14803 #[test]
14804 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
14805 let limit = Some(7.0);
14806 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
14807 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
14808 assert!(
14809 step_expert_activation_host(-20.0, 9.0, limit).abs()
14810 < step_expert_activation_host(-20.0, 9.0, None).abs()
14811 );
14812 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
14813 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
14814 assert!(validate_step_expert_activation_limit(limit).is_ok());
14815 }
14816
14817 #[test]
14818 fn moe_residual_host_preserves_official_add_order() {
14819 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
14820 assert_eq!(output, [0.0]);
14821 assert_eq!(
14822 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
14823 "MoE residual lengths residual=1 routed=2 shared=1"
14824 );
14825 }
14826
14827 #[test]
14828 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
14829 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
14830 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
14831 assert_eq!(owners.len(), 4);
14832 for (rank, owner) in owners.iter().enumerate() {
14833 assert_eq!(owner.rank, rank);
14834 assert_eq!(owner.selected, vec![0, 36]);
14835 assert_eq!(owner.token_rows, vec![0, 0]);
14836 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
14837 }
14838 }
14839
14840 #[test]
14841 fn expert_owner_routes_validate_geometry_and_selected_experts() {
14842 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
14843 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
14844 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
14845 assert!(error.contains("outside 0..288"));
14846 }
14847
14848 #[test]
14849 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
14850 let selected = [
14851 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
14852 ];
14853 assert_eq!(
14854 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
14855 16
14856 );
14857 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
14858 assert_eq!(
14859 owners
14860 .iter()
14861 .map(|owner| owner.selected.len())
14862 .collect::<Vec<_>>(),
14863 vec![2, 4, 6, 4]
14864 );
14865 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
14866 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
14867 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
14868 }
14869
14870 #[test]
14871 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
14872 let owner0 = [0usize, 3];
14873 let owner1 = [1usize, 2];
14874 let owners = [owner0.as_slice(), owner1.as_slice()];
14875 assert_eq!(
14876 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
14877 .unwrap(),
14878 WeightedRouteCombineShape {
14879 pairs: 4,
14880 max_pairs: 12,
14881 }
14882 );
14883 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
14884 assert!(
14885 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
14886 .is_err()
14887 );
14888 assert!(
14889 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
14890 .is_err()
14891 );
14892 assert!(
14893 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
14894 .is_err()
14895 );
14896 }
14897
14898 #[test]
14899 fn native_p2p_door_is_strict_and_default_off() {
14900 assert!(!parse_step_tp_native_p2p(None).unwrap());
14901 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
14902 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
14903 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
14904 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
14905 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
14906 }
14907
14908 #[test]
14909 fn bulk_p2p_door_is_strict_and_default_off() {
14910 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
14911 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
14912 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
14913 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
14914 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
14915 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
14916 }
14917
14918 #[test]
14919 fn ep_device_arithmetic_door_is_strict_and_default_off() {
14920 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
14921 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
14922 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
14923 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
14924 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
14925 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
14926 }
14927
14928 #[test]
14929 fn f32_mirror_door_is_strict_and_default_off() {
14930 assert!(!parse_step_tp_f32_mirror(None).unwrap());
14931 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
14932 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
14933 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
14934 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
14935 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
14936 }
14937
14938 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
14939 let codes = (0..out_features * in_features)
14940 .map(|index| (index % 251) as u8)
14941 .collect();
14942 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
14943 .map(|index| index as f32 + 1.0)
14944 .collect();
14945 (codes, scales)
14946 }
14947
14948 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
14949 (0..out_features * in_features)
14950 .flat_map(|value| (value as u16).to_le_bytes())
14951 .collect()
14952 }
14953
14954 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
14955 bytes
14956 .chunks_exact(2)
14957 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
14958 .collect()
14959 }
14960
14961 #[test]
14962 fn bf16_matrix_rejects_wrong_byte_count() {
14963 let bytes = vec![0u8; 4 * 4 * 2 - 1];
14964 let matrix = Bf16Matrix {
14965 bytes: &bytes,
14966 out_features: 4,
14967 in_features: 4,
14968 };
14969 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
14970 }
14971
14972 #[test]
14973 fn replicated_device_rows_require_exact_rank_local_shapes() {
14974 assert_eq!(
14975 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
14976 12_288
14977 );
14978 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
14979 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
14980 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
14981 assert!(
14982 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
14983 );
14984 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
14985 }
14986
14987 #[test]
14988 fn replicated_device_row_refresh_requires_exact_root_source() {
14989 assert_eq!(
14990 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
14991 12_288
14992 );
14993 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
14994 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
14995 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
14996 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
14997 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
14998 }
14999
15000 #[test]
15001 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
15002 for tp in [1, 2, 4, 8] {
15003 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
15004 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
15005 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
15006 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
15007 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
15008 }
15009 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
15010 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
15011 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
15012 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
15013 }
15014
15015 #[test]
15016 fn cache_rows_split_by_token_then_rank() {
15017 let rows = (0u8..24).collect::<Vec<_>>();
15018 assert_eq!(
15019 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
15020 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
15021 );
15022 assert_eq!(
15023 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
15024 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
15025 );
15026 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
15027 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
15028 }
15029
15030 #[test]
15031 fn bf16_column_shard_preserves_contiguous_output_rows() {
15032 let bytes = bf16_matrix_bytes(4, 4);
15033 let matrix = Bf16Matrix {
15034 bytes: &bytes,
15035 out_features: 4,
15036 in_features: 4,
15037 };
15038 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
15039 assert_eq!(shard.out_features, 2);
15040 assert_eq!(shard.in_features, 4);
15041 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
15042 }
15043
15044 #[test]
15045 fn bf16_row_shard_preserves_each_input_column_window() {
15046 let bytes = bf16_matrix_bytes(3, 4);
15047 let matrix = Bf16Matrix {
15048 bytes: &bytes,
15049 out_features: 3,
15050 in_features: 4,
15051 };
15052 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
15053 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
15054 }
15055
15056 #[test]
15057 fn bf16_row_block_preserves_global_column_order() {
15058 let bytes = bf16_matrix_bytes(3, 8);
15059 let matrix = Bf16Matrix {
15060 bytes: &bytes,
15061 out_features: 3,
15062 in_features: 8,
15063 };
15064 let block = bf16_row_block(matrix, 2, 3).unwrap();
15065 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
15066 }
15067
15068 #[test]
15069 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
15070 let (codes, scales) = matrix(1280, 4096);
15071 let matrix = E4m3BlockMatrix {
15072 codes: &codes,
15073 scales: &scales,
15074 out_features: 1280,
15075 in_features: 4096,
15076 };
15077 let shard = column_shard(matrix, 2, 1).unwrap();
15078 assert_eq!(shard.out_features, 640);
15079 assert_eq!(shard.codes, &codes[640 * 4096..]);
15080 assert_eq!(shard.scales, &scales[5 * 32..]);
15081 }
15082
15083 #[test]
15084 fn row_shard_preserves_each_weight_and_scale_column_window() {
15085 let (codes, scales) = matrix(4096, 1280);
15086 let matrix = E4m3BlockMatrix {
15087 codes: &codes,
15088 scales: &scales,
15089 out_features: 4096,
15090 in_features: 1280,
15091 };
15092 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
15093 assert_eq!(shard_codes.len(), 4096 * 640);
15094 assert_eq!(&shard_codes[..640], &codes[640..1280]);
15095 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
15096 assert_eq!(shard_scales.len(), 32 * 5);
15097 assert_eq!(&shard_scales[..5], &scales[5..10]);
15098 assert_eq!(&shard_scales[5..10], &scales[15..20]);
15099 }
15100
15101 #[test]
15102 fn activation_shards_keep_token_rows_separate() {
15103 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
15104 assert_eq!(
15105 activation_shard(&activations, 2, 8, 2, 1),
15106 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
15107 );
15108 }
15109
15110 #[test]
15111 fn expert_bank_selects_expert_major_code_and_scale_planes() {
15112 let expert_count = 2;
15113 let out_features = 128;
15114 let in_features = 128;
15115 let code_stride = out_features * in_features;
15116 let codes: Vec<u8> = (0..expert_count * code_stride)
15117 .map(|index| (index % 251) as u8)
15118 .collect();
15119 let scales = vec![1.0f32, 2.0];
15120 let bank = E4m3ExpertBank {
15121 codes: &codes,
15122 scales: &scales,
15123 expert_count,
15124 out_features,
15125 in_features,
15126 };
15127 bank.validate().unwrap();
15128 let expert = bank.expert(1).unwrap();
15129 assert_eq!(expert.codes, &codes[code_stride..]);
15130 assert_eq!(expert.scales, &[2.0]);
15131 }
15132
15133 #[test]
15134 fn expert_bank_rejects_non_positive_scale() {
15135 let codes = vec![0u8; 128 * 128];
15136 let scales = vec![0.0f32];
15137 let bank = E4m3ExpertBank {
15138 codes: &codes,
15139 scales: &scales,
15140 expert_count: 1,
15141 out_features: 128,
15142 in_features: 128,
15143 };
15144 assert!(bank.validate().unwrap_err().contains("non-positive"));
15145 }
15146
15147 #[test]
15148 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
15149 let expert_count = 2;
15150 let out_features = 256;
15151 let in_features = 128;
15152 let code_stride = out_features * in_features;
15153 let scale_stride = 2;
15154 let codes = (0..expert_count * code_stride)
15155 .map(|index| (index % 251) as u8)
15156 .collect::<Vec<_>>();
15157 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15158 let bank = E4m3ExpertBank {
15159 codes: &codes,
15160 scales: &scales,
15161 expert_count,
15162 out_features,
15163 in_features,
15164 };
15165
15166 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
15167 assert_eq!(rank.out_features, 128);
15168 assert_eq!(rank.in_features, 128);
15169 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15170 assert_eq!(rank.scales, vec![11.0, 21.0]);
15171 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
15172 assert_eq!(
15173 &rank.codes[128 * 128..],
15174 &codes[code_stride + 128 * 128..2 * code_stride]
15175 );
15176 assert_eq!(scale_stride, scales.len() / expert_count);
15177 }
15178
15179 #[test]
15180 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
15181 let expert_count = 2;
15182 let out_features = 128;
15183 let in_features = 256;
15184 let code_stride = out_features * in_features;
15185 let codes = (0..expert_count * code_stride)
15186 .map(|index| (index % 251) as u8)
15187 .collect::<Vec<_>>();
15188 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
15189 let bank = E4m3ExpertBank {
15190 codes: &codes,
15191 scales: &scales,
15192 expert_count,
15193 out_features,
15194 in_features,
15195 };
15196
15197 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15198 assert_eq!(rank.out_features, 128);
15199 assert_eq!(rank.in_features, 128);
15200 assert_eq!(rank.k_blocks, Some(1));
15201 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
15202 assert_eq!(rank.scales, vec![11.0, 21.0]);
15203 assert_eq!(&rank.codes[..128], &codes[128..256]);
15204 assert_eq!(
15205 &rank.codes[128 * 128..128 * 128 + 128],
15206 &codes[code_stride + 128..code_stride + 256]
15207 );
15208 }
15209
15210 #[test]
15211 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
15212 let expert_count = 2;
15213 let out_features = 256;
15214 let in_features = 512;
15215 let code_stride = out_features * in_features;
15216 let mut codes = vec![0u8; expert_count * code_stride];
15217 for expert in 0..expert_count {
15218 for row in 0..out_features {
15219 for block in 0..4 {
15220 let value = (expert * 80 + block * 16 + row % 16) as u8;
15221 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
15222 codes[start..start + FP8_BLOCK].fill(value);
15223 }
15224 }
15225 }
15226 let scales = vec![
15227 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,
15228 112.0, 113.0, 114.0,
15229 ];
15230 let bank = E4m3ExpertBank {
15231 codes: &codes,
15232 scales: &scales,
15233 expert_count,
15234 out_features,
15235 in_features,
15236 };
15237
15238 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
15239 assert_eq!(rank.out_features, out_features);
15240 assert_eq!(rank.in_features, 256);
15241 assert_eq!(rank.k_blocks, Some(2));
15242 assert_eq!(rank.code_stride, out_features * 256);
15243 assert_eq!(rank.scale_stride, 4);
15244 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
15245 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
15246
15247 let block_stride = out_features * FP8_BLOCK;
15248 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
15249 assert!(
15250 rank.codes[block_stride..block_stride + FP8_BLOCK]
15251 .iter()
15252 .all(|&code| code == 48)
15253 );
15254 assert!(
15255 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
15256 .iter()
15257 .all(|&code| code == 112)
15258 );
15259 assert!(
15260 rank.codes
15261 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
15262 .iter()
15263 .all(|&code| code == 128)
15264 );
15265 }
15266
15267 #[test]
15268 fn automatic_parallel_policy_needs_only_one_device_set_not_layer_recipes() {
15269 assert_eq!(parse_auto_parallel_devices(None, None).unwrap(), None);
15270 assert_eq!(
15271 parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3")).unwrap(),
15272 Some(vec![0, 1, 2, 3])
15273 );
15274 assert!(parse_auto_parallel_devices(Some("auto"), None).is_err());
15275 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,1")).is_err());
15276 assert!(parse_auto_parallel_devices(Some("auto"), Some("0,1,2,3,4")).is_err());
15277 assert!(parse_auto_parallel_devices(Some("ep"), Some("0,1")).is_err());
15278 }
15279
15280 #[test]
15281 fn automatic_ep_device_router_flag_is_strict() {
15282 assert!(!parse_parallel_ep_device_router(None).unwrap());
15283 assert!(!parse_parallel_ep_device_router(Some("0")).unwrap());
15284 assert!(parse_parallel_ep_device_router(Some("1")).unwrap());
15285 assert!(parse_parallel_ep_device_router(Some("true")).is_err());
15286 }
15287
15288 #[test]
15289 fn automatic_ep_pair_down_flag_is_strict_and_defaults_off() {
15290 assert!(!parse_parallel_ep_pair_down(None).unwrap());
15291 assert!(!parse_parallel_ep_pair_down(Some("0")).unwrap());
15292 assert!(parse_parallel_ep_pair_down(Some("1")).unwrap());
15293 assert!(parse_parallel_ep_pair_down(Some("true")).is_err());
15294 }
15295
15296 #[test]
15297 fn automatic_ep_q8_activation_flag_is_strict() {
15298 assert!(!parse_parallel_ep_q8_act(None).unwrap());
15299 assert!(!parse_parallel_ep_q8_act(Some("0")).unwrap());
15300 assert!(parse_parallel_ep_q8_act(Some("1")).unwrap());
15301 assert!(parse_parallel_ep_q8_act(Some("true")).is_err());
15302 }
15303
15304 #[test]
15305 fn automatic_ep_q8_scope_is_explicit_and_strict() {
15306 assert_eq!(parse_parallel_ep_q8_scope(None).unwrap(), None);
15307 assert_eq!(
15308 parse_parallel_ep_q8_scope(Some("all")).unwrap(),
15309 Some(ParallelEpQ8Scope::All)
15310 );
15311 assert_eq!(
15312 parse_parallel_ep_q8_scope(Some("gate-up")).unwrap(),
15313 Some(ParallelEpQ8Scope::GateUp)
15314 );
15315 assert_eq!(
15316 parse_parallel_ep_q8_scope(Some("down")).unwrap(),
15317 Some(ParallelEpQ8Scope::Down)
15318 );
15319 assert!(parse_parallel_ep_q8_scope(Some("input")).is_err());
15320 }
15321
15322 #[test]
15323 fn w4a16_device_ep_accepts_a_capacity_backed_active_prefix() {
15324 let width = 4096;
15325 assert_eq!(
15326 nvfp4_ep_active_input_values(160 * width, 44, width).unwrap(),
15327 44 * width
15328 );
15329 assert_eq!(
15330 nvfp4_ep_active_input_values(44 * width, 44, width).unwrap(),
15331 44 * width
15332 );
15333 assert!(nvfp4_ep_active_input_values(43 * width, 44, width).is_err());
15334 assert!(
15335 nvfp4_ep_active_input_values(160 * width, NVFP4_EP_DEVICE_BATCH_CAP + 1, width)
15336 .is_err()
15337 );
15338 }
15339
15340 #[test]
15341 fn step_ep_layer_specs_are_literal_and_fail_closed() {
15342 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
15343 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
15344 assert_eq!(
15345 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
15346 vec![StepEpLayerSpec {
15347 layer: 24,
15348 devices: vec![1, 2],
15349 }]
15350 );
15351 assert_eq!(
15352 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
15353 vec![
15354 StepEpLayerSpec {
15355 layer: 24,
15356 devices: vec![1, 2],
15357 },
15358 StepEpLayerSpec {
15359 layer: 25,
15360 devices: vec![1, 2],
15361 },
15362 StepEpLayerSpec {
15363 layer: 31,
15364 devices: vec![0, 2],
15365 },
15366 ]
15367 );
15368 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
15369 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
15370 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
15371 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
15372 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
15373 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
15374 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
15375 }
15376
15377 #[test]
15378 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
15379 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
15380 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
15381 assert_eq!(
15382 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
15383 vec![
15384 StepTpLayerSpec {
15385 layer: 24,
15386 devices: vec![1, 2],
15387 },
15388 StepTpLayerSpec {
15389 layer: 25,
15390 devices: vec![1, 2],
15391 },
15392 ]
15393 );
15394 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
15395 assert!(error.contains("MEMRA_STEP_TP"));
15396 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
15397 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
15398
15399 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
15400 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
15401 assert_eq!(all.first().unwrap().layer, 0);
15402 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
15403 let devices = (0..8).collect::<Vec<_>>();
15404 assert!(all.iter().all(|spec| spec.devices == devices));
15405 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
15406 }
15407}
15408
15409struct TokenGraphChild {
15421 #[allow(dead_code)]
15422 graph: cudarc::driver::CudaGraph,
15424 node: cudarc::driver::sys::CUgraphNode,
15425 ctx: cudarc::driver::sys::CUcontext,
15426}
15427
15428struct TokenGraphFaSite {
15432 ctx: cudarc::driver::sys::CUcontext,
15433 memset_o: cudarc::driver::sys::CUgraphNode,
15434 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
15435 fa: cudarc::driver::sys::CUgraphNode,
15436 combine: cudarc::driver::sys::CUgraphNode,
15437 window: usize,
15438 n_head: usize,
15439 n_head_kv: usize,
15440 head_dim: usize,
15441}
15442
15443pub struct TokenGraphBuilder {
15444 parent: cudarc::driver::sys::CUgraph,
15445 children: Vec<TokenGraphChild>,
15446 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
15449 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
15452 group: Option<(
15455 u32,
15456 Vec<cudarc::driver::sys::CUgraphNode>,
15457 Vec<cudarc::driver::sys::CUgraphNode>,
15458 )>,
15459}
15460
15461unsafe impl Send for TokenGraphBuilder {}
15463
15464impl TokenGraphBuilder {
15465 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
15466 use cudarc::driver::sys;
15467 let mut parent: sys::CUgraph = std::ptr::null_mut();
15468 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
15469 if r != sys::CUresult::CUDA_SUCCESS {
15470 return Err(format!("token graph create: {r:?}").into());
15471 }
15472 Ok(Self {
15473 parent,
15474 children: Vec::new(),
15475 frontier: Vec::new(),
15476 pending_detached: Vec::new(),
15477 group: None,
15478 })
15479 }
15480
15481 fn push_child(
15482 &mut self,
15483 graph: cudarc::driver::CudaGraph,
15484 parallel_group: Option<u32>,
15485 detached: bool,
15486 absorb: bool,
15487 ctx: cudarc::driver::sys::CUcontext,
15488 ) -> Result<(), Box<dyn std::error::Error>> {
15489 use cudarc::driver::sys;
15490 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
15494 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
15495 (state, Some(group)) => {
15496 if let Some((_, _, members)) = state.take() {
15498 self.frontier = members;
15499 }
15500 let base = self.frontier.clone();
15501 *state = Some((group, base.clone(), Vec::new()));
15502 base
15503 }
15504 (state, None) if detached => match state.as_ref() {
15505 Some((_, base, _)) => base.clone(),
15506 None => self.frontier.clone(),
15507 },
15508 (state, None) => {
15509 if let Some((_, _, members)) = state.take() {
15510 self.frontier = members;
15511 }
15512 let mut deps = self.frontier.clone();
15513 if absorb {
15514 deps.append(&mut self.pending_detached);
15515 }
15516 deps
15517 }
15518 };
15519 let mut node: sys::CUgraphNode = std::ptr::null_mut();
15520 let r = unsafe {
15521 sys::cuGraphAddChildGraphNode(
15522 &mut node,
15523 self.parent,
15524 if deps.is_empty() {
15525 std::ptr::null()
15526 } else {
15527 deps.as_ptr()
15528 },
15529 deps.len(),
15530 graph.cu_graph(),
15531 )
15532 };
15533 if r != sys::CUresult::CUDA_SUCCESS {
15534 return Err(format!("token graph child: {r:?}").into());
15535 }
15536 match (&mut self.group, parallel_group, detached) {
15537 (_, None, true) => self.pending_detached.push(node),
15538 (Some((_, _, members)), Some(_), _) => members.push(node),
15539 _ => self.frontier = vec![node],
15540 }
15541 self.children.push(TokenGraphChild { graph, node, ctx });
15542 Ok(())
15543 }
15544
15545 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
15546 use cudarc::driver::sys;
15547 if let Some((_, _, members)) = self.group.take() {
15548 self.frontier = members;
15549 }
15550 let mut fa_sites = Vec::new();
15553 for child in &self.children {
15554 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
15555 fa_sites.push(site);
15556 }
15557 }
15558 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
15559 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
15560 if r != sys::CUresult::CUDA_SUCCESS {
15561 return Err(format!("token graph instantiate: {r:?}").into());
15562 }
15563 Ok(TokenGraph {
15564 exec,
15565 parent: self.parent,
15566 _children: self.children,
15567 fa_sites,
15568 })
15569 }
15570}
15571
15572fn discover_fa_site(
15575 child_node: cudarc::driver::sys::CUgraphNode,
15576 ctx: cudarc::driver::sys::CUcontext,
15577) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
15578 use cudarc::driver::sys;
15579 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
15580 if r == sys::CUresult::CUDA_SUCCESS {
15581 Ok(())
15582 } else {
15583 Err(format!("{what}: {r:?}").into())
15584 }
15585 }
15586 let mut graph: sys::CUgraph = std::ptr::null_mut();
15587 unsafe {
15588 cu_try(
15589 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
15590 "fa-site child GetGraph",
15591 )?;
15592 }
15593 let mut count: usize = 0;
15594 unsafe {
15595 cu_try(
15596 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
15597 "fa-site GetNodes(count)",
15598 )?;
15599 }
15600 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
15601 unsafe {
15602 cu_try(
15603 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
15604 "fa-site GetNodes",
15605 )?;
15606 }
15607 nodes.truncate(count);
15608 let node_type =
15609 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
15610 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
15611 unsafe {
15612 cu_try(
15613 sys::cuGraphNodeGetType(node, &mut ty),
15614 "fa-site NodeGetType",
15615 )?;
15616 }
15617 Ok(ty)
15618 };
15619 let memsets: Vec<sys::CUgraphNode> = {
15620 let mut v = Vec::new();
15621 for &node in &nodes {
15622 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
15623 v.push(node);
15624 }
15625 }
15626 v
15627 };
15628 if memsets.len() != 3 {
15629 return Ok(None);
15630 }
15631 let dependents =
15633 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
15634 let mut n: usize = 0;
15635 unsafe {
15636 cu_try(
15637 sys::cuGraphNodeGetDependentNodes_v2(
15638 node,
15639 std::ptr::null_mut(),
15640 std::ptr::null_mut(),
15641 &mut n,
15642 ),
15643 "fa-site GetDependentNodes(count)",
15644 )?;
15645 }
15646 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
15647 unsafe {
15648 cu_try(
15649 sys::cuGraphNodeGetDependentNodes_v2(
15650 node,
15651 v.as_mut_ptr(),
15652 std::ptr::null_mut(),
15653 &mut n,
15654 ),
15655 "fa-site GetDependentNodes",
15656 )?;
15657 }
15658 v.truncate(n);
15659 Ok(v)
15660 };
15661 let mut fa: Option<sys::CUgraphNode> = None;
15664 let mut last_memset: Option<sys::CUgraphNode> = None;
15665 for &ms in &memsets {
15666 for dep in dependents(ms)? {
15667 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
15668 fa = Some(dep);
15669 last_memset = Some(ms);
15670 }
15671 }
15672 }
15673 let (Some(fa), Some(_last)) = (fa, last_memset) else {
15674 return Ok(None);
15675 };
15676 let mut combine: Option<sys::CUgraphNode> = None;
15677 for dep in dependents(fa)? {
15678 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
15679 combine = Some(dep);
15680 }
15681 }
15682 let Some(combine) = combine else {
15683 return Ok(None);
15684 };
15685 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
15688 unsafe {
15689 cu_try(
15690 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
15691 "fa-site KernelNodeGetParams",
15692 )?;
15693 }
15694 let arg_i32 =
15695 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
15696 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
15697 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
15699 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
15700 unsafe {
15701 cu_try(
15702 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
15703 "fa-site MemsetNodeGetParams",
15704 )?;
15705 }
15706 Ok(mp.width)
15707 };
15708 let mut widest = memsets[0];
15709 for &ms in &memsets[1..] {
15710 if width_of(ms)? > width_of(widest)? {
15711 widest = ms;
15712 }
15713 }
15714 let memset_m: Vec<sys::CUgraphNode> =
15715 memsets.iter().copied().filter(|&m| m != widest).collect();
15716 Ok(Some(TokenGraphFaSite {
15717 ctx,
15718 memset_o: widest,
15719 memset_m: [memset_m[0], memset_m[1]],
15720 fa,
15721 combine,
15722 window: win as usize,
15723 n_head: nh as usize,
15724 n_head_kv: nhkv as usize,
15725 head_dim: hd as usize,
15726 }))
15727}
15728
15729pub struct TokenGraph {
15730 exec: cudarc::driver::sys::CUgraphExec,
15731 parent: cudarc::driver::sys::CUgraph,
15732 _children: Vec<TokenGraphChild>,
15733 fa_sites: Vec<TokenGraphFaSite>,
15734}
15735
15736unsafe impl Send for TokenGraph {}
15737
15738impl TokenGraph {
15739 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
15744 use cudarc::driver::sys;
15745 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
15746 if r == sys::CUresult::CUDA_SUCCESS {
15747 Ok(())
15748 } else {
15749 Err(format!("{what}: {r:?}").into())
15750 }
15751 }
15752 for site in &self.fa_sites {
15753 let layer_bucket = if site.window > 0 {
15754 bucket.min(site.window)
15755 } else {
15756 bucket
15757 };
15758 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
15759 let nsp = layer_bucket.div_ceil(sp).max(1);
15760 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
15762 unsafe {
15763 cu_try(
15764 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
15765 "retarget fa GetParams",
15766 )?;
15767 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
15768 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
15769 params.gridDimY = nsp as u32;
15770 cu_try(
15771 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
15772 "retarget fa SetParams",
15773 )?;
15774 }
15775 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
15777 unsafe {
15778 cu_try(
15779 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
15780 "retarget combine GetParams",
15781 )?;
15782 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
15783 cu_try(
15784 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
15785 "retarget combine SetParams",
15786 )?;
15787 }
15788 let set_width =
15790 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
15791 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
15792 unsafe {
15793 cu_try(
15794 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
15795 "retarget memset GetParams",
15796 )?;
15797 }
15798 mp.width = width;
15799 unsafe {
15800 cu_try(
15801 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
15802 "retarget memset SetParams",
15803 )?;
15804 }
15805 Ok(())
15806 };
15807 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
15808 set_width(site.memset_m[0], site.n_head * nsp)?;
15809 set_width(site.memset_m[1], site.n_head * nsp)?;
15810 }
15811 Ok(())
15812 }
15813
15814 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
15815 use cudarc::driver::sys;
15816 let _main = e.gpu.enter_main()?;
15817 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
15818 if r != sys::CUresult::CUDA_SUCCESS {
15819 return Err(format!("token graph launch: {r:?}").into());
15820 }
15821 Ok(())
15822 }
15823}
15824
15825impl Drop for TokenGraph {
15826 fn drop(&mut self) {
15827 unsafe {
15828 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
15829 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
15830 }
15831 }
15832}
15833
15834std::thread_local! {
15835 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
15836 const { std::cell::RefCell::new(None) };
15837}
15838
15839pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
15841 let builder = TokenGraphBuilder::new()?;
15842 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
15843 Ok(())
15844}
15845
15846pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
15848 let builder = TOKEN_GRAPH_BUILDER
15849 .with(|cell| cell.borrow_mut().take())
15850 .ok_or("token graph build was not begun")?;
15851 builder.finish()
15852}
15853
15854pub fn token_graph_building() -> bool {
15856 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
15857}
15858
15859pub fn graph_section<F>(
15864 engine: &Engine,
15865 parallel_group: Option<u32>,
15866 f: F,
15867) -> Result<(), Box<dyn std::error::Error>>
15868where
15869 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
15870{
15871 graph_section_opts(engine, parallel_group, false, false, f)
15872}
15873
15874pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
15876where
15877 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
15878{
15879 graph_section_opts(engine, None, false, true, f)
15880}
15881
15882pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
15885where
15886 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
15887{
15888 graph_section_opts(engine, None, true, false, f)
15889}
15890
15891pub fn graph_section_opts<F>(
15892 engine: &Engine,
15893 parallel_group: Option<u32>,
15894 detached: bool,
15895 absorb: bool,
15896 f: F,
15897) -> Result<(), Box<dyn std::error::Error>>
15898where
15899 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
15900{
15901 let building = token_graph_building();
15902 if !building {
15903 let mut f = f;
15904 return f();
15905 }
15906 let (child, ctx) = {
15907 let _main = engine.gpu.enter_main()?;
15908 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
15909 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
15910 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
15911 return Err(format!("graph section ctx query: {r:?}").into());
15912 }
15913 let mut f = f;
15914 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
15917 (child, ctx)
15918 };
15919 TOKEN_GRAPH_BUILDER.with(|cell| {
15920 cell.borrow_mut()
15921 .as_mut()
15922 .expect("builder checked above")
15923 .push_child(child, parallel_group, detached, absorb, ctx)
15924 })
15925}