1use crate::Engine;
9use crate::mmq_ffi::{DeviceExpertCsr, ExpertCsr, Fp8GroupedWorkspace};
10use crate::parallel::{PRODUCT_MAX_CARDS, STEP37_TRUNK_LAYERS};
11use cudarc::driver::{CudaEvent, CudaSlice, DeviceSlice};
12use std::ops::Range;
13
14static DETERM_PREV: std::sync::OnceLock<
17 std::sync::Mutex<std::collections::HashMap<(usize, usize), Vec<f32>>>,
18> = std::sync::OnceLock::new();
19
20const FP8_BLOCK: usize = 128;
21const NATIVE_P2P_PROBE_WORDS: &[usize] = &[4096, 16_384, 262_144, 16_777_216];
22const STEP_GROUPED_FP8_EXPERTS: usize = 288;
23const STEP_GROUPED_FP8_TOP_K: usize = 8;
24const STEP_GROUPED_FP8_WIDTH: usize = 1280;
25
26fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
27 if let Some(limit) = limit {
28 if !limit.is_finite() || limit <= 0.0 {
29 return Err(format!(
30 "Step routed-expert activation limit must be positive and finite, got {limit}"
31 ));
32 }
33 }
34 Ok(())
35}
36
37pub(crate) fn routes_prestage_on() -> bool {
60 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
61 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
62}
63
64pub(crate) fn oproj_tail_on() -> bool {
82 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
83 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
84}
85thread_local! {
86 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
87 const { std::cell::Cell::new(None) };
88}
89thread_local! {
90 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
95}
96pub(crate) struct OprojTailScope(());
98pub(crate) fn oproj_tail_scope() -> OprojTailScope {
99 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
100 OprojTailScope(())
101}
102impl Drop for OprojTailScope {
103 fn drop(&mut self) {
104 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
105 OPROJ_TAIL_PENDING.with(|c| c.set(None));
107 }
108}
109thread_local! {
110 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
113}
114pub(crate) fn set_verify_tcol(c: Option<usize>) {
115 VERIFY_TCOL.with(|x| x.set(c));
116}
117pub(crate) fn take_verify_tcol() -> Option<usize> {
118 VERIFY_TCOL.with(|x| x.take())
119}
120
121pub(crate) fn tcol_oproj_on() -> bool {
128 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
129 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
130}
131thread_local! {
132 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
136 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
137}
138pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
139 TCOL_OPROJ_DEFER.with(|x| x.set(c));
140}
141pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
142 TCOL_OPROJ_DEFER.with(|x| x.take())
143}
144pub(crate) fn set_tcol_oproj_stashed() {
145 TCOL_OPROJ_STASHED.with(|x| x.set(true));
146}
147pub(crate) fn take_tcol_oproj_stashed() -> bool {
148 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
149}
150
151pub(crate) fn oproj_tail_eligible() -> bool {
152 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
153}
154pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
155 OPROJ_TAIL_PENDING.with(|c| c.take())
156}
157pub(crate) fn set_oproj_tail(v: (u64, u64)) {
158 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
159}
160
161pub(crate) fn rank0_merge_on() -> bool {
162 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
163 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
164}
165
166pub(crate) fn len_mirror_lazy_on() -> bool {
167 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
168 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
169}
170
171pub(crate) fn fence_memops_on() -> bool {
172 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
173 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
174}
175
176pub(crate) fn moe_direct_on() -> bool {
177 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
178 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
179}
180
181pub(crate) fn fence_rank1_on() -> bool {
193 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
194 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
195}
196
197pub(crate) fn rows_tab_host(
221 parts_rank: &[[u64; 4]],
222 ctr_base: u64,
223 same_session: bool,
224 t: usize,
225) -> Vec<u64> {
226 let mut host = Vec::with_capacity(t * 6);
227 for (r, parts) in parts_rank.iter().enumerate().take(t) {
228 host.extend_from_slice(&[
229 parts[0],
230 parts[1],
231 parts[2],
232 parts[3],
233 if same_session {
234 ctr_base
235 } else {
236 ctr_base + (r as u64) * 4
237 },
238 if same_session {
239 (t - 1 - r) as u64
240 } else {
241 0u64
242 },
243 ]);
244 }
245 host
246}
247
248#[cfg(test)]
252pub(crate) fn retired_rows_tab_key(kp: u64, bp: u64, il: usize, t: usize) -> u64 {
253 kp.rotate_left(17)
254 .wrapping_add(bp)
255 .wrapping_add((il as u64) << 32)
256 .wrapping_add(t as u64)
257 .wrapping_add(1 << 63)
258}
259
260pub(crate) fn rows_tab_restage_on() -> bool {
261 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
262 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_RESTAGE").as_deref() != Ok("0"))
263}
264
265pub(crate) fn rows_tab_stale_scan() -> bool {
274 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
275 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_STALE_SCAN").as_deref() == Ok("1"))
276}
277
278pub(crate) static ROWS_TAB_ENGAGED: std::sync::atomic::AtomicU64 =
279 std::sync::atomic::AtomicU64::new(0);
280pub(crate) static ROWS_TAB_STALE: std::sync::atomic::AtomicU64 =
281 std::sync::atomic::AtomicU64::new(0);
282
283pub(crate) fn spec_fa2_on() -> bool {
284 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
285 crate::step37_door(&ENV, "MEMRA_SPEC_FA2")
286}
287thread_local! {
288 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
291 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
292}
293pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
294 SPEC_FA2_DEFER.with(|x| x.set(c));
295}
296pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
297 SPEC_FA2_DEFER.with(|x| x.take())
298}
299pub(crate) fn set_spec_fa2_stashed() {
300 SPEC_FA2_STASHED.with(|x| x.set(true));
301}
302pub(crate) fn take_spec_fa2_stashed() -> bool {
303 SPEC_FA2_STASHED.with(|x| x.replace(false))
304}
305
306pub(crate) fn sel_mirror_on() -> bool {
307 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
308 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
309}
310
311pub(crate) fn step_nvfp4_ep2_on() -> bool {
318 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
319 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
320}
321
322pub(crate) fn oproj_direct_on() -> bool {
323 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
324 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
325}
326
327pub(crate) fn raw_copy_bytes(
328 dst: u64,
329 src: u64,
330 bytes: usize,
331 engine: &Engine,
332) -> Result<(), Box<dyn std::error::Error>> {
333 use cudarc::driver::sys;
334 let r = unsafe {
335 sys::cuMemcpyAsync(
336 dst as sys::CUdeviceptr,
337 src as sys::CUdeviceptr,
338 bytes,
339 engine.stream().cu_stream() as sys::CUstream,
340 )
341 };
342 if r == sys::CUresult::CUDA_SUCCESS {
343 Ok(())
344 } else {
345 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
348 eprintln!(
349 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
350 std::backtrace::Backtrace::force_capture()
351 );
352 }
353 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
354 }
355}
356
357pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
358 let silu = gate / (1.0 + (-gate).exp());
359 match limit {
360 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
361 None => silu * up,
362 }
363}
364
365#[derive(Debug, Clone, PartialEq, Eq)]
366struct ExpertOwnerRoutes {
367 rank: usize,
368 selected: Vec<usize>,
369 token_rows: Vec<usize>,
370 global_pairs: Vec<usize>,
371}
372
373fn partition_expert_owner_routes(
374 expert_count: usize,
375 ranks: usize,
376 tokens: usize,
377 experts_per_token: usize,
378 selected: &[usize],
379) -> Result<Vec<ExpertOwnerRoutes>, String> {
380 if expert_count == 0
381 || ranks == 0
382 || tokens == 0
383 || experts_per_token == 0
384 || expert_count % ranks != 0
385 {
386 return Err(format!(
387 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
388 tokens={tokens} experts_per_token={experts_per_token}"
389 ));
390 }
391 let pairs = tokens
392 .checked_mul(experts_per_token)
393 .ok_or("expert-owner route count overflow")?;
394 if selected.len() != pairs {
395 return Err(format!(
396 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
397 selected.len()
398 ));
399 }
400 let per_rank = expert_count / ranks;
401 let mut owners = (0..ranks)
402 .map(|rank| ExpertOwnerRoutes {
403 rank,
404 selected: Vec::new(),
405 token_rows: Vec::new(),
406 global_pairs: Vec::new(),
407 })
408 .collect::<Vec<_>>();
409 for (pair, &expert) in selected.iter().enumerate() {
410 if expert >= expert_count {
411 return Err(format!(
412 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
413 ));
414 }
415 let rank = expert / per_rank;
416 owners[rank].selected.push(expert - rank * per_rank);
417 owners[rank].token_rows.push(pair / experts_per_token);
418 owners[rank].global_pairs.push(pair);
419 }
420 Ok(owners)
421}
422
423fn validate_step_grouped_owner_routes(
424 expert_count: usize,
425 tokens: usize,
426 selected: &[usize],
427) -> Result<usize, String> {
428 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
429 return Err(format!(
430 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
431 experts={expert_count} tokens={tokens}",
432 STEP_GROUPED_FP8_EXPERTS
433 ));
434 }
435 let pairs = tokens
436 .checked_mul(STEP_GROUPED_FP8_TOP_K)
437 .ok_or("official Step owner-grouped FP8 route count overflow")?;
438 if selected.len() != pairs {
439 return Err(format!(
440 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
441 selected.len(),
442 STEP_GROUPED_FP8_TOP_K,
443 ));
444 }
445 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
446 let mut unique = routes.to_vec();
447 unique.sort_unstable();
448 unique.dedup();
449 if unique.len() != STEP_GROUPED_FP8_TOP_K {
450 return Err(format!(
451 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
452 {routes:?}"
453 ));
454 }
455 }
456 Ok(pairs)
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
460struct WeightedRouteCombineShape {
461 pairs: usize,
462 max_pairs: usize,
463}
464
465fn validate_weighted_route_combine(
466 width: usize,
467 experts_per_token: usize,
468 max_tokens: usize,
469 tokens: usize,
470 owner_global_pairs: &[&[usize]],
471 route_weights: &[f32],
472) -> Result<WeightedRouteCombineShape, String> {
473 if width == 0
474 || experts_per_token == 0
475 || max_tokens == 0
476 || tokens == 0
477 || tokens > max_tokens
478 || width > i32::MAX as usize
479 || experts_per_token > i32::MAX as usize
480 || tokens > i32::MAX as usize
481 {
482 return Err(format!(
483 "invalid weighted route combine geometry width={width} experts_per_token=\
484 {experts_per_token} tokens={tokens}/{max_tokens}"
485 ));
486 }
487 let pairs = tokens
488 .checked_mul(experts_per_token)
489 .ok_or("weighted route combine pair count overflow")?;
490 let max_pairs = max_tokens
491 .checked_mul(experts_per_token)
492 .ok_or("weighted route combine capacity overflow")?;
493 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
494 return Err(format!(
495 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
496 route_weights.len()
497 ));
498 }
499 let mut seen = vec![false; pairs];
500 let mut observed = 0usize;
501 for pairs_for_owner in owner_global_pairs {
502 observed = observed
503 .checked_add(pairs_for_owner.len())
504 .ok_or("weighted route combine observed pair count overflow")?;
505 for &pair in *pairs_for_owner {
506 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
507 return Err(format!(
508 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
509 ));
510 }
511 }
512 }
513 if observed != pairs || seen.iter().any(|present| !present) {
514 return Err(format!(
515 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
516 ));
517 }
518 Ok(WeightedRouteCombineShape { pairs, max_pairs })
519}
520
521fn cache_rank_rows(
522 rows: &[u8],
523 tokens: usize,
524 local_token_bytes: usize,
525 ranks: usize,
526 rank: usize,
527) -> Result<Vec<u8>, String> {
528 if ranks == 0 || rank >= ranks {
529 return Err(format!(
530 "TP cache rank {rank} is outside a {ranks}-rank layout"
531 ));
532 }
533 let global_token_bytes = local_token_bytes
534 .checked_mul(ranks)
535 .ok_or("TP cache global token-byte overflow")?;
536 let expected = tokens
537 .checked_mul(global_token_bytes)
538 .ok_or("TP cache row-byte overflow")?;
539 if rows.len() != expected {
540 return Err(format!(
541 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
542 rows.len()
543 ));
544 }
545 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
546 for token in 0..tokens {
547 let start = token * global_token_bytes + rank * local_token_bytes;
548 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
549 }
550 Ok(shard)
551}
552
553fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
554 match value {
555 None | Some("") | Some("0") => Ok(false),
556 Some("1") => Ok(true),
557 Some(value) => Err(format!(
558 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
559 )),
560 }
561}
562
563pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
564 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
565}
566
567fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
568 match value {
569 None | Some("") | Some("0") => Ok(false),
570 Some("1") => Ok(true),
571 Some(value) => Err(format!(
572 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
573 )),
574 }
575}
576
577pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
578 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
579}
580
581fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
582 match value {
583 None | Some("") | Some("0") => Ok(false),
584 Some("1") => Ok(true),
585 Some(value) => Err(format!(
586 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
587 )),
588 }
589}
590
591fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
592 match value {
593 None | Some("") | Some("0") => Ok(false),
594 Some("1") => Ok(true),
595 Some(value) => Err(format!(
596 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
597 )),
598 }
599}
600
601pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
604 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
605}
606
607pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
608 parse_step_ep_device_arithmetic(
609 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
610 .ok()
611 .as_deref(),
612 )
613}
614
615fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
616 match value {
617 None | Some("") | Some("0") => Ok(false),
618 Some("1") => Ok(true),
619 Some(value) => Err(format!(
620 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
621 )),
622 }
623}
624
625pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
626 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
627}
628
629fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
630 match value {
631 None | Some("") | Some("0") => Ok(false),
632 Some("1") => Ok(true),
633 Some(value) => Err(format!(
634 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
635 )),
636 }
637}
638
639pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
644 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
645}
646
647fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
648 match value {
649 None | Some("") | Some("0") => Ok(false),
650 Some("1") => Ok(true),
651 Some(value) => Err(format!(
652 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
653 )),
654 }
655}
656
657fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
658 match value {
659 None | Some("") | Some("0") => Ok(false),
660 Some("1") => Ok(true),
661 Some(value) => Err(format!(
662 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
663 )),
664 }
665}
666
667pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
671 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
672}
673
674fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
675 match value {
676 None | Some("") | Some("0") => Ok(false),
677 Some("1") => Ok(true),
678 Some(value) => Err(format!(
679 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
680 )),
681 }
682}
683
684fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
685 match value {
686 None | Some("") | Some("0") => Ok(false),
687 Some("1") => Ok(true),
688 Some(value) => Err(format!(
689 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
690 )),
691 }
692}
693
694pub fn step_tp_dcw_enabled() -> Result<bool, String> {
699 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
700}
701
702pub fn step_tp_graph_enabled() -> Result<bool, String> {
707 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
708}
709
710fn step_tp_graph_headroom_ok(e: &Engine) -> bool {
715 let ok = crate::spec::graph_launch_headroom_ok(e);
716 if !ok {
717 static NOTED: std::sync::Once = std::sync::Once::new();
718 NOTED.call_once(|| crate::spec::graph_replay_suspended_note("step-tp-routes"));
719 }
720 ok
721}
722
723pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
727 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
728}
729
730#[derive(Debug, Clone, PartialEq, Eq)]
731pub struct StepEpLayerSpec {
732 pub layer: usize,
733 pub devices: Vec<usize>,
734}
735
736pub type StepTpLayerSpec = StepEpLayerSpec;
737
738fn parse_step_layer_specs(
739 flag: &str,
740 value: Option<&str>,
741 allow_full_model: bool,
742) -> Result<Vec<StepEpLayerSpec>, String> {
743 let Some(value) = value else {
744 return Ok(Vec::new());
745 };
746 if value.is_empty() || value == "0" {
747 return Ok(Vec::new());
748 }
749
750 let mut specs = Vec::new();
751 for item in value.split(';') {
752 let (layers, devices) = item.split_once('@').ok_or_else(|| {
753 let layers = if allow_full_model {
754 "LAYER[-LAYER] or all"
755 } else {
756 "LAYER[-LAYER]"
757 };
758 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
759 })?;
760 let (first, last) = if layers == "all" {
761 if !allow_full_model {
762 return Err(format!(
763 "{flag} does not support the full-model shorthand; assign routed layers \
764 explicitly"
765 ));
766 }
767 (0, STEP37_TRUNK_LAYERS - 1)
768 } else {
769 match layers.split_once('-') {
770 Some((first, last)) => {
771 let first = first
772 .parse::<usize>()
773 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
774 let last = last
775 .parse::<usize>()
776 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
777 if first > last {
778 return Err(format!("{flag} layer range {first}-{last} is reversed"));
779 }
780 if last - first + 1 > 128 {
781 return Err(format!(
782 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
783 ));
784 }
785 (first, last)
786 }
787 None => {
788 let layer = layers
789 .parse::<usize>()
790 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
791 (layer, layer)
792 }
793 }
794 };
795 let devices = devices
796 .split(',')
797 .map(|device| {
798 device
799 .parse::<usize>()
800 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
801 })
802 .collect::<Result<Vec<_>, _>>()?;
803 if !(2..=8).contains(&devices.len()) {
804 return Err(format!(
805 "{flag} requires 2..=8 devices, got {}",
806 devices.len()
807 ));
808 }
809 let mut unique = devices.clone();
810 unique.sort_unstable();
811 unique.dedup();
812 if unique.len() != devices.len() {
813 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
814 }
815 for layer in first..=last {
816 if specs
817 .iter()
818 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
819 {
820 return Err(format!("{flag} assigns layer {layer} more than once"));
821 }
822 specs.push(StepEpLayerSpec {
823 layer,
824 devices: devices.clone(),
825 });
826 }
827 }
828 Ok(specs)
829}
830
831pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
832 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
833}
834
835pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
836 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
837}
838
839pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
840 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
841}
842
843pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
844 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
845}
846
847#[derive(Clone, Copy)]
848pub struct E4m3BlockMatrix<'a> {
849 pub codes: &'a [u8],
850 pub scales: &'a [f32],
851 pub out_features: usize,
852 pub in_features: usize,
853}
854
855impl E4m3BlockMatrix<'_> {
856 fn validate(&self) -> Result<(), String> {
857 let code_count = self
858 .out_features
859 .checked_mul(self.in_features)
860 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
861 if self.codes.len() != code_count {
862 return Err(format!(
863 "E4M3 code count {} != {}x{} ({code_count})",
864 self.codes.len(),
865 self.out_features,
866 self.in_features,
867 ));
868 }
869 let scale_count =
870 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
871 if self.scales.len() != scale_count {
872 return Err(format!(
873 "E4M3 scale count {} != {scale_count} for {}x{}",
874 self.scales.len(),
875 self.out_features,
876 self.in_features,
877 ));
878 }
879 if !self
880 .scales
881 .iter()
882 .all(|scale| scale.is_finite() && *scale > 0.0)
883 {
884 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
885 }
886 Ok(())
887 }
888}
889
890#[derive(Clone, Copy)]
891pub struct E4m3ExpertBank<'a> {
892 pub codes: &'a [u8],
893 pub scales: &'a [f32],
894 pub expert_count: usize,
895 pub out_features: usize,
896 pub in_features: usize,
897}
898
899impl E4m3ExpertBank<'_> {
900 fn validate(&self) -> Result<(), String> {
901 if self.expert_count == 0 {
902 return Err("E4M3 expert bank is empty".to_string());
903 }
904 let code_stride = self
905 .out_features
906 .checked_mul(self.in_features)
907 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
908 let code_count = self
909 .expert_count
910 .checked_mul(code_stride)
911 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
912 if self.codes.len() != code_count {
913 return Err(format!(
914 "E4M3 expert code count {} != {}x{} ({code_count})",
915 self.codes.len(),
916 self.expert_count,
917 code_stride,
918 ));
919 }
920 let scale_stride =
921 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
922 let scale_count = self
923 .expert_count
924 .checked_mul(scale_stride)
925 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
926 if self.scales.len() != scale_count {
927 return Err(format!(
928 "E4M3 expert scale count {} != {}x{} ({scale_count})",
929 self.scales.len(),
930 self.expert_count,
931 scale_stride,
932 ));
933 }
934 if !self
935 .scales
936 .iter()
937 .all(|scale| scale.is_finite() && *scale > 0.0)
938 {
939 return Err(
940 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
941 );
942 }
943 Ok(())
944 }
945
946 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
947 if expert >= self.expert_count {
948 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
949 }
950 let code_stride = self.out_features * self.in_features;
951 let scale_stride =
952 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
953 Ok(E4m3BlockMatrix {
954 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
955 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
956 out_features: self.out_features,
957 in_features: self.in_features,
958 })
959 }
960}
961
962pub struct ColumnParallelResult {
963 pub gathered: Vec<f32>,
964 pub rank_outputs: Vec<Vec<f32>>,
965}
966
967pub struct RowParallelResult {
968 pub reduced: Vec<f32>,
969 pub rank_partials: Vec<Vec<f32>>,
970}
971
972#[derive(Clone, Copy)]
973pub struct Bf16Matrix<'a> {
974 pub bytes: &'a [u8],
975 pub out_features: usize,
976 pub in_features: usize,
977}
978
979impl Bf16Matrix<'_> {
980 pub fn validate(&self) -> Result<(), String> {
981 if self.out_features == 0 || self.in_features == 0 {
982 return Err("BF16 matrix dimensions must be nonzero".into());
983 }
984 let expected = self
985 .out_features
986 .checked_mul(self.in_features)
987 .and_then(|values| values.checked_mul(2))
988 .ok_or("BF16 matrix byte count overflow")?;
989 if self.bytes.len() != expected {
990 return Err(format!(
991 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
992 self.bytes.len(),
993 self.out_features,
994 self.in_features,
995 ));
996 }
997 Ok(())
998 }
999}
1000
1001struct ResidentE4m3Rank {
1002 codes: CudaSlice<u8>,
1003 scales: CudaSlice<f32>,
1004 out_features: usize,
1005 in_features: usize,
1006}
1007
1008enum ResidentBf16Weight {
1009 Bf16(CudaSlice<u8>),
1010 F32(CudaSlice<f32>),
1011}
1012
1013impl ResidentBf16Weight {
1014 fn ordinal(&self) -> usize {
1015 match self {
1016 Self::Bf16(bytes) => bytes.ordinal(),
1017 Self::F32(values) => values.ordinal(),
1018 }
1019 }
1020}
1021
1022struct ResidentBf16Rank {
1023 weight: ResidentBf16Weight,
1024 out_features: usize,
1025 in_features: usize,
1026 q8: Option<CudaSlice<u8>>,
1029}
1030
1031pub struct ResidentColumnParallel {
1032 ranks: Vec<ResidentE4m3Rank>,
1033 out_features: usize,
1034 in_features: usize,
1035}
1036
1037pub struct ResidentRowParallel {
1038 ranks: Vec<ResidentE4m3Rank>,
1039 out_features: usize,
1040 in_features: usize,
1041}
1042
1043pub struct ResidentBf16ColumnParallel {
1044 ranks: Vec<ResidentBf16Rank>,
1045 out_features: usize,
1046 in_features: usize,
1047 canonical_chunk_rows: Option<usize>,
1048}
1049
1050pub struct ResidentBf16RowParallel {
1051 ranks: Vec<ResidentBf16Rank>,
1052 out_features: usize,
1053 in_features: usize,
1054}
1055
1056pub struct ResidentStepBf16RowParallel {
1057 ranks: Vec<Vec<ResidentBf16Rank>>,
1058 out_features: usize,
1059 in_features: usize,
1060 canonical_chunk_cols: usize,
1061}
1062
1063pub struct ResidentSigmoidTopKRouter {
1065 weight: CudaSlice<f32>,
1066 correction_bias: CudaSlice<f32>,
1067 active: CudaSlice<u8>,
1068 root_device: usize,
1069 input_width: usize,
1070 expert_count: usize,
1071 experts_per_token: usize,
1072 active_count: usize,
1073 scaling_factor: f32,
1074 route_norm: bool,
1075}
1076
1077pub struct SigmoidTopKHostOutput {
1078 pub logits: Vec<f32>,
1079 pub selected: Vec<u32>,
1080 pub weights: Vec<f32>,
1081}
1082
1083pub struct ResidentReplicatedBf16SwiGlu {
1085 gate: Vec<ResidentBf16Rank>,
1086 up: Vec<ResidentBf16Rank>,
1087 down: Vec<ResidentBf16Rank>,
1088 input_width: usize,
1089 intermediate_width: usize,
1090}
1091
1092pub struct ResidentReplicatedDeviceRows {
1097 ranks: Vec<CudaSlice<f32>>,
1098 tokens: usize,
1099 width: usize,
1100}
1101
1102impl ResidentReplicatedDeviceRows {
1103 pub fn tokens(&self) -> usize {
1104 self.tokens
1105 }
1106
1107 pub fn width(&self) -> usize {
1108 self.width
1109 }
1110
1111 pub fn ranks(&self) -> usize {
1112 self.ranks.len()
1113 }
1114}
1115
1116pub fn moe_residual_host(
1118 residual: &[f32],
1119 routed: &[f32],
1120 shared: &[f32],
1121) -> Result<Vec<f32>, String> {
1122 if residual.len() != routed.len() || residual.len() != shared.len() {
1123 return Err(format!(
1124 "MoE residual lengths residual={} routed={} shared={}",
1125 residual.len(),
1126 routed.len(),
1127 shared.len()
1128 ));
1129 }
1130 let ffn = routed
1131 .iter()
1132 .zip(shared)
1133 .map(|(&routed, &shared)| routed + shared)
1134 .collect::<Vec<_>>();
1135 Ok(residual
1136 .iter()
1137 .zip(ffn)
1138 .map(|(&residual, ffn)| residual + ffn)
1139 .collect())
1140}
1141
1142pub use memra_kv::{
1143 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1144};
1145
1146pub struct ResidentTpExpert {
1152 gate: ResidentColumnParallel,
1153 up: ResidentColumnParallel,
1154 down: ResidentRowParallel,
1155 input_width: usize,
1156 expert_width: usize,
1157}
1158
1159struct ResidentE4m3ExpertBankRank {
1160 codes: CudaSlice<u8>,
1161 scales: CudaSlice<f32>,
1162 expert_range: Range<usize>,
1163 out_features: usize,
1164 in_features: usize,
1165 code_stride: usize,
1166 scale_stride: usize,
1167 k_blocks: Option<usize>,
1170}
1171
1172struct PackedE4m3ExpertBankRank {
1173 codes: Vec<u8>,
1174 scales: Vec<f32>,
1175 expert_range: Range<usize>,
1176 out_features: usize,
1177 in_features: usize,
1178 code_stride: usize,
1179 scale_stride: usize,
1180 k_blocks: Option<usize>,
1181}
1182
1183struct ResidentEpRank {
1184 gate: ResidentE4m3ExpertBankRank,
1185 up: ResidentE4m3ExpertBankRank,
1186 down: ResidentE4m3ExpertBankRank,
1187}
1188
1189pub struct ResidentExpertParallel {
1196 ranks: Vec<ResidentEpRank>,
1197 expert_count: usize,
1198 input_width: usize,
1199 expert_width: usize,
1200}
1201
1202pub struct StepGroupedFp8ProjectionOutput {
1207 pub gate: Vec<f32>,
1208 pub up: Vec<f32>,
1209 pub down: Vec<f32>,
1210}
1211
1212pub struct PreparedStepGroupedFp8Gate {
1217 device: usize,
1218 gate: ResidentE4m3ExpertBankRank,
1219 up: ResidentE4m3ExpertBankRank,
1220 down: ResidentE4m3ExpertBankRank,
1221 input: CudaSlice<f32>,
1222 route_csr: DeviceExpertCsr,
1223 down_csr: DeviceExpertCsr,
1224 gate_workspace: Fp8GroupedWorkspace,
1225 up_workspace: Fp8GroupedWorkspace,
1226 down_workspace: Fp8GroupedWorkspace,
1227 activation: CudaSlice<f32>,
1228 activation_limit: Option<f32>,
1229 tokens: usize,
1230 pairs: usize,
1231}
1232
1233impl PreparedStepGroupedFp8Gate {
1234 pub fn tokens(&self) -> usize {
1235 self.tokens
1236 }
1237
1238 pub fn pairs(&self) -> usize {
1239 self.pairs
1240 }
1241}
1242
1243struct PreparedStepGroupedExpertOwner {
1244 rank: usize,
1245 global_pairs: Vec<usize>,
1246 route_csr: DeviceExpertCsr,
1247 down_csr: DeviceExpertCsr,
1248 gate_workspace: Fp8GroupedWorkspace,
1249 up_workspace: Fp8GroupedWorkspace,
1250 down_workspace: Fp8GroupedWorkspace,
1251 activation: CudaSlice<f32>,
1252}
1253
1254struct StepGroupedExpertOwnerSchedule {
1255 global_pairs: Vec<usize>,
1256 route_csr: ExpertCsr,
1257 down_csr: ExpertCsr,
1258}
1259
1260pub struct PreparedStepGroupedExpertParallelGate {
1266 rank_inputs: Vec<CudaSlice<f32>>,
1267 owners: Vec<PreparedStepGroupedExpertOwner>,
1268 activation_limit: Option<f32>,
1269 tokens: usize,
1270 pairs: usize,
1271 max_tokens: usize,
1272 max_pairs: usize,
1273 input_width: usize,
1274 expert_width: usize,
1275 generation: u64,
1276 executed_generation: Option<u64>,
1277 ready: bool,
1278}
1279
1280impl PreparedStepGroupedExpertParallelGate {
1281 pub fn tokens(&self) -> usize {
1282 self.tokens
1283 }
1284
1285 pub fn pairs(&self) -> usize {
1286 self.pairs
1287 }
1288
1289 pub fn max_tokens(&self) -> usize {
1290 self.max_tokens
1291 }
1292
1293 pub fn input_width(&self) -> usize {
1294 self.input_width
1295 }
1296
1297 pub fn expert_width(&self) -> usize {
1298 self.expert_width
1299 }
1300
1301 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1302 validate_step_expert_activation_limit(limit)?;
1303 self.activation_limit = limit;
1304 self.executed_generation = None;
1305 Ok(())
1306 }
1307
1308 pub fn active_owners(&self) -> usize {
1309 self.owners
1310 .iter()
1311 .filter(|owner| !owner.global_pairs.is_empty())
1312 .count()
1313 }
1314
1315 pub fn owner_pair_counts(&self) -> Vec<usize> {
1316 self.owners
1317 .iter()
1318 .map(|owner| owner.global_pairs.len())
1319 .collect()
1320 }
1321
1322 pub fn generation(&self) -> u64 {
1323 self.generation
1324 }
1325}
1326
1327struct PreparedPeerWeightedRouteOwner {
1328 token_rows: CudaSlice<i32>,
1329 slots: CudaSlice<i32>,
1330 weights: CudaSlice<f32>,
1331 active_pairs: usize,
1332}
1333
1334pub struct PreparedPeerWeightedRouteCombine {
1340 root_device: usize,
1341 owners: Vec<PreparedPeerWeightedRouteOwner>,
1342 peer_staging: CudaSlice<f32>,
1343 slots: CudaSlice<f32>,
1344 weights: CudaSlice<f32>,
1345 output: CudaSlice<f32>,
1346 peer_devices: Vec<usize>,
1347 peer_outputs: Vec<CudaSlice<f32>>,
1348 width: usize,
1349 experts_per_token: usize,
1350 max_tokens: usize,
1351 max_pairs: usize,
1352 tokens: usize,
1353 pairs: usize,
1354 projection_generation: u64,
1355 output_generation: Option<u64>,
1356 broadcast_generation: Option<u64>,
1357 ready: bool,
1358}
1359
1360impl PreparedPeerWeightedRouteCombine {
1361 pub fn tokens(&self) -> usize {
1362 self.tokens
1363 }
1364
1365 pub fn pairs(&self) -> usize {
1366 self.pairs
1367 }
1368
1369 pub fn owner_pair_counts(&self) -> Vec<usize> {
1370 self.owners.iter().map(|owner| owner.active_pairs).collect()
1371 }
1372
1373 pub fn distributed_ranks(&self) -> usize {
1374 1 + self.peer_outputs.len()
1375 }
1376}
1377
1378struct ResidentTpExpertBank {
1379 gate: Vec<ResidentE4m3ExpertBankRank>,
1380 up: Vec<ResidentE4m3ExpertBankRank>,
1381 down: Vec<ResidentE4m3ExpertBankRank>,
1382 expert_count: usize,
1383 input_width: usize,
1384 expert_width: usize,
1385}
1386
1387pub struct ResidentTensorParallel {
1393 bank: ResidentTpExpertBank,
1394}
1395
1396pub struct TpE4m3HostBounce {
1402 devices: Vec<usize>,
1403 ranks: Vec<Engine>,
1404 native_p2p: bool,
1405 ep_device_arithmetic: bool,
1406 bulk_p2p: bool,
1407 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1410}
1411
1412pub enum StepTpGateShards<'a> {
1421 F32(&'a [crate::CudaSlice<f32>]),
1422 Bf16(&'a [crate::CudaSlice<u8>]),
1423}
1424
1425pub struct StepTpDecodeV2Ws {
1426 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1430 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1431 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1432 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1433 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1434 pub(crate) tcol_cap: usize,
1435 w8_aq: Vec<CudaSlice<i8>>,
1439 w8_ad: Vec<CudaSlice<f32>>,
1440 w8_in: usize,
1441 w8o_aq: Vec<CudaSlice<i8>>,
1444 w8o_ad: Vec<CudaSlice<f32>>,
1445 w8o_in: usize,
1446 w8t_aq: Vec<CudaSlice<i8>>,
1450 w8t_ad: Vec<CudaSlice<f32>>,
1451 w8t_in: usize,
1452 w8t_oaq: Vec<CudaSlice<i8>>,
1453 w8t_oad: Vec<CudaSlice<f32>>,
1454 w8t_oin: usize,
1455 w8t_cap: usize,
1456 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1463 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1464 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1465 pub(crate) fa2_cap: usize,
1466 rope_k_t: Vec<CudaSlice<f32>>,
1470 rope_ctr_t: Vec<CudaSlice<u32>>,
1471 rope_pos_t: Vec<CudaSlice<i32>>,
1472 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1476 rows_tab_t: Vec<CudaSlice<u64>>,
1486 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1490 tcol_gated: Vec<CudaSlice<f32>>,
1491 tcol_opart: Vec<CudaSlice<f32>>,
1492 tcol_opeer: Option<CudaSlice<f32>>,
1493 tcol_omix: Option<CudaSlice<f32>>,
1494 tcol_ocap: usize,
1495 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1498 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1499 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1500 pub(crate) q: Vec<CudaSlice<f32>>,
1501 pub(crate) k: Vec<CudaSlice<f32>>,
1502 pub(crate) pos: Vec<CudaSlice<i32>>,
1503 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1505 pub(crate) gate: Vec<CudaSlice<f32>>,
1506 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1507 pub(crate) gated: Vec<CudaSlice<f32>>,
1508 o_partials: Vec<Vec<CudaSlice<f32>>>,
1510 ev_rank: Vec<CudaEvent>,
1512 peer_partial: CudaSlice<f32>,
1514 reduce_a: CudaSlice<f32>,
1515 reduce_b: CudaSlice<f32>,
1516 zeros: CudaSlice<f32>,
1518 pub(crate) k_shadow: CudaSlice<f32>,
1519 pub(crate) v_shadow: CudaSlice<f32>,
1520 ev_refresh: CudaEvent,
1521 ev_oproj: CudaEvent,
1522 gate_e: CudaSlice<f32>,
1524 pub(crate) h_stage: Option<CudaSlice<f32>>,
1527 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1528 attn_in: Vec<CudaSlice<f32>>,
1532 raw_h_stage: u64,
1534 raw_pos_stage: u64,
1535 raw_attn_in: Vec<u64>,
1536 raw_pos: Vec<u64>,
1537 raw_o_partial1: u64,
1538 raw_peer_partial: u64,
1539 raw_k1: u64,
1540 raw_v1: u64,
1541 raw_k_shadow: u64,
1542 raw_v_shadow: u64,
1543 raw_mixed_stage_e: u64,
1547 raw_reduce_a: u64,
1548 raw_shadow_stage_e: (u64, u64),
1549 ev_entry: CudaEvent,
1550 e_device: usize,
1551 local_q_dim: usize,
1553 local_kv_dim: usize,
1554 heads: usize,
1555 pub(crate) o_out: usize,
1556 o_block_cols: usize,
1557 blocks_per_rank: usize,
1558}
1559
1560impl TpE4m3HostBounce {
1561 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1562 Self::new_inner(devices, false, false, false, false)
1563 }
1564
1565 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1566 Self::new_inner(devices, false, true, false, false)
1567 }
1568
1569 pub fn new_native_p2p_device_arithmetic(
1570 devices: &[usize],
1571 ) -> Result<Self, Box<dyn std::error::Error>> {
1572 Self::new_inner(devices, false, true, true, false)
1573 }
1574
1575 pub(crate) fn new_configured(
1576 devices: &[usize],
1577 native_p2p: bool,
1578 ep_device_arithmetic: bool,
1579 bulk_p2p: bool,
1580 ) -> Result<Self, Box<dyn std::error::Error>> {
1581 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1582 }
1583
1584 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1589 Self::new_inner(&[device], true, false, false, false)
1590 }
1591
1592 fn new_inner(
1593 devices: &[usize],
1594 allow_single_rank: bool,
1595 native_p2p: bool,
1596 ep_device_arithmetic: bool,
1597 bulk_p2p: bool,
1598 ) -> Result<Self, Box<dyn std::error::Error>> {
1599 if ep_device_arithmetic && !native_p2p {
1600 return Err("device-resident EP arithmetic requires native P2P".into());
1601 }
1602 if bulk_p2p && !native_p2p {
1603 return Err("bulk TP transport requires native P2P".into());
1604 }
1605 let minimum = if allow_single_rank { 1 } else { 2 };
1606 if !(minimum..=8).contains(&devices.len()) {
1607 return Err(format!(
1608 "TP reference requires {minimum}..=8 devices, got {}",
1609 devices.len()
1610 )
1611 .into());
1612 }
1613 let mut unique = devices.to_vec();
1614 unique.sort_unstable();
1615 unique.dedup();
1616 if unique.len() != devices.len() {
1617 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1618 }
1619 let ranks = devices
1620 .iter()
1621 .map(|&device| Engine::new(device))
1622 .collect::<Result<Vec<_>, _>>()?;
1623 if native_p2p {
1624 configure_native_p2p(&ranks, devices)?;
1625 }
1626 if allow_single_rank {
1627 eprintln!(
1628 "[tp] canonical oracle transport=local device={} performance_claim=false",
1629 devices[0]
1630 );
1631 } else if native_p2p {
1632 if ep_device_arithmetic {
1633 eprintln!(
1634 "[tp] correctness transport=native-p2p devices={devices:?} \
1635 native_p2p=true activation=device-host-exact \
1636 accumulation=device-host-exact output=root-readback \
1637 bulk_p2p={bulk_p2p} performance_claim=false"
1638 );
1639 } else {
1640 eprintln!(
1641 "[tp] correctness transport=native-p2p devices={devices:?} \
1642 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1643 performance_claim=false"
1644 );
1645 }
1646 } else {
1647 eprintln!(
1648 "[tp] correctness transport=host-bounce devices={devices:?} \
1649 native_p2p=false performance_claim=false"
1650 );
1651 }
1652 Ok(Self {
1653 devices: devices.to_vec(),
1654 ranks,
1655 native_p2p,
1656 ep_device_arithmetic,
1657 bulk_p2p,
1658 decode_v2: std::sync::Mutex::new(Vec::new()),
1659 })
1660 }
1661
1662 pub fn devices(&self) -> &[usize] {
1663 &self.devices
1664 }
1665
1666 pub fn native_p2p(&self) -> bool {
1667 self.native_p2p
1668 }
1669
1670 pub fn bulk_p2p(&self) -> bool {
1671 self.bulk_p2p
1672 }
1673
1674 pub fn expert_activation_label(&self) -> &'static str {
1675 if self.ep_device_arithmetic {
1676 "device-host-exact"
1677 } else {
1678 "host-canonical"
1679 }
1680 }
1681
1682 pub fn expert_accumulation_label(&self) -> &'static str {
1683 self.expert_activation_label()
1684 }
1685
1686 pub fn expert_output_label(&self) -> &'static str {
1687 if self.ep_device_arithmetic {
1688 "root-readback"
1689 } else {
1690 "host-accumulated"
1691 }
1692 }
1693
1694 pub fn transport_label(&self) -> &'static str {
1695 if self.devices.len() == 1 {
1696 "local"
1697 } else if self.native_p2p {
1698 "native-p2p"
1699 } else {
1700 "host-bounce"
1701 }
1702 }
1703
1704 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1705 self.ranks
1706 .iter()
1707 .map(|rank| rank.ctx().name().map_err(Into::into))
1708 .collect()
1709 }
1710
1711 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1717 self.ranks.get(rank)
1718 }
1719
1720 pub fn allocate_tp_kv_cache(
1721 &self,
1722 kv_dim_k: usize,
1723 kv_dim_v: usize,
1724 capacity: usize,
1725 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1726 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1727 }
1728
1729 pub fn allocate_tp_swa_kv_cache(
1730 &self,
1731 kv_dim_k: usize,
1732 kv_dim_v: usize,
1733 capacity: usize,
1734 window: usize,
1735 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1736 if window == 0 {
1737 return Err("TP SWA KV window must be nonzero".into());
1738 }
1739 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1740 }
1741
1742 fn allocate_tp_kv_cache_inner(
1743 &self,
1744 kv_dim_k: usize,
1745 kv_dim_v: usize,
1746 capacity: usize,
1747 window: Option<usize>,
1748 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1749 if capacity == 0 || capacity > i32::MAX as usize {
1750 return Err(
1751 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1752 );
1753 }
1754 let tp = self.ranks.len();
1755 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1756 let physical_rows = window
1757 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1758 .unwrap_or(capacity);
1759 let k_plane_bytes = physical_rows
1760 .checked_mul(shape.k_token_bytes)
1761 .and_then(|bytes| bytes.checked_add(8))
1762 .ok_or("TP KV K plane-byte overflow")?;
1763 let v_plane_bytes = physical_rows
1764 .checked_mul(shape.v_token_bytes)
1765 .and_then(|bytes| bytes.checked_add(8))
1766 .ok_or("TP KV V plane-byte overflow")?;
1767 let mut ranks = Vec::with_capacity(tp);
1768 for engine in &self.ranks {
1769 let _main = engine.gpu.enter_main()?;
1770 ranks.push(ResidentTpKvCacheRank::new(
1771 engine.alloc_u8(k_plane_bytes)?,
1772 engine.alloc_u8(v_plane_bytes)?,
1773 engine.htod_i32(&[0])?,
1774 ));
1775 }
1776 Ok(match window {
1777 Some(window) => ResidentTpKvCache::new_swa(
1778 ranks,
1779 shape.kv_dim_k,
1780 shape.kv_dim_v,
1781 shape.k_token_bytes,
1782 shape.v_token_bytes,
1783 capacity,
1784 window,
1785 ),
1786 None => ResidentTpKvCache::new(
1787 ranks,
1788 shape.kv_dim_k,
1789 shape.kv_dim_v,
1790 shape.k_token_bytes,
1791 shape.v_token_bytes,
1792 capacity,
1793 ),
1794 })
1795 }
1796
1797 pub fn grow_tp_kv_cache(
1798 &self,
1799 source: &ResidentTpKvCache,
1800 target_capacity: usize,
1801 rows: usize,
1802 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1803 self.validate_tp_kv_cache(source)?;
1804 let plan = source.prepare_grow(target_capacity, rows)?;
1805 let ranks = self.ranks.len();
1806 let global_k = source
1807 .kv_dim_k()
1808 .checked_mul(ranks)
1809 .ok_or("TP KV grow global K dimension overflow")?;
1810 let global_v = source
1811 .kv_dim_v()
1812 .checked_mul(ranks)
1813 .ok_or("TP KV grow global V dimension overflow")?;
1814 let mut target = match source.ring_window() {
1815 Some(window) => {
1816 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1817 }
1818 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1819 };
1820 self.validate_tp_kv_cache(&target)?;
1821
1822 for (rank, engine) in self.ranks.iter().enumerate() {
1823 let _main = engine.gpu.enter_main()?;
1824 let src = source
1825 .rank(rank)
1826 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1827 let dst = target
1828 .rank_mut(rank)
1829 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1830 if plan.k_bytes() > 0 {
1831 engine.copy_u8_range_into(
1832 dst.k_mut(),
1833 0,
1834 src.k(),
1835 plan.source_row() * source.k_tok_bytes(),
1836 plan.k_bytes(),
1837 )?;
1838 }
1839 if plan.v_bytes() > 0 {
1840 engine.copy_u8_range_into(
1841 dst.v_mut(),
1842 0,
1843 src.v(),
1844 plan.source_row() * source.v_tok_bytes(),
1845 plan.v_bytes(),
1846 )?;
1847 }
1848 }
1849 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1850
1851 for engine in &self.ranks {
1854 let _main = engine.gpu.enter_main()?;
1855 engine.stream().synchronize()?;
1856 }
1857 let physical_copy_rows = plan.copy_rows();
1858 target.publish_grow(plan)?;
1859 eprintln!(
1860 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1861 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1862 rank_streams_synchronized=true generation_preserved=true",
1863 rows,
1864 source.capacity(),
1865 target_capacity,
1866 ranks,
1867 physical_copy_rows,
1868 source.ring_window(),
1869 );
1870 Ok(target)
1871 }
1872
1873 pub fn hydrate_tp_kv_cache(
1874 &self,
1875 cache: &mut ResidentTpKvCache,
1876 rows: usize,
1877 k_rows: &[u8],
1878 v_rows: &[u8],
1879 ) -> Result<(), Box<dyn std::error::Error>> {
1880 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1881 }
1882
1883 pub fn hydrate_tp_kv_cache_from(
1884 &self,
1885 cache: &mut ResidentTpKvCache,
1886 logical_len: usize,
1887 resident_start: usize,
1888 k_rows: &[u8],
1889 v_rows: &[u8],
1890 ) -> Result<(), Box<dyn std::error::Error>> {
1891 self.validate_tp_kv_cache(cache)?;
1892 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1893 return Err(format!(
1894 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1895 cache.committed_len(),
1896 cache.staged_len()
1897 )
1898 .into());
1899 }
1900 if resident_start > logical_len || logical_len > cache.capacity() {
1901 return Err(format!(
1902 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1903 cache.capacity(),
1904 )
1905 .into());
1906 }
1907 let rows = logical_len - resident_start;
1908 if rows > cache.physical_capacity() {
1909 return Err(format!(
1910 "TP KV hydration rows {rows} exceed physical capacity {}",
1911 cache.physical_capacity()
1912 )
1913 .into());
1914 }
1915 for rank in 0..self.ranks.len() {
1916 let k_rank =
1917 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1918 let v_rank =
1919 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1920 let engine = &self.ranks[rank];
1921 let _main = engine.gpu.enter_main()?;
1922 let rank_cache = cache
1923 .rank_mut(rank)
1924 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1925 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1926 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1927 }
1928 cache.publish_hydration(logical_len, resident_start)?;
1929 Ok(())
1930 }
1931
1932 pub fn append_tp_kv_transaction(
1933 &self,
1934 cache: &mut ResidentTpKvCache,
1935 transaction: TpKvTransaction,
1936 k_shards: &[CudaSlice<f32>],
1937 v_shards: &[CudaSlice<f32>],
1938 rows: usize,
1939 ) -> Result<(), Box<dyn std::error::Error>> {
1940 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1941 }
1942
1943 #[allow(clippy::too_many_arguments)]
1948 pub fn append_tp_kv_transaction_inner(
1949 &self,
1950 cache: &mut ResidentTpKvCache,
1951 transaction: TpKvTransaction,
1952 k_shards: &[CudaSlice<f32>],
1953 v_shards: &[CudaSlice<f32>],
1954 rows: usize,
1955 external_rank_appends: bool,
1956 ) -> Result<(), Box<dyn std::error::Error>> {
1957 self.validate_tp_kv_cache(cache)?;
1958 let plan = cache.prepare_append(transaction, rows)?;
1959 let target = plan.target();
1960 let expected_k = rows
1961 .checked_mul(cache.kv_dim_k())
1962 .ok_or("TP KV K append size overflow")?;
1963 let expected_v = rows
1964 .checked_mul(cache.kv_dim_v())
1965 .ok_or("TP KV V append size overflow")?;
1966 if !external_rank_appends
1969 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1970 {
1971 return Err(format!(
1972 "TP KV append shard counts k={} v={} != ranks {}",
1973 k_shards.len(),
1974 v_shards.len(),
1975 self.ranks.len()
1976 )
1977 .into());
1978 }
1979 let kv_dim_k = cache.kv_dim_k();
1980 let kv_dim_v = cache.kv_dim_v();
1981 let k_tok_bytes = cache.k_tok_bytes();
1982 let v_tok_bytes = cache.v_tok_bytes();
1983 if let Some(KvRingAppend::Rebase {
1984 src_row,
1985 keep_rows,
1986 new_base,
1987 ..
1988 }) = plan.ring_append()
1989 {
1990 for rank in 0..self.ranks.len() {
1991 let engine = &self.ranks[rank];
1992 let _main = engine.gpu.enter_main()?;
1993 let rank_cache = cache
1994 .rank_mut(rank)
1995 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1996 if keep_rows > 0 {
1997 let k_len = keep_rows
1998 .checked_mul(k_tok_bytes)
1999 .ok_or("TP KV K rebase-byte overflow")?;
2000 let v_len = keep_rows
2001 .checked_mul(v_tok_bytes)
2002 .ok_or("TP KV V rebase-byte overflow")?;
2003 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
2004 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2005 engine.copy_u8_range_into(
2006 &mut k_tmp,
2007 0,
2008 rank_cache.k(),
2009 src_row * k_tok_bytes,
2010 k_len,
2011 )?;
2012 engine.copy_u8_range_into(
2013 &mut v_tmp,
2014 0,
2015 rank_cache.v(),
2016 src_row * v_tok_bytes,
2017 v_len,
2018 )?;
2019 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2020 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2021 }
2022 if rank_cache.base_d().is_some() {
2026 let value = new_base as i32;
2027 let rank_cache = cache
2028 .rank_mut(rank)
2029 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2030 if let Some(base_d) = rank_cache.base_d_mut() {
2031 engine.set_i32_one(base_d, value)?;
2032 }
2033 }
2034 }
2035 }
2036 cache.publish_append_rebase(plan)?;
2037 let write_row = plan.write_row();
2038 for rank in 0..self.ranks.len() {
2039 if external_rank_appends {
2040 break;
2041 }
2042 let engine = &self.ranks[rank];
2043 let _main = engine.gpu.enter_main()?;
2044 if k_shards[rank].len() != expected_k
2045 || v_shards[rank].len() != expected_v
2046 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2047 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2048 {
2049 return Err(format!(
2050 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2051 != expected {expected_k}/{expected_v} on device {}",
2052 k_shards[rank].len(),
2053 k_shards[rank].ordinal(),
2054 v_shards[rank].len(),
2055 v_shards[rank].ordinal(),
2056 engine.ctx().ordinal(),
2057 )
2058 .into());
2059 }
2060 let rank_cache = cache
2061 .rank_mut(rank)
2062 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2063 let (rank_k, rank_v) = rank_cache.planes_mut();
2064 engine.append_kv_quantized_rows(
2065 &k_shards[rank],
2066 &v_shards[rank],
2067 rank_k,
2068 rank_v,
2069 write_row,
2070 rows,
2071 kv_dim_k,
2072 kv_dim_v,
2073 k_tok_bytes,
2074 v_tok_bytes,
2075 Engine::kv_fp8_on(),
2076 )?;
2077 }
2078 if !external_rank_appends {
2079 self.set_tp_kv_len_mirrors(cache, target)?;
2082 }
2083 cache.publish_append_plan(plan)?;
2084 Ok(())
2085 }
2086
2087 pub fn commit_tp_kv_transaction(
2088 &self,
2089 cache: &mut ResidentTpKvCache,
2090 transaction: TpKvTransaction,
2091 accepted_rows: usize,
2092 ) -> Result<(), Box<dyn std::error::Error>> {
2093 self.validate_tp_kv_cache(cache)?;
2094 let target = cache.commit_target(transaction, accepted_rows)?;
2095 self.set_tp_kv_len_mirrors(cache, target)?;
2096 cache.publish_finalize(transaction, target)?;
2097 Ok(())
2098 }
2099
2100 pub fn commit_tp_kv_transaction_external(
2106 &self,
2107 cache: &mut ResidentTpKvCache,
2108 transaction: TpKvTransaction,
2109 accepted_rows: usize,
2110 ) -> Result<(), Box<dyn std::error::Error>> {
2111 self.validate_tp_kv_cache(cache)?;
2112 let target = cache.commit_target(transaction, accepted_rows)?;
2113 cache.publish_finalize(transaction, target)?;
2114 Ok(())
2115 }
2116
2117 pub fn rollback_tp_kv_transaction(
2118 &self,
2119 cache: &mut ResidentTpKvCache,
2120 transaction: TpKvTransaction,
2121 ) -> Result<(), Box<dyn std::error::Error>> {
2122 self.validate_tp_kv_cache(cache)?;
2123 cache.validate_transaction(transaction)?;
2124 let target = transaction.base_len();
2125 self.set_tp_kv_len_mirrors(cache, target)?;
2126 cache.publish_finalize(transaction, target)?;
2127 Ok(())
2128 }
2129
2130 pub fn tp_kv_device_lengths(
2131 &self,
2132 cache: &ResidentTpKvCache,
2133 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2134 self.validate_tp_kv_cache(cache)?;
2135 let mut lengths = Vec::with_capacity(self.ranks.len());
2136 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2137 let _main = engine.gpu.enter_main()?;
2138 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2139 }
2140 Ok(lengths)
2141 }
2142
2143 fn set_tp_kv_len_mirrors(
2144 &self,
2145 cache: &mut ResidentTpKvCache,
2146 len: usize,
2147 ) -> Result<(), Box<dyn std::error::Error>> {
2148 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2149 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2150 let _main = engine.gpu.enter_main()?;
2151 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2152 }
2153 Ok(())
2154 }
2155
2156 fn validate_tp_kv_cache(
2157 &self,
2158 cache: &ResidentTpKvCache,
2159 ) -> Result<(), Box<dyn std::error::Error>> {
2160 if cache.ranks_len() != self.ranks.len() {
2161 return Err(format!(
2162 "TP KV cache ranks {} != runtime ranks {}",
2163 cache.ranks_len(),
2164 self.ranks.len()
2165 )
2166 .into());
2167 }
2168 let expected_k = cache
2169 .physical_capacity()
2170 .checked_mul(cache.k_tok_bytes())
2171 .and_then(|bytes| bytes.checked_add(8))
2172 .ok_or("TP KV K plane validation overflow")?;
2173 let expected_v = cache
2174 .physical_capacity()
2175 .checked_mul(cache.v_tok_bytes())
2176 .and_then(|bytes| bytes.checked_add(8))
2177 .ok_or("TP KV V plane validation overflow")?;
2178 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2179 let device = engine.ctx().ordinal();
2180 if rank_cache.k().len() != expected_k
2181 || rank_cache.v().len() != expected_v
2182 || rank_cache.len_d().len() != 1
2183 || rank_cache.k().ordinal() != device
2184 || rank_cache.v().ordinal() != device
2185 || rank_cache.len_d().ordinal() != device
2186 {
2187 return Err(format!(
2188 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2189 )
2190 .into());
2191 }
2192 }
2193 Ok(())
2194 }
2195
2196 pub fn full(
2197 &self,
2198 matrix: E4m3BlockMatrix<'_>,
2199 activations: &[f32],
2200 tokens: usize,
2201 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2202 matrix.validate()?;
2203 validate_activations(activations, tokens, matrix.in_features)?;
2204 run_rank(&self.ranks[0], matrix, activations, tokens)
2205 }
2206
2207 pub fn column_parallel(
2211 &self,
2212 matrix: E4m3BlockMatrix<'_>,
2213 activations: &[f32],
2214 tokens: usize,
2215 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2216 matrix.validate()?;
2217 validate_activations(activations, tokens, matrix.in_features)?;
2218 let tp = self.ranks.len();
2219 if matrix.out_features % tp != 0 {
2220 return Err(format!(
2221 "column-parallel out_features {} is not divisible by TP={tp}",
2222 matrix.out_features
2223 )
2224 .into());
2225 }
2226 let local_out = matrix.out_features / tp;
2227 if local_out % FP8_BLOCK != 0 {
2228 return Err(format!(
2229 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2230 E4M3 scale block"
2231 )
2232 .into());
2233 }
2234
2235 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2236 let mut rank_outputs = Vec::with_capacity(tp);
2237 for (rank_index, rank) in self.ranks.iter().enumerate() {
2238 let shard = column_shard(matrix, tp, rank_index)?;
2239 let output = run_rank(rank, shard, activations, tokens)?;
2240 let row_start = rank_index * local_out;
2241 for token in 0..tokens {
2242 gathered[token * matrix.out_features + row_start
2243 ..token * matrix.out_features + row_start + local_out]
2244 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2245 }
2246 rank_outputs.push(output);
2247 }
2248 Ok(ColumnParallelResult {
2249 gathered,
2250 rank_outputs,
2251 })
2252 }
2253
2254 pub fn upload_column_parallel(
2255 &self,
2256 matrix: E4m3BlockMatrix<'_>,
2257 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2258 matrix.validate()?;
2259 let tp = self.ranks.len();
2260 validate_column_shape(matrix, tp)?;
2261 let mut ranks = Vec::with_capacity(tp);
2262 for (rank_index, engine) in self.ranks.iter().enumerate() {
2263 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2264 }
2265 Ok(ResidentColumnParallel {
2266 ranks,
2267 out_features: matrix.out_features,
2268 in_features: matrix.in_features,
2269 })
2270 }
2271
2272 pub fn column_parallel_resident(
2273 &self,
2274 matrix: &ResidentColumnParallel,
2275 activations: &[f32],
2276 tokens: usize,
2277 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2278 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2279 validate_activations(activations, tokens, matrix.in_features)?;
2280 let local_out = matrix.out_features / self.ranks.len();
2281 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2282 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2283 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2284 let output = run_resident_rank(engine, shard, activations, tokens)?;
2285 let row_start = rank_index * local_out;
2286 for token in 0..tokens {
2287 gathered[token * matrix.out_features + row_start
2288 ..token * matrix.out_features + row_start + local_out]
2289 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2290 }
2291 rank_outputs.push(output);
2292 }
2293 Ok(ColumnParallelResult {
2294 gathered,
2295 rank_outputs,
2296 })
2297 }
2298
2299 pub fn row_parallel(
2303 &self,
2304 matrix: E4m3BlockMatrix<'_>,
2305 activations: &[f32],
2306 tokens: usize,
2307 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2308 matrix.validate()?;
2309 validate_activations(activations, tokens, matrix.in_features)?;
2310 let tp = self.ranks.len();
2311 if matrix.in_features % tp != 0 {
2312 return Err(format!(
2313 "row-parallel in_features {} is not divisible by TP={tp}",
2314 matrix.in_features
2315 )
2316 .into());
2317 }
2318 let local_in = matrix.in_features / tp;
2319 if local_in % FP8_BLOCK != 0 {
2320 return Err(format!(
2321 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2322 E4M3 scale block"
2323 )
2324 .into());
2325 }
2326
2327 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2328 let mut rank_partials = Vec::with_capacity(tp);
2329 for (rank_index, rank) in self.ranks.iter().enumerate() {
2330 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2331 let local_activations =
2332 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2333 let shard = E4m3BlockMatrix {
2334 codes: &codes,
2335 scales: &scales,
2336 out_features: matrix.out_features,
2337 in_features: local_in,
2338 };
2339 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2340 for (sum, value) in reduced.iter_mut().zip(&partial) {
2341 *sum += *value;
2342 }
2343 rank_partials.push(partial);
2344 }
2345 Ok(RowParallelResult {
2346 reduced,
2347 rank_partials,
2348 })
2349 }
2350
2351 pub fn upload_row_parallel(
2352 &self,
2353 matrix: E4m3BlockMatrix<'_>,
2354 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2355 matrix.validate()?;
2356 let tp = self.ranks.len();
2357 validate_row_shape(matrix, tp)?;
2358 let local_in = matrix.in_features / tp;
2359 let mut ranks = Vec::with_capacity(tp);
2360 for (rank_index, engine) in self.ranks.iter().enumerate() {
2361 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2362 ranks.push(upload_rank(
2363 engine,
2364 E4m3BlockMatrix {
2365 codes: &codes,
2366 scales: &scales,
2367 out_features: matrix.out_features,
2368 in_features: local_in,
2369 },
2370 )?);
2371 }
2372 Ok(ResidentRowParallel {
2373 ranks,
2374 out_features: matrix.out_features,
2375 in_features: matrix.in_features,
2376 })
2377 }
2378
2379 pub fn row_parallel_resident(
2380 &self,
2381 matrix: &ResidentRowParallel,
2382 activations: &[f32],
2383 tokens: usize,
2384 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2385 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2386 validate_activations(activations, tokens, matrix.in_features)?;
2387 let tp = self.ranks.len();
2388 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2389 let mut rank_partials = Vec::with_capacity(tp);
2390 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2391 let local_activations =
2392 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2393 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2394 for (sum, value) in reduced.iter_mut().zip(&partial) {
2395 *sum += *value;
2396 }
2397 rank_partials.push(partial);
2398 }
2399 Ok(RowParallelResult {
2400 reduced,
2401 rank_partials,
2402 })
2403 }
2404
2405 pub fn upload_bf16_column_parallel(
2406 &self,
2407 matrix: Bf16Matrix<'_>,
2408 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2409 self.upload_bf16_column_parallel_inner(matrix, None, false)
2410 }
2411
2412 pub fn upload_step_bf16_column_parallel(
2414 &self,
2415 matrix: Bf16Matrix<'_>,
2416 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2417 self.upload_step_bf16_column_parallel_inner(matrix, false)
2418 }
2419
2420 pub fn upload_step_bf16_column_parallel_f32_mirror(
2425 &self,
2426 matrix: Bf16Matrix<'_>,
2427 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2428 self.upload_step_bf16_column_parallel_inner(matrix, true)
2429 }
2430
2431 fn upload_step_bf16_column_parallel_inner(
2432 &self,
2433 matrix: Bf16Matrix<'_>,
2434 f32_mirror: bool,
2435 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2436 let canonical_chunk_rows =
2437 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2438 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2439 }
2440
2441 fn upload_bf16_column_parallel_inner(
2442 &self,
2443 matrix: Bf16Matrix<'_>,
2444 canonical_chunk_rows: Option<usize>,
2445 f32_mirror: bool,
2446 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2447 matrix.validate()?;
2448 let tp = self.ranks.len();
2449 if matrix.out_features % tp != 0 {
2450 return Err(format!(
2451 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2452 matrix.out_features
2453 )
2454 .into());
2455 }
2456 let mut ranks = Vec::with_capacity(tp);
2457 for (rank, engine) in self.ranks.iter().enumerate() {
2458 ranks.push(upload_bf16_rank(
2459 engine,
2460 bf16_column_shard(matrix, tp, rank)?,
2461 f32_mirror,
2462 )?);
2463 }
2464 Ok(ResidentBf16ColumnParallel {
2465 ranks,
2466 out_features: matrix.out_features,
2467 in_features: matrix.in_features,
2468 canonical_chunk_rows,
2469 })
2470 }
2471
2472 pub fn bf16_column_parallel_resident(
2473 &self,
2474 matrix: &ResidentBf16ColumnParallel,
2475 activations: &[f32],
2476 tokens: usize,
2477 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2478 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2479 validate_activations(activations, tokens, matrix.in_features)?;
2480 let local_out = matrix.out_features / self.ranks.len();
2481 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2482 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2483 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2484 let output = run_resident_bf16_rank(
2485 engine,
2486 shard,
2487 activations,
2488 tokens,
2489 matrix.canonical_chunk_rows,
2490 )?;
2491 for token in 0..tokens {
2492 let src = &output[token * local_out..(token + 1) * local_out];
2493 let dst_start = token * matrix.out_features + rank * local_out;
2494 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2495 }
2496 rank_outputs.push(output);
2497 }
2498 Ok(ColumnParallelResult {
2499 gathered,
2500 rank_outputs,
2501 })
2502 }
2503
2504 pub fn bf16_column_parallel_resident_native(
2511 &self,
2512 matrix: &ResidentBf16ColumnParallel,
2513 activations: &[f32],
2514 tokens: usize,
2515 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2516 let rank_outputs =
2517 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2518 let local_out = matrix.out_features / self.ranks.len();
2519 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2520 }
2521
2522 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2527 self.ranks
2528 .first()
2529 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2530 }
2531
2532 pub fn bf16_column_parallel_resident_native_device(
2545 &self,
2546 matrix: &ResidentBf16ColumnParallel,
2547 root_activation: &CudaSlice<f32>,
2548 tokens: usize,
2549 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2550 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2551 matrix,
2552 root_activation,
2553 tokens,
2554 )?;
2555 let local_out = matrix.out_features / self.ranks.len();
2556 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2557 let root = &self.ranks[0];
2558 let _main = root.gpu.enter_main()?;
2559 root.stream().synchronize()?;
2560 Ok(gathered)
2561 }
2562
2563 pub fn bf16_column_parallel_resident_device_shards_from_root(
2568 &self,
2569 matrix: &ResidentBf16ColumnParallel,
2570 root_activation: &CudaSlice<f32>,
2571 tokens: usize,
2572 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2573 if self.ranks.len() > 1 && !self.native_p2p {
2574 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2575 }
2576 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2577 let values = tokens
2578 .checked_mul(matrix.in_features)
2579 .ok_or("device BF16 column activation size overflow")?;
2580 let root = &self.ranks[0];
2581 if tokens == 0
2582 || root_activation.len() < values
2583 || root_activation.ordinal() != root.ctx().ordinal()
2584 {
2585 return Err("device BF16 column root activation geometry mismatch".into());
2586 }
2587
2588 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2589 let root_input = {
2590 let _main = root.gpu.enter_main()?;
2591 let mut root_input = root.uninit(values)?;
2592 root.stream()
2593 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2594 root_input
2595 };
2596 {
2600 let _main = root.gpu.enter_main()?;
2601 root.stream().synchronize()?;
2602 }
2603 rank_inputs.push(root_input);
2604 for engine in &self.ranks[1..] {
2605 let peer_input = {
2606 let _main = engine.gpu.enter_main()?;
2607 let mut peer_input = engine.uninit(values)?;
2608 engine
2609 .stream()
2610 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2611 peer_input
2612 };
2613 rank_inputs.push(peer_input);
2614 }
2615
2616 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2617 for rank in 0..self.ranks.len() {
2618 rank_outputs.push(run_resident_bf16_rank_device(
2619 &self.ranks[rank],
2620 &matrix.ranks[rank],
2621 &rank_inputs[rank],
2622 tokens,
2623 matrix.canonical_chunk_rows,
2624 self.bulk_p2p,
2625 )?);
2626 }
2627 Ok(rank_outputs)
2628 }
2629
2630 pub fn bf16_column_parallel_resident_device_shards(
2637 &self,
2638 matrix: &ResidentBf16ColumnParallel,
2639 activations: &[f32],
2640 tokens: usize,
2641 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2642 if self.ranks.len() > 1 && !self.native_p2p {
2643 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2644 }
2645 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2646 validate_activations(activations, tokens, matrix.in_features)?;
2647
2648 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2649 let root_input = {
2650 let root = &self.ranks[0];
2651 let _main = root.gpu.enter_main()?;
2652 root.htod(activations)?
2653 };
2654 {
2660 let root = &self.ranks[0];
2661 let _main = root.gpu.enter_main()?;
2662 root.stream().synchronize()?;
2663 }
2664 rank_inputs.push(root_input);
2665 for engine in &self.ranks[1..] {
2666 let peer_input = {
2667 let _main = engine.gpu.enter_main()?;
2668 let mut peer_input = engine.uninit(activations.len())?;
2669 engine
2670 .stream()
2671 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2672 peer_input
2673 };
2674 rank_inputs.push(peer_input);
2675 }
2676
2677 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2678 for rank in 0..self.ranks.len() {
2679 rank_outputs.push(run_resident_bf16_rank_device(
2680 &self.ranks[rank],
2681 &matrix.ranks[rank],
2682 &rank_inputs[rank],
2683 tokens,
2684 matrix.canonical_chunk_rows,
2685 self.bulk_p2p,
2686 )?);
2687 }
2688 Ok(rank_outputs)
2689 }
2690
2691 pub fn allocate_replicated_device_rows(
2695 &self,
2696 tokens: usize,
2697 width: usize,
2698 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2699 if self.ranks.len() > 1 && !self.native_p2p {
2700 return Err("replicated device rows require native P2P ranks".into());
2701 }
2702 let values = tokens
2703 .checked_mul(width)
2704 .ok_or("replicated device row size overflow")?;
2705 let rank_lengths = vec![values; self.ranks.len()];
2706 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2707 let mut ranks = Vec::with_capacity(self.ranks.len());
2708 for engine in &self.ranks {
2709 let _main = engine.gpu.enter_main()?;
2710 ranks.push(engine.uninit(values)?);
2711 }
2712 Ok(ResidentReplicatedDeviceRows {
2713 ranks,
2714 tokens,
2715 width,
2716 })
2717 }
2718
2719 pub fn refresh_replicated_device_rows_from_root(
2721 &self,
2722 rows: &mut ResidentReplicatedDeviceRows,
2723 source: &CudaSlice<f32>,
2724 ) -> Result<(), Box<dyn std::error::Error>> {
2725 if self.ranks.len() > 1 && !self.native_p2p {
2726 return Err("replicated device rows require native P2P ranks".into());
2727 }
2728 validate_replicated_device_rows(&self.ranks, rows)?;
2729 let root = self
2730 .ranks
2731 .first()
2732 .ok_or("replicated rows have no root rank")?;
2733 let values = replicated_device_row_source_values(
2734 rows.tokens,
2735 rows.width,
2736 source.len(),
2737 source.ordinal(),
2738 root.ctx().ordinal(),
2739 )?;
2740 let (root_rows, peer_rows) = rows
2741 .ranks
2742 .split_first_mut()
2743 .ok_or("replicated rows have no root allocation")?;
2744 {
2745 let _main = root.gpu.enter_main()?;
2746 let mut destination = root_rows.slice_mut(0..values);
2747 root.stream()
2748 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2749 root.stream().synchronize()?;
2750 }
2751 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2752 let _main = engine.gpu.enter_main()?;
2753 let mut destination = peer_rows.slice_mut(0..values);
2754 engine
2755 .stream()
2756 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2757 }
2758 Ok(())
2759 }
2760
2761 pub fn upload_replicated_device_rows(
2763 &self,
2764 rows: &[f32],
2765 tokens: usize,
2766 width: usize,
2767 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2768 if self.ranks.len() > 1 && !self.native_p2p {
2769 return Err("replicated device rows require native P2P ranks".into());
2770 }
2771 validate_activations(rows, tokens, width)?;
2772 let root = self
2773 .ranks
2774 .first()
2775 .ok_or("replicated rows have no root rank")?;
2776 let root_rows = {
2777 let _main = root.gpu.enter_main()?;
2778 root.htod(rows)?
2779 };
2780 {
2781 let _main = root.gpu.enter_main()?;
2782 root.stream().synchronize()?;
2783 }
2784 let mut ranks = Vec::with_capacity(self.ranks.len());
2785 ranks.push(root_rows);
2786 for engine in self.ranks.iter().skip(1) {
2787 let _main = engine.gpu.enter_main()?;
2788 let mut peer_rows = engine.uninit(rows.len())?;
2789 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2790 ranks.push(peer_rows);
2791 }
2792 Ok(ResidentReplicatedDeviceRows {
2793 ranks,
2794 tokens,
2795 width,
2796 })
2797 }
2798
2799 pub fn bf16_column_parallel_resident_replicated_device_shards(
2801 &self,
2802 matrix: &ResidentBf16ColumnParallel,
2803 activations: &ResidentReplicatedDeviceRows,
2804 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2805 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2806 validate_replicated_device_rows(&self.ranks, activations)?;
2807 if activations.width != matrix.in_features {
2808 return Err(format!(
2809 "replicated BF16 column input width {} != matrix width {}",
2810 activations.width, matrix.in_features
2811 )
2812 .into());
2813 }
2814 let mut outputs = Vec::with_capacity(self.ranks.len());
2815 for rank in 0..self.ranks.len() {
2816 outputs.push(run_resident_bf16_rank_device(
2817 &self.ranks[rank],
2818 &matrix.ranks[rank],
2819 &activations.ranks[rank],
2820 activations.tokens,
2821 matrix.canonical_chunk_rows,
2822 self.bulk_p2p,
2823 )?);
2824 }
2825 Ok(outputs)
2826 }
2827
2828 #[allow(clippy::too_many_arguments)]
2830 pub fn upload_sigmoid_topk_router(
2831 &self,
2832 weight: Bf16Matrix<'_>,
2833 correction_bias: &[f32],
2834 active: Option<&[bool]>,
2835 experts_per_token: usize,
2836 scaling_factor: f32,
2837 route_norm: bool,
2838 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2839 weight.validate()?;
2840 if correction_bias.len() != weight.out_features
2841 || experts_per_token == 0
2842 || experts_per_token > weight.out_features
2843 || !correction_bias.iter().all(|value| value.is_finite())
2844 || !scaling_factor.is_finite()
2845 || scaling_factor <= 0.0
2846 {
2847 return Err(format!(
2848 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2849 weight.out_features,
2850 weight.in_features,
2851 correction_bias.len(),
2852 experts_per_token,
2853 )
2854 .into());
2855 }
2856 let active_row = active
2857 .map(|mask| {
2858 if mask.len() != weight.out_features {
2859 return Err(format!(
2860 "sigmoid router active mask {} != experts {}",
2861 mask.len(),
2862 weight.out_features
2863 ));
2864 }
2865 Ok(mask
2866 .iter()
2867 .map(|&enabled| u8::from(enabled))
2868 .collect::<Vec<_>>())
2869 })
2870 .transpose()?
2871 .unwrap_or_else(|| vec![1; weight.out_features]);
2872 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2873 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2874
2875 let root = self
2876 .ranks
2877 .first()
2878 .ok_or("sigmoid router runtime has no root rank")?;
2879 let _main = root.gpu.enter_main()?;
2880 let bf16 = root.htod_bytes(weight.bytes)?;
2881 let weight_f32 = root.bf16_to_f32(
2882 &bf16.slice(0..bf16.len()),
2883 weight.out_features * weight.in_features,
2884 )?;
2885 Ok(ResidentSigmoidTopKRouter {
2886 weight: weight_f32,
2887 correction_bias: root.htod(correction_bias)?,
2888 active: root.htod_bytes(&active_row)?,
2889 root_device: root.ctx().ordinal(),
2890 input_width: weight.in_features,
2891 expert_count: weight.out_features,
2892 experts_per_token,
2893 active_count,
2894 scaling_factor,
2895 route_norm,
2896 })
2897 }
2898
2899 pub fn sigmoid_topk_replicated_device_rows_host(
2904 &self,
2905 router: &ResidentSigmoidTopKRouter,
2906 input: &ResidentReplicatedDeviceRows,
2907 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2908 validate_replicated_device_rows(&self.ranks, input)?;
2909 if input.width != router.input_width {
2910 return Err(format!(
2911 "sigmoid router input width {} != resident width {}",
2912 input.width, router.input_width
2913 )
2914 .into());
2915 }
2916 let root = self
2917 .ranks
2918 .first()
2919 .ok_or("sigmoid router runtime has no root rank")?;
2920 let _main = root.gpu.enter_main()?;
2921 if root.ctx().ordinal() != router.root_device
2922 || router.weight.ordinal() != router.root_device
2923 || router.correction_bias.ordinal() != router.root_device
2924 || router.active.ordinal() != router.root_device
2925 {
2926 return Err("sigmoid router root residency changed".into());
2927 }
2928 let logits = root.router_gemv(
2929 &router.weight,
2930 &input.ranks[0],
2931 router.input_width,
2932 router.expert_count,
2933 input.tokens,
2934 )?;
2935 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2936 &logits,
2937 input.tokens,
2938 router.expert_count,
2939 router.experts_per_token,
2940 router.active_count,
2941 &router.correction_bias,
2942 &router.active,
2943 router.scaling_factor,
2944 router.route_norm,
2945 )?;
2946 Ok(SigmoidTopKHostOutput {
2947 logits: root.dtoh(&logits)?,
2948 selected,
2949 weights,
2950 })
2951 }
2952
2953 pub fn upload_replicated_bf16_swiglu(
2955 &self,
2956 gate: Bf16Matrix<'_>,
2957 up: Bf16Matrix<'_>,
2958 down: Bf16Matrix<'_>,
2959 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2960 gate.validate()?;
2961 up.validate()?;
2962 down.validate()?;
2963 if gate.in_features != up.in_features
2964 || gate.out_features != up.out_features
2965 || down.in_features != gate.out_features
2966 || down.out_features != gate.in_features
2967 {
2968 return Err(format!(
2969 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2970 gate.out_features,
2971 gate.in_features,
2972 up.out_features,
2973 up.in_features,
2974 down.out_features,
2975 down.in_features,
2976 )
2977 .into());
2978 }
2979 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2980 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2981 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2982 for engine in &self.ranks {
2983 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2984 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2985 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2986 }
2987 Ok(ResidentReplicatedBf16SwiGlu {
2988 gate: gate_ranks,
2989 up: up_ranks,
2990 down: down_ranks,
2991 input_width: gate.in_features,
2992 intermediate_width: gate.out_features,
2993 })
2994 }
2995
2996 pub fn replicated_bf16_swiglu_resident_device(
2998 &self,
2999 mlp: &ResidentReplicatedBf16SwiGlu,
3000 input: &ResidentReplicatedDeviceRows,
3001 activation_limit: Option<f32>,
3002 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3003 validate_step_expert_activation_limit(activation_limit)?;
3004 validate_replicated_device_rows(&self.ranks, input)?;
3005 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3006 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3007 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3008 if input.width != mlp.input_width
3009 || mlp.gate.len() != self.ranks.len()
3010 || mlp.up.len() != self.ranks.len()
3011 || mlp.down.len() != self.ranks.len()
3012 {
3013 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3014 }
3015
3016 let mut outputs = Vec::with_capacity(self.ranks.len());
3017 for rank in 0..self.ranks.len() {
3018 let engine = &self.ranks[rank];
3019 let gate = run_resident_bf16_rank_device(
3020 engine,
3021 &mlp.gate[rank],
3022 &input.ranks[rank],
3023 input.tokens,
3024 None,
3025 self.bulk_p2p,
3026 )?;
3027 let up = run_resident_bf16_rank_device(
3028 engine,
3029 &mlp.up[rank],
3030 &input.ranks[rank],
3031 input.tokens,
3032 None,
3033 self.bulk_p2p,
3034 )?;
3035 let _main = engine.gpu.enter_main()?;
3036 let values = input
3037 .tokens
3038 .checked_mul(mlp.intermediate_width)
3039 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3040 let mut activation = engine.uninit(values)?;
3041 if let Some(limit) = activation_limit {
3042 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3043 } else {
3044 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3045 }
3046 outputs.push(run_resident_bf16_rank_device(
3047 engine,
3048 &mlp.down[rank],
3049 &activation,
3050 input.tokens,
3051 None,
3052 self.bulk_p2p,
3053 )?);
3054 }
3055 Ok(ResidentReplicatedDeviceRows {
3056 ranks: outputs,
3057 tokens: input.tokens,
3058 width: mlp.input_width,
3059 })
3060 }
3061
3062 pub fn rms_norm_replicated_device_rows(
3064 &self,
3065 input: &ResidentReplicatedDeviceRows,
3066 weight: &[f32],
3067 eps: f32,
3068 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3069 validate_replicated_device_rows(&self.ranks, input)?;
3070 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3071 return Err(format!(
3072 "replicated RMS norm weight/eps {}/{} != width {}",
3073 weight.len(),
3074 eps,
3075 input.width
3076 )
3077 .into());
3078 }
3079 let mut ranks = Vec::with_capacity(self.ranks.len());
3080 for (rank, engine) in self.ranks.iter().enumerate() {
3081 let _main = engine.gpu.enter_main()?;
3082 let weight = engine.htod(weight)?;
3083 let mut output = engine.uninit(input.tokens * input.width)?;
3084 engine.rms_norm(
3085 &input.ranks[rank],
3086 &weight,
3087 &mut output,
3088 input.width,
3089 input.tokens,
3090 eps,
3091 )?;
3092 ranks.push(output);
3093 }
3094 Ok(ResidentReplicatedDeviceRows {
3095 ranks,
3096 tokens: input.tokens,
3097 width: input.width,
3098 })
3099 }
3100
3101 pub fn add_rms_norm_replicated_device_rows(
3103 &self,
3104 input: &ResidentReplicatedDeviceRows,
3105 update: &ResidentReplicatedDeviceRows,
3106 weight: &[f32],
3107 eps: f32,
3108 ) -> Result<
3109 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3110 Box<dyn std::error::Error>,
3111 > {
3112 validate_replicated_device_rows(&self.ranks, input)?;
3113 validate_replicated_device_rows(&self.ranks, update)?;
3114 if input.tokens != update.tokens
3115 || input.width != update.width
3116 || weight.len() != input.width
3117 || !eps.is_finite()
3118 || eps <= 0.0
3119 {
3120 return Err(format!(
3121 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3122 input.tokens,
3123 input.width,
3124 update.tokens,
3125 update.width,
3126 weight.len(),
3127 )
3128 .into());
3129 }
3130 let values = input.tokens * input.width;
3131 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3132 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3133 for (rank, engine) in self.ranks.iter().enumerate() {
3134 let _main = engine.gpu.enter_main()?;
3135 let weight = engine.htod(weight)?;
3136 let mut residual = engine.uninit(values)?;
3137 let mut normalized = engine.uninit(values)?;
3138 engine.add_rms_norm(
3139 &input.ranks[rank],
3140 &update.ranks[rank],
3141 &weight,
3142 &mut residual,
3143 &mut normalized,
3144 input.width,
3145 input.tokens,
3146 eps,
3147 )?;
3148 residual_ranks.push(residual);
3149 normalized_ranks.push(normalized);
3150 }
3151 Ok((
3152 ResidentReplicatedDeviceRows {
3153 ranks: residual_ranks,
3154 tokens: input.tokens,
3155 width: input.width,
3156 },
3157 ResidentReplicatedDeviceRows {
3158 ranks: normalized_ranks,
3159 tokens: input.tokens,
3160 width: input.width,
3161 },
3162 ))
3163 }
3164
3165 pub fn collect_replicated_device_rows(
3166 &self,
3167 rows: &ResidentReplicatedDeviceRows,
3168 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3169 validate_replicated_device_rows(&self.ranks, rows)?;
3170 let mut outputs = Vec::with_capacity(self.ranks.len());
3171 for (rank, engine) in self.ranks.iter().enumerate() {
3172 let _main = engine.gpu.enter_main()?;
3173 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3174 }
3175 Ok(outputs)
3176 }
3177
3178 pub fn upload_bf16_row_parallel(
3179 &self,
3180 matrix: Bf16Matrix<'_>,
3181 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3182 matrix.validate()?;
3183 let tp = self.ranks.len();
3184 if matrix.in_features % tp != 0 {
3185 return Err(format!(
3186 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3187 matrix.in_features
3188 )
3189 .into());
3190 }
3191 let mut ranks = Vec::with_capacity(tp);
3192 for (rank, engine) in self.ranks.iter().enumerate() {
3193 let shard = bf16_row_shard(matrix, tp, rank)?;
3194 ranks.push(upload_bf16_rank(
3195 engine,
3196 Bf16Matrix {
3197 bytes: &shard,
3198 out_features: matrix.out_features,
3199 in_features: matrix.in_features / tp,
3200 },
3201 false,
3202 )?);
3203 }
3204 Ok(ResidentBf16RowParallel {
3205 ranks,
3206 out_features: matrix.out_features,
3207 in_features: matrix.in_features,
3208 })
3209 }
3210
3211 pub fn bf16_row_parallel_resident(
3212 &self,
3213 matrix: &ResidentBf16RowParallel,
3214 activations: &[f32],
3215 tokens: usize,
3216 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3217 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3218 validate_activations(activations, tokens, matrix.in_features)?;
3219 let tp = self.ranks.len();
3220 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3221 let mut rank_partials = Vec::with_capacity(tp);
3222 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3223 let local_activations =
3224 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3225 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3226 for (sum, value) in reduced.iter_mut().zip(&partial) {
3227 *sum += value;
3228 }
3229 rank_partials.push(partial);
3230 }
3231 Ok(RowParallelResult {
3232 reduced,
3233 rank_partials,
3234 })
3235 }
3236
3237 pub fn upload_step_bf16_row_parallel(
3239 &self,
3240 matrix: Bf16Matrix<'_>,
3241 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3242 self.upload_step_bf16_row_parallel_inner(matrix, false)
3243 }
3244
3245 pub fn upload_step_bf16_row_parallel_f32_mirror(
3246 &self,
3247 matrix: Bf16Matrix<'_>,
3248 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3249 self.upload_step_bf16_row_parallel_inner(matrix, true)
3250 }
3251
3252 fn upload_step_bf16_row_parallel_inner(
3253 &self,
3254 matrix: Bf16Matrix<'_>,
3255 f32_mirror: bool,
3256 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3257 matrix.validate()?;
3258 let tp = self.ranks.len();
3259 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3260 let local_in = matrix.in_features / tp;
3261 let blocks_per_rank = local_in / canonical_chunk_cols;
3262 let mut ranks = Vec::with_capacity(tp);
3263 for (rank, engine) in self.ranks.iter().enumerate() {
3264 let mut blocks = Vec::with_capacity(blocks_per_rank);
3265 for block in 0..blocks_per_rank {
3266 let global_block = rank * blocks_per_rank + block;
3267 let col_start = global_block * canonical_chunk_cols;
3268 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3269 blocks.push(upload_bf16_rank(
3270 engine,
3271 Bf16Matrix {
3272 bytes: &bytes,
3273 out_features: matrix.out_features,
3274 in_features: canonical_chunk_cols,
3275 },
3276 f32_mirror,
3277 )?);
3278 }
3279 ranks.push(blocks);
3280 }
3281 Ok(ResidentStepBf16RowParallel {
3282 ranks,
3283 out_features: matrix.out_features,
3284 in_features: matrix.in_features,
3285 canonical_chunk_cols,
3286 })
3287 }
3288
3289 pub fn step_bf16_row_parallel_resident(
3294 &self,
3295 matrix: &ResidentStepBf16RowParallel,
3296 activations: &[f32],
3297 tokens: usize,
3298 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3299 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3300 validate_activations(activations, tokens, matrix.in_features)?;
3301 let root = &self.ranks[0];
3302 let output_len = tokens
3303 .checked_mul(matrix.out_features)
3304 .ok_or("Step BF16 row output size overflow")?;
3305 let mut reduced = {
3306 let _main = root.gpu.enter_main()?;
3307 root.htod(&vec![0.0f32; output_len])?
3308 };
3309 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3310 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3311 for (block, resident) in blocks.iter().enumerate() {
3312 let global_block = rank * blocks_per_rank + block;
3313 let input = activation_shard(
3314 activations,
3315 tokens,
3316 matrix.in_features,
3317 PRODUCT_MAX_CARDS,
3318 global_block,
3319 );
3320 let partial =
3321 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3322 let next = {
3323 let _main = root.gpu.enter_main()?;
3324 let partial = root.htod(&partial)?;
3325 let mut next = root.uninit(output_len)?;
3326 root.add(&reduced, &partial, &mut next, output_len)?;
3327 next
3328 };
3329 reduced = next;
3330 }
3331 }
3332 let _main = root.gpu.enter_main()?;
3333 root.dtoh(&reduced)
3334 }
3335
3336 pub fn step_bf16_row_parallel_resident_native(
3342 &self,
3343 matrix: &ResidentStepBf16RowParallel,
3344 activations: &[f32],
3345 tokens: usize,
3346 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3347 if self.ranks.len() > 1 && !self.native_p2p {
3348 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3349 }
3350 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3351 validate_activations(activations, tokens, matrix.in_features)?;
3352 let root = &self.ranks[0];
3353 let root_input = {
3354 let _main = root.gpu.enter_main()?;
3355 root.htod(activations)?
3356 };
3357 {
3360 let _main = root.gpu.enter_main()?;
3361 root.stream().synchronize()?;
3362 }
3363 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3364 let _main = root.gpu.enter_main()?;
3365 root.dtoh(&reduced)
3366 }
3367
3368 pub fn step_bf16_row_parallel_resident_native_device(
3376 &self,
3377 matrix: &ResidentStepBf16RowParallel,
3378 root_activation: &CudaSlice<f32>,
3379 tokens: usize,
3380 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3381 if self.ranks.len() > 1 && !self.native_p2p {
3382 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3383 }
3384 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3385 let values = tokens
3386 .checked_mul(matrix.in_features)
3387 .ok_or("device Step BF16 row activation size overflow")?;
3388 let root = &self.ranks[0];
3389 if tokens == 0
3390 || root_activation.len() < values
3391 || root_activation.ordinal() != root.ctx().ordinal()
3392 {
3393 return Err("device Step BF16 row root activation geometry mismatch".into());
3394 }
3395 let root_input = {
3396 let _main = root.gpu.enter_main()?;
3397 let mut root_input = root.uninit(values)?;
3398 root.stream()
3399 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3400 root.stream().synchronize()?; root_input
3402 };
3403 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3404 let _main = root.gpu.enter_main()?;
3405 root.stream().synchronize()?;
3406 Ok(reduced)
3407 }
3408
3409 fn step_bf16_row_native_reduce_from_root(
3414 &self,
3415 matrix: &ResidentStepBf16RowParallel,
3416 root_input: &CudaSlice<f32>,
3417 tokens: usize,
3418 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3419 let root = &self.ranks[0];
3420 let output_len = tokens
3421 .checked_mul(matrix.out_features)
3422 .ok_or("native Step BF16 row output size overflow")?;
3423 let mut reduced = {
3424 let _main = root.gpu.enter_main()?;
3425 root.htod(&vec![0.0f32; output_len])?
3426 };
3427 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3428 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3429 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3430 let mut remote_partial_keepalive = Vec::new();
3431 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3432 for (block, resident) in blocks.iter().enumerate() {
3433 let global_block = rank * blocks_per_rank + block;
3434 let col_start = global_block * matrix.canonical_chunk_cols;
3435 let block_len = tokens
3436 .checked_mul(matrix.canonical_chunk_cols)
3437 .ok_or("native Step BF16 row block size overflow")?;
3438 let block_input = if self.bulk_p2p {
3439 let root_packed = {
3440 let _main = root.gpu.enter_main()?;
3441 let mut root_packed = root.uninit(block_len)?;
3442 root.copy_rows_strided(
3443 &root_input,
3444 &mut root_packed,
3445 matrix.canonical_chunk_cols,
3446 tokens,
3447 matrix.in_features,
3448 col_start,
3449 )?;
3450 root_packed
3451 };
3452 if rank == 0 {
3453 root_packed
3454 } else {
3455 {
3458 let _main = root.gpu.enter_main()?;
3459 root.stream().synchronize()?;
3460 }
3461 let engine = &self.ranks[rank];
3462 let _main = engine.gpu.enter_main()?;
3463 let mut block_input = engine.uninit(block_len)?;
3464 engine
3465 .stream()
3466 .memcpy_dtod(&root_packed, &mut block_input)?;
3467 root_packed_keepalive.push(root_packed);
3468 block_input
3469 }
3470 } else {
3471 let engine = &self.ranks[rank];
3472 let _main = engine.gpu.enter_main()?;
3473 let mut block_input = engine.uninit(block_len)?;
3474 for token in 0..tokens {
3475 let source_start = token * matrix.in_features + col_start;
3476 let source = root_input
3477 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3478 let destination_start = token * matrix.canonical_chunk_cols;
3479 let mut destination = block_input.slice_mut(
3480 destination_start..destination_start + matrix.canonical_chunk_cols,
3481 );
3482 engine.stream().memcpy_dtod(&source, &mut destination)?;
3483 }
3484 block_input
3485 };
3486 let partial = run_resident_bf16_rank_device(
3487 &self.ranks[rank],
3488 resident,
3489 &block_input,
3490 tokens,
3491 None,
3492 self.bulk_p2p,
3493 )?;
3494 block_input_keepalive.push(block_input);
3495 let root_partial = if rank == 0 {
3496 partial
3497 } else {
3498 {
3501 let engine = &self.ranks[rank];
3502 let _main = engine.gpu.enter_main()?;
3503 engine.stream().synchronize()?;
3504 }
3505 let _main = root.gpu.enter_main()?;
3506 let mut peer_partial = root.uninit(output_len)?;
3507 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3508 remote_partial_keepalive.push(partial);
3509 peer_partial
3510 };
3511 let next = {
3512 let _main = root.gpu.enter_main()?;
3513 let mut next = root.uninit(output_len)?;
3514 root.add(&reduced, &root_partial, &mut next, output_len)?;
3515 next
3516 };
3517 reduced = next;
3518 }
3519 }
3520 {
3521 let _main = root.gpu.enter_main()?;
3522 root.stream().synchronize()?;
3523 }
3524 drop(remote_partial_keepalive);
3525 drop(root_packed_keepalive);
3526 drop(block_input_keepalive);
3527 Ok(reduced)
3528 }
3529
3530 pub fn step_bf16_row_parallel_resident_root_device(
3533 &self,
3534 matrix: &ResidentStepBf16RowParallel,
3535 rank_activations: &[CudaSlice<f32>],
3536 tokens: usize,
3537 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3538 if self.ranks.len() > 1 && !self.native_p2p {
3539 return Err(
3540 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3541 );
3542 }
3543 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3544 let local_width = matrix.in_features / self.ranks.len();
3545 let shard_len = tokens
3546 .checked_mul(local_width)
3547 .ok_or("device Step BF16 row shard size overflow")?;
3548 if tokens == 0
3549 || rank_activations.len() != self.ranks.len()
3550 || rank_activations
3551 .iter()
3552 .zip(&self.ranks)
3553 .any(|(rows, engine)| {
3554 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3555 })
3556 {
3557 return Err("device Step BF16 row activation shard geometry changed".into());
3558 }
3559
3560 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3561 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3562 let mut partials = Vec::with_capacity(self.ranks.len());
3563 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3564 if blocks.len() != blocks_per_rank {
3565 return Err(format!(
3566 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3567 blocks.len()
3568 )
3569 .into());
3570 }
3571 let engine = &self.ranks[rank];
3572 let _main = engine.gpu.enter_main()?;
3573 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3574 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3575 for (block, resident) in blocks.iter().enumerate() {
3576 let block_len = tokens
3577 .checked_mul(matrix.canonical_chunk_cols)
3578 .ok_or("device Step BF16 row block size overflow")?;
3579 let mut block_input = engine.uninit(block_len)?;
3580 let local_col_start = block * matrix.canonical_chunk_cols;
3581 if self.bulk_p2p {
3582 engine.copy_rows_strided(
3583 &rank_activations[rank],
3584 &mut block_input,
3585 matrix.canonical_chunk_cols,
3586 tokens,
3587 local_width,
3588 local_col_start,
3589 )?;
3590 } else {
3591 for token in 0..tokens {
3592 let source_start = token * local_width + local_col_start;
3593 let source = rank_activations[rank]
3594 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3595 let destination_start = token * matrix.canonical_chunk_cols;
3596 let mut destination = block_input.slice_mut(
3597 destination_start..destination_start + matrix.canonical_chunk_cols,
3598 );
3599 engine.stream().memcpy_dtod(&source, &mut destination)?;
3600 }
3601 }
3602 let partial = run_resident_bf16_rank_device(
3603 engine,
3604 resident,
3605 &block_input,
3606 tokens,
3607 None,
3608 self.bulk_p2p,
3609 )?;
3610 rank_inputs.push(block_input);
3611 rank_partials.push(partial);
3612 }
3613 block_inputs.push(rank_inputs);
3614 partials.push(rank_partials);
3615 }
3616 for engine in self.ranks.iter().skip(1) {
3617 let _main = engine.gpu.enter_main()?;
3618 engine.stream().synchronize()?;
3619 }
3620
3621 let output_len = tokens
3622 .checked_mul(matrix.out_features)
3623 .ok_or("device Step BF16 row output size overflow")?;
3624 let root = &self.ranks[0];
3625 let _main = root.gpu.enter_main()?;
3626 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3627 let mut remote_partials = Vec::new();
3628 for (rank, rank_partials) in partials.into_iter().enumerate() {
3629 for partial in rank_partials {
3630 let root_partial = if rank == 0 {
3631 partial
3632 } else {
3633 let mut peer_partial = root.uninit(output_len)?;
3634 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3635 remote_partials.push(partial);
3636 peer_partial
3637 };
3638 let mut next = root.uninit(output_len)?;
3639 root.add(&reduced, &root_partial, &mut next, output_len)?;
3640 reduced = next;
3641 }
3642 }
3643 root.stream().synchronize()?;
3644 drop(remote_partials);
3645 drop(block_inputs);
3646 Ok(reduced)
3647 }
3648
3649 pub fn step_bf16_row_parallel_resident_replicated_device(
3651 &self,
3652 matrix: &ResidentStepBf16RowParallel,
3653 rank_activations: &[CudaSlice<f32>],
3654 tokens: usize,
3655 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3656 let reduced =
3657 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3658 let output_len = tokens
3659 .checked_mul(matrix.out_features)
3660 .ok_or("device Step BF16 row output size overflow")?;
3661 let mut ranks = Vec::with_capacity(self.ranks.len());
3662 ranks.push(reduced);
3663 for engine in self.ranks.iter().skip(1) {
3664 let _main = engine.gpu.enter_main()?;
3665 let mut peer_output = engine.uninit(output_len)?;
3666 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3667 ranks.push(peer_output);
3668 }
3669 Ok(ResidentReplicatedDeviceRows {
3670 ranks,
3671 tokens,
3672 width: matrix.out_features,
3673 })
3674 }
3675
3676 pub fn upload_expert(
3677 &self,
3678 gate: E4m3BlockMatrix<'_>,
3679 up: E4m3BlockMatrix<'_>,
3680 down: E4m3BlockMatrix<'_>,
3681 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3682 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3683 return Err("TP expert gate/up dimensions differ".into());
3684 }
3685 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3686 return Err(format!(
3687 "TP expert down {}x{} does not invert gate/up {}x{}",
3688 down.out_features, down.in_features, gate.out_features, gate.in_features
3689 )
3690 .into());
3691 }
3692 Ok(ResidentTpExpert {
3693 gate: self.upload_column_parallel(gate)?,
3694 up: self.upload_column_parallel(up)?,
3695 down: self.upload_row_parallel(down)?,
3696 input_width: gate.in_features,
3697 expert_width: gate.out_features,
3698 })
3699 }
3700
3701 pub fn run_expert(
3702 &self,
3703 expert: &ResidentTpExpert,
3704 input: &[f32],
3705 tokens: usize,
3706 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3707 validate_activations(input, tokens, expert.input_width)?;
3708 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3709 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3710 let activated: Vec<f32> = gate
3711 .gathered
3712 .iter()
3713 .zip(&up.gathered)
3714 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3715 .collect();
3716 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3717 Ok(self
3718 .row_parallel_resident(&expert.down, &activated, tokens)?
3719 .reduced)
3720 }
3721
3722 pub fn upload_expert_parallel(
3723 &self,
3724 gate: E4m3ExpertBank<'_>,
3725 up: E4m3ExpertBank<'_>,
3726 down: E4m3ExpertBank<'_>,
3727 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3728 gate.validate()?;
3729 up.validate()?;
3730 down.validate()?;
3731 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3732 return Err("EP gate/up/down expert counts differ".into());
3733 }
3734 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3735 return Err("EP gate/up dimensions differ".into());
3736 }
3737 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3738 return Err(format!(
3739 "EP down {}x{} does not invert gate/up {}x{}",
3740 down.out_features, down.in_features, gate.out_features, gate.in_features
3741 )
3742 .into());
3743 }
3744 if gate.expert_count % self.ranks.len() != 0 {
3745 return Err(format!(
3746 "EP expert count {} is not divisible by {} ranks",
3747 gate.expert_count,
3748 self.ranks.len()
3749 )
3750 .into());
3751 }
3752
3753 let per_rank = gate.expert_count / self.ranks.len();
3754 let mut ranks = Vec::with_capacity(self.ranks.len());
3755 for (rank, engine) in self.ranks.iter().enumerate() {
3756 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3757 ranks.push(ResidentEpRank {
3758 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3759 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3760 down: upload_expert_bank_rank(engine, down, expert_range)?,
3761 });
3762 }
3763 Ok(ResidentExpertParallel {
3764 ranks,
3765 expert_count: gate.expert_count,
3766 input_width: gate.in_features,
3767 expert_width: gate.out_features,
3768 })
3769 }
3770
3771 #[allow(clippy::too_many_arguments)]
3777 pub fn prepare_step_grouped_fp8_gate(
3778 &self,
3779 gate: E4m3ExpertBank<'_>,
3780 up: E4m3ExpertBank<'_>,
3781 down: E4m3ExpertBank<'_>,
3782 input: &[f32],
3783 tokens: usize,
3784 selected: &[usize],
3785 activation_limit: Option<f32>,
3786 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3787 gate.validate()?;
3788 up.validate()?;
3789 down.validate()?;
3790 validate_step_expert_activation_limit(activation_limit)?;
3791 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3792 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3793 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3794 {
3795 return Err(format!(
3796 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3797 got gate/up/down={}/{}/{}",
3798 gate.expert_count, up.expert_count, down.expert_count,
3799 )
3800 .into());
3801 }
3802 if gate.in_features != up.in_features
3803 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3804 || up.out_features != STEP_GROUPED_FP8_WIDTH
3805 || down.in_features != STEP_GROUPED_FP8_WIDTH
3806 || down.out_features != gate.in_features
3807 {
3808 return Err(format!(
3809 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3810 gate.out_features,
3811 gate.in_features,
3812 up.out_features,
3813 up.in_features,
3814 down.out_features,
3815 down.in_features,
3816 )
3817 .into());
3818 }
3819 validate_activations(input, tokens, gate.in_features)?;
3820 let pairs = tokens
3821 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3822 .ok_or("official Step grouped FP8 route count overflow")?;
3823 if selected.len() != pairs {
3824 return Err(format!(
3825 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3826 ({pairs})",
3827 selected.len()
3828 )
3829 .into());
3830 }
3831 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3832 let mut unique = routes.to_vec();
3833 unique.sort_unstable();
3834 unique.dedup();
3835 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3836 return Err(format!(
3837 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3838 {routes:?}"
3839 )
3840 .into());
3841 }
3842 }
3843
3844 let engine = self
3845 .ranks
3846 .first()
3847 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3848 let _main = engine.gpu.enter_main()?;
3849 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3850 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3851 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3852 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3853 let input = engine.htod(input)?;
3854 let route_csr = ExpertCsr::from_token_routes(
3855 STEP_GROUPED_FP8_EXPERTS,
3856 tokens,
3857 STEP_GROUPED_FP8_TOP_K,
3858 selected,
3859 )?
3860 .upload(engine)?;
3861 let pair_rows = (0..pairs).collect::<Vec<_>>();
3862 let down_csr =
3863 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3864 .upload(engine)?;
3865 let gate_workspace =
3866 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3867 let up_workspace =
3868 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3869 let down_workspace =
3870 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3871 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3872 Ok(PreparedStepGroupedFp8Gate {
3873 device: engine.ctx().ordinal(),
3874 gate,
3875 up,
3876 down,
3877 input,
3878 route_csr,
3879 down_csr,
3880 gate_workspace,
3881 up_workspace,
3882 down_workspace,
3883 activation,
3884 activation_limit,
3885 tokens,
3886 pairs,
3887 })
3888 }
3889
3890 pub fn run_step_grouped_fp8_gate(
3892 &self,
3893 plan: &mut PreparedStepGroupedFp8Gate,
3894 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3895 let engine = self
3896 .ranks
3897 .first()
3898 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3899 if engine.ctx().ordinal() != plan.device {
3900 return Err(format!(
3901 "official Step grouped FP8 plan device {} != rank-zero device {}",
3902 plan.device,
3903 engine.ctx().ordinal()
3904 )
3905 .into());
3906 }
3907 let _main = engine.gpu.enter_main()?;
3908
3909 plan.gate_workspace.quantize(engine, &plan.input)?;
3910 plan.gate_workspace.project(
3911 engine,
3912 &plan.gate.codes,
3913 &plan.gate.scales,
3914 &plan.route_csr,
3915 plan.gate.code_stride,
3916 plan.gate.scale_stride,
3917 1.0,
3918 )?;
3919 plan.up_workspace.quantize(engine, &plan.input)?;
3920 plan.up_workspace.project(
3921 engine,
3922 &plan.up.codes,
3923 &plan.up.scales,
3924 &plan.route_csr,
3925 plan.up.code_stride,
3926 plan.up.scale_stride,
3927 1.0,
3928 )?;
3929 if let Some(limit) = plan.activation_limit {
3930 engine.silu_clamped_mul_host_expf(
3931 plan.gate_workspace.output(),
3932 plan.up_workspace.output(),
3933 limit,
3934 &mut plan.activation,
3935 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3936 )?;
3937 } else {
3938 engine.silu_mul_host_expf(
3939 plan.gate_workspace.output(),
3940 plan.up_workspace.output(),
3941 &mut plan.activation,
3942 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3943 )?;
3944 }
3945 plan.down_workspace.quantize(engine, &plan.activation)?;
3946 plan.down_workspace.project(
3947 engine,
3948 &plan.down.codes,
3949 &plan.down.scales,
3950 &plan.down_csr,
3951 plan.down.code_stride,
3952 plan.down.scale_stride,
3953 1.0,
3954 )?;
3955
3956 Ok(StepGroupedFp8ProjectionOutput {
3957 gate: engine.dtoh(plan.gate_workspace.output())?,
3958 up: engine.dtoh(plan.up_workspace.output())?,
3959 down: engine.dtoh(plan.down_workspace.output())?,
3960 })
3961 }
3962
3963 pub fn prepare_step_grouped_expert_parallel_gate(
3964 &self,
3965 experts: &ResidentExpertParallel,
3966 input: &[f32],
3967 tokens: usize,
3968 selected: &[usize],
3969 activation_limit: Option<f32>,
3970 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3971 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3972 experts,
3973 input,
3974 tokens,
3975 selected,
3976 activation_limit,
3977 tokens,
3978 )
3979 }
3980
3981 #[allow(clippy::too_many_arguments)]
3982 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3983 &self,
3984 experts: &ResidentExpertParallel,
3985 input: &[f32],
3986 tokens: usize,
3987 selected: &[usize],
3988 activation_limit: Option<f32>,
3989 max_tokens: usize,
3990 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3991 if !self.native_p2p || !self.ep_device_arithmetic {
3992 return Err(
3993 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3994 );
3995 }
3996 validate_step_expert_activation_limit(activation_limit)?;
3997 validate_ep_residency(&self.ranks, experts)?;
3998 validate_activations(input, tokens, experts.input_width)?;
3999 if max_tokens < tokens || max_tokens > i32::MAX as usize {
4000 return Err(format!(
4001 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
4002 )
4003 .into());
4004 }
4005 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4006 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4007 {
4008 return Err(format!(
4009 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4010 STEP_GROUPED_FP8_EXPERTS,
4011 STEP_GROUPED_FP8_WIDTH,
4012 experts.expert_count,
4013 experts.expert_width,
4014 )
4015 .into());
4016 }
4017 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4018 let max_pairs = max_tokens
4019 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4020 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4021 let input_capacity = max_tokens
4022 .checked_mul(experts.input_width)
4023 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4024
4025 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4026 for engine in &self.ranks {
4027 let _main = engine.gpu.enter_main()?;
4028 rank_inputs.push(engine.uninit(input_capacity)?);
4029 }
4030
4031 let mut owners = Vec::with_capacity(self.ranks.len());
4032 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4033 if rank.gate.expert_range != rank.up.expert_range
4034 || rank.gate.expert_range != rank.down.expert_range
4035 {
4036 return Err(format!(
4037 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4038 owner_rank
4039 )
4040 .into());
4041 }
4042 let local_experts = rank.gate.expert_range.len();
4043 let engine = &self.ranks[owner_rank];
4044 let _main = engine.gpu.enter_main()?;
4045 let route_csr =
4046 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4047 let down_csr =
4048 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4049 let gate_workspace = Fp8GroupedWorkspace::new(
4050 engine,
4051 experts.input_width,
4052 experts.expert_width,
4053 max_tokens,
4054 max_pairs,
4055 )?;
4056 let up_workspace = Fp8GroupedWorkspace::new(
4057 engine,
4058 experts.input_width,
4059 experts.expert_width,
4060 max_tokens,
4061 max_pairs,
4062 )?;
4063 let down_workspace = Fp8GroupedWorkspace::new(
4064 engine,
4065 experts.expert_width,
4066 experts.input_width,
4067 max_pairs,
4068 max_pairs,
4069 )?;
4070 let activation = engine.uninit(
4071 max_pairs
4072 .checked_mul(experts.expert_width)
4073 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4074 )?;
4075 owners.push(PreparedStepGroupedExpertOwner {
4076 rank: owner_rank,
4077 global_pairs: Vec::new(),
4078 route_csr,
4079 down_csr,
4080 gate_workspace,
4081 up_workspace,
4082 down_workspace,
4083 activation,
4084 });
4085 }
4086
4087 let mut plan = PreparedStepGroupedExpertParallelGate {
4088 rank_inputs,
4089 owners,
4090 activation_limit,
4091 tokens: 0,
4092 pairs: 0,
4093 max_tokens,
4094 max_pairs,
4095 input_width: experts.input_width,
4096 expert_width: experts.expert_width,
4097 generation: 0,
4098 executed_generation: None,
4099 ready: false,
4100 };
4101 self.refresh_step_grouped_expert_parallel_gate(
4102 experts, &mut plan, input, tokens, selected,
4103 )?;
4104 Ok(plan)
4105 }
4106
4107 fn prepare_step_grouped_expert_parallel_refresh(
4108 &self,
4109 experts: &ResidentExpertParallel,
4110 plan: &PreparedStepGroupedExpertParallelGate,
4111 tokens: usize,
4112 selected: &[usize],
4113 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4114 {
4115 validate_ep_residency(&self.ranks, experts)?;
4116 if plan.rank_inputs.len() != self.ranks.len()
4117 || plan.owners.len() != self.ranks.len()
4118 || plan.input_width != experts.input_width
4119 || plan.expert_width != experts.expert_width
4120 || tokens > plan.max_tokens
4121 {
4122 return Err(format!(
4123 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4124 input={}/{} expert={}/{} tokens={}/{}",
4125 plan.rank_inputs.len(),
4126 self.ranks.len(),
4127 plan.owners.len(),
4128 self.ranks.len(),
4129 plan.input_width,
4130 experts.input_width,
4131 plan.expert_width,
4132 experts.expert_width,
4133 tokens,
4134 plan.max_tokens,
4135 )
4136 .into());
4137 }
4138 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4139 if pairs > plan.max_pairs {
4140 return Err(format!(
4141 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4142 plan.max_pairs
4143 )
4144 .into());
4145 }
4146 let next_generation = plan
4147 .generation
4148 .checked_add(1)
4149 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4150 let owner_routes = partition_expert_owner_routes(
4151 experts.expert_count,
4152 self.ranks.len(),
4153 tokens,
4154 STEP_GROUPED_FP8_TOP_K,
4155 selected,
4156 )?;
4157 let mut schedules = Vec::with_capacity(self.ranks.len());
4158 for routes in owner_routes {
4159 if routes.selected.is_empty() {
4160 schedules.push(None);
4161 continue;
4162 }
4163 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4164 let local_pairs = routes.selected.len();
4165 let route_csr = ExpertCsr::from_pair_rows(
4166 local_experts,
4167 tokens,
4168 &routes.selected,
4169 &routes.token_rows,
4170 )?;
4171 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4172 let down_csr = ExpertCsr::from_pair_rows(
4173 local_experts,
4174 local_pairs,
4175 &routes.selected,
4176 &down_rows,
4177 )?;
4178 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4179 global_pairs: routes.global_pairs,
4180 route_csr,
4181 down_csr,
4182 }));
4183 }
4184 Ok((pairs, next_generation, schedules))
4185 }
4186
4187 fn commit_step_grouped_expert_parallel_refresh(
4188 &self,
4189 plan: &mut PreparedStepGroupedExpertParallelGate,
4190 tokens: usize,
4191 pairs: usize,
4192 next_generation: u64,
4193 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4194 ) -> Result<(), Box<dyn std::error::Error>> {
4195 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4196 let engine = &self.ranks[owner.rank];
4197 let _main = engine.gpu.enter_main()?;
4198 if let Some(schedule) = schedule {
4199 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4200 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4201 owner.global_pairs = schedule.global_pairs;
4202 } else {
4203 owner.route_csr.clear();
4204 owner.down_csr.clear();
4205 owner.global_pairs.clear();
4206 }
4207 }
4208 plan.tokens = tokens;
4209 plan.pairs = pairs;
4210 plan.generation = next_generation;
4211 plan.ready = true;
4212 Ok(())
4213 }
4214
4215 pub fn refresh_step_grouped_expert_parallel_gate(
4216 &self,
4217 experts: &ResidentExpertParallel,
4218 plan: &mut PreparedStepGroupedExpertParallelGate,
4219 input: &[f32],
4220 tokens: usize,
4221 selected: &[usize],
4222 ) -> Result<(), Box<dyn std::error::Error>> {
4223 validate_activations(input, tokens, experts.input_width)?;
4224 let (pairs, next_generation, schedules) =
4225 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4226
4227 plan.ready = false;
4228 plan.executed_generation = None;
4229 {
4230 let root = &self.ranks[0];
4231 let _main = root.gpu.enter_main()?;
4232 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4233 root.stream().memcpy_htod(input, &mut destination)?;
4234 root.stream().synchronize()?;
4235 }
4236 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4237 let root_input = &root_inputs[0];
4238 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4239 let engine = &self.ranks[rank + 1];
4240 let _main = engine.gpu.enter_main()?;
4241 let mut destination = peer_input.slice_mut(0..input.len());
4242 engine
4243 .stream()
4244 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4245 }
4246 self.commit_step_grouped_expert_parallel_refresh(
4247 plan,
4248 tokens,
4249 pairs,
4250 next_generation,
4251 schedules,
4252 )
4253 }
4254
4255 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4260 &self,
4261 experts: &ResidentExpertParallel,
4262 plan: &mut PreparedStepGroupedExpertParallelGate,
4263 input: &CudaSlice<f32>,
4264 tokens: usize,
4265 selected: &[usize],
4266 ) -> Result<(), Box<dyn std::error::Error>> {
4267 let input_values = tokens
4268 .checked_mul(experts.input_width)
4269 .ok_or("Step owner-grouped FP8 input size overflow")?;
4270 let root = self
4271 .ranks
4272 .first()
4273 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4274 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4275 return Err(format!(
4276 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4277 device {}",
4278 input.len(),
4279 input.ordinal(),
4280 input_values,
4281 root.ctx().ordinal(),
4282 )
4283 .into());
4284 }
4285 let (pairs, next_generation, schedules) =
4286 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4287
4288 plan.ready = false;
4289 plan.executed_generation = None;
4290 {
4291 let _main = root.gpu.enter_main()?;
4292 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4293 root.stream()
4294 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4295 root.stream().synchronize()?;
4296 }
4297 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4298 let root_input = &root_inputs[0];
4299 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4300 let engine = &self.ranks[rank + 1];
4301 let _main = engine.gpu.enter_main()?;
4302 let mut destination = peer_input.slice_mut(0..input_values);
4303 engine
4304 .stream()
4305 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4306 }
4307 self.commit_step_grouped_expert_parallel_refresh(
4308 plan,
4309 tokens,
4310 pairs,
4311 next_generation,
4312 schedules,
4313 )
4314 }
4315
4316 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4321 &self,
4322 experts: &ResidentExpertParallel,
4323 plan: &mut PreparedStepGroupedExpertParallelGate,
4324 input: &ResidentReplicatedDeviceRows,
4325 ) -> Result<(), Box<dyn std::error::Error>> {
4326 validate_ep_residency(&self.ranks, experts)?;
4327 validate_replicated_device_rows(&self.ranks, input)?;
4328 if !plan.ready
4329 || input.tokens != plan.tokens
4330 || input.width != plan.input_width
4331 || input.tokens > plan.max_tokens
4332 || plan.rank_inputs.len() != self.ranks.len()
4333 || plan.owners.len() != self.ranks.len()
4334 || plan.input_width != experts.input_width
4335 || plan.expert_width != experts.expert_width
4336 {
4337 return Err("Step owner-grouped replicated input geometry changed".into());
4338 }
4339 let values = input
4340 .tokens
4341 .checked_mul(input.width)
4342 .ok_or("Step owner-grouped replicated input size overflow")?;
4343 let next_generation = plan
4344 .generation
4345 .checked_add(1)
4346 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4347 plan.ready = false;
4348 plan.executed_generation = None;
4349 for (rank, engine) in self.ranks.iter().enumerate() {
4350 let _main = engine.gpu.enter_main()?;
4351 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4352 engine
4353 .stream()
4354 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4355 }
4356 plan.generation = next_generation;
4357 plan.ready = true;
4358 Ok(())
4359 }
4360
4361 pub fn execute_step_grouped_expert_parallel_gate(
4362 &self,
4363 experts: &ResidentExpertParallel,
4364 plan: &mut PreparedStepGroupedExpertParallelGate,
4365 ) -> Result<(), Box<dyn std::error::Error>> {
4366 validate_ep_residency(&self.ranks, experts)?;
4367 if !plan.ready
4368 || plan.rank_inputs.len() != self.ranks.len()
4369 || plan.owners.len() != self.ranks.len()
4370 || plan.input_width != experts.input_width
4371 || plan.expert_width != experts.expert_width
4372 {
4373 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4374 }
4375 plan.executed_generation = None;
4376
4377 for owner in &mut plan.owners {
4378 if owner.global_pairs.is_empty() {
4379 continue;
4380 }
4381 let engine = &self.ranks[owner.rank];
4382 let bank = &experts.ranks[owner.rank];
4383 let _main = engine.gpu.enter_main()?;
4384 let local_pairs = owner.global_pairs.len();
4385 owner.gate_workspace.quantize_for_shape(
4386 engine,
4387 &plan.rank_inputs[owner.rank],
4388 plan.tokens,
4389 local_pairs,
4390 )?;
4391 owner.gate_workspace.project(
4392 engine,
4393 &bank.gate.codes,
4394 &bank.gate.scales,
4395 &owner.route_csr,
4396 bank.gate.code_stride,
4397 bank.gate.scale_stride,
4398 1.0,
4399 )?;
4400 owner.up_workspace.quantize_for_shape(
4401 engine,
4402 &plan.rank_inputs[owner.rank],
4403 plan.tokens,
4404 local_pairs,
4405 )?;
4406 owner.up_workspace.project(
4407 engine,
4408 &bank.up.codes,
4409 &bank.up.scales,
4410 &owner.route_csr,
4411 bank.up.code_stride,
4412 bank.up.scale_stride,
4413 1.0,
4414 )?;
4415 }
4416 for owner in &mut plan.owners {
4417 if owner.global_pairs.is_empty() {
4418 continue;
4419 }
4420 let engine = &self.ranks[owner.rank];
4421 let _main = engine.gpu.enter_main()?;
4422 let values = owner.global_pairs.len() * plan.expert_width;
4423 if let Some(limit) = plan.activation_limit {
4424 engine.silu_clamped_mul_host_expf(
4425 owner.gate_workspace.output(),
4426 owner.up_workspace.output(),
4427 limit,
4428 &mut owner.activation,
4429 values,
4430 )?;
4431 } else {
4432 engine.silu_mul_host_expf(
4433 owner.gate_workspace.output(),
4434 owner.up_workspace.output(),
4435 &mut owner.activation,
4436 values,
4437 )?;
4438 }
4439 }
4440 for owner in &mut plan.owners {
4441 if owner.global_pairs.is_empty() {
4442 continue;
4443 }
4444 let engine = &self.ranks[owner.rank];
4445 let bank = &experts.ranks[owner.rank];
4446 let _main = engine.gpu.enter_main()?;
4447 let local_pairs = owner.global_pairs.len();
4448 owner.down_workspace.quantize_for_shape(
4449 engine,
4450 &owner.activation,
4451 local_pairs,
4452 local_pairs,
4453 )?;
4454 owner.down_workspace.project(
4455 engine,
4456 &bank.down.codes,
4457 &bank.down.scales,
4458 &owner.down_csr,
4459 bank.down.code_stride,
4460 bank.down.scale_stride,
4461 1.0,
4462 )?;
4463 }
4464 plan.executed_generation = Some(plan.generation);
4465 Ok(())
4466 }
4467
4468 pub fn collect_step_grouped_expert_parallel_gate(
4469 &self,
4470 plan: &PreparedStepGroupedExpertParallelGate,
4471 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4472 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4473 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4474 }
4475 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4476 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4477 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4478 for owner in &plan.owners {
4479 if owner.global_pairs.is_empty() {
4480 continue;
4481 }
4482 let engine = &self.ranks[owner.rank];
4483 let _main = engine.gpu.enter_main()?;
4484 let owner_gate = engine.dtoh_view(
4485 &owner
4486 .gate_workspace
4487 .output()
4488 .slice(0..owner.gate_workspace.output_len()),
4489 )?;
4490 let owner_up = engine.dtoh_view(
4491 &owner
4492 .up_workspace
4493 .output()
4494 .slice(0..owner.up_workspace.output_len()),
4495 )?;
4496 let owner_down = engine.dtoh_view(
4497 &owner
4498 .down_workspace
4499 .output()
4500 .slice(0..owner.down_workspace.output_len()),
4501 )?;
4502 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4503 let local_expert = local_pair * plan.expert_width;
4504 let global_expert = global_pair * plan.expert_width;
4505 gate[global_expert..global_expert + plan.expert_width]
4506 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4507 up[global_expert..global_expert + plan.expert_width]
4508 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4509
4510 let local_hidden = local_pair * plan.input_width;
4511 let global_hidden = global_pair * plan.input_width;
4512 down[global_hidden..global_hidden + plan.input_width]
4513 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4514 }
4515 }
4516 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4517 }
4518
4519 pub fn run_step_grouped_expert_parallel_gate(
4520 &self,
4521 experts: &ResidentExpertParallel,
4522 plan: &mut PreparedStepGroupedExpertParallelGate,
4523 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4524 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4525 self.collect_step_grouped_expert_parallel_gate(plan)
4526 }
4527
4528 pub fn prepare_step_grouped_expert_parallel_combine(
4529 &self,
4530 plan: &PreparedStepGroupedExpertParallelGate,
4531 route_weights: &[f32],
4532 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4533 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4534 return Err(
4535 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4536 );
4537 }
4538 let owner_pairs = plan
4539 .owners
4540 .iter()
4541 .map(|owner| owner.global_pairs.as_slice())
4542 .collect::<Vec<_>>();
4543 let shape = validate_weighted_route_combine(
4544 plan.input_width,
4545 STEP_GROUPED_FP8_TOP_K,
4546 plan.max_tokens,
4547 plan.tokens,
4548 &owner_pairs,
4549 route_weights,
4550 )?;
4551 if shape.max_pairs != plan.max_pairs {
4552 return Err(format!(
4553 "Step owner-grouped combine capacity {} != projection capacity {}",
4554 shape.max_pairs, plan.max_pairs
4555 )
4556 .into());
4557 }
4558 let root = self
4559 .ranks
4560 .first()
4561 .ok_or("Step owner-grouped combine has no root rank")?;
4562 let slot_values = shape
4563 .max_pairs
4564 .checked_mul(plan.input_width)
4565 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4566 let output_values = plan
4567 .max_tokens
4568 .checked_mul(plan.input_width)
4569 .ok_or("Step owner-grouped combine output capacity overflow")?;
4570 let (root_device, owners, peer_staging, slots, weights, output) = {
4571 let _main = root.gpu.enter_main()?;
4572 let mut owners = Vec::with_capacity(plan.owners.len());
4573 for _ in &plan.owners {
4574 owners.push(PreparedPeerWeightedRouteOwner {
4575 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4576 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4577 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4578 active_pairs: 0,
4579 });
4580 }
4581 (
4582 root.ctx().ordinal(),
4583 owners,
4584 root.uninit(slot_values)?,
4585 root.uninit(slot_values)?,
4586 root.uninit(shape.max_pairs)?,
4587 root.uninit(output_values)?,
4588 )
4589 };
4590 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4591 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4592 for engine in self.ranks.iter().skip(1) {
4593 let _main = engine.gpu.enter_main()?;
4594 peer_devices.push(engine.ctx().ordinal());
4595 peer_outputs.push(engine.uninit(output_values)?);
4596 }
4597 let mut combine = PreparedPeerWeightedRouteCombine {
4598 root_device,
4599 owners,
4600 peer_staging,
4601 slots,
4602 weights,
4603 output,
4604 peer_devices,
4605 peer_outputs,
4606 width: plan.input_width,
4607 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4608 max_tokens: plan.max_tokens,
4609 max_pairs: shape.max_pairs,
4610 tokens: 0,
4611 pairs: 0,
4612 projection_generation: 0,
4613 output_generation: None,
4614 broadcast_generation: None,
4615 ready: false,
4616 };
4617 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4618 Ok(combine)
4619 }
4620
4621 pub fn refresh_step_grouped_expert_parallel_combine(
4622 &self,
4623 plan: &PreparedStepGroupedExpertParallelGate,
4624 combine: &mut PreparedPeerWeightedRouteCombine,
4625 route_weights: &[f32],
4626 ) -> Result<(), Box<dyn std::error::Error>> {
4627 let output_capacity = combine
4628 .max_tokens
4629 .checked_mul(combine.width)
4630 .ok_or("Step owner-grouped combine output capacity overflow")?;
4631 if !plan.ready
4632 || combine.owners.len() != plan.owners.len()
4633 || combine.peer_devices.len() + 1 != self.ranks.len()
4634 || combine.peer_outputs.len() + 1 != self.ranks.len()
4635 || combine.width != plan.input_width
4636 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4637 || combine.max_tokens != plan.max_tokens
4638 || combine.max_pairs != plan.max_pairs
4639 || combine.output.len() < output_capacity
4640 || combine
4641 .peer_outputs
4642 .iter()
4643 .any(|output| output.len() < output_capacity)
4644 {
4645 return Err("Step owner-grouped combine/projection geometry changed".into());
4646 }
4647 if self
4648 .ranks
4649 .iter()
4650 .skip(1)
4651 .zip(&combine.peer_devices)
4652 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4653 {
4654 return Err("Step owner-grouped combine peer devices changed".into());
4655 }
4656 let owner_pairs = plan
4657 .owners
4658 .iter()
4659 .map(|owner| owner.global_pairs.as_slice())
4660 .collect::<Vec<_>>();
4661 let shape = validate_weighted_route_combine(
4662 combine.width,
4663 combine.experts_per_token,
4664 combine.max_tokens,
4665 plan.tokens,
4666 &owner_pairs,
4667 route_weights,
4668 )?;
4669 if shape.max_pairs != combine.max_pairs {
4670 return Err("Step owner-grouped combine capacity changed during refresh".into());
4671 }
4672 let metadata = owner_pairs
4673 .iter()
4674 .map(|pairs| {
4675 let token_rows = pairs
4676 .iter()
4677 .map(|&pair| (pair / combine.experts_per_token) as i32)
4678 .collect::<Vec<_>>();
4679 let slots = pairs
4680 .iter()
4681 .map(|&pair| (pair % combine.experts_per_token) as i32)
4682 .collect::<Vec<_>>();
4683 let weights = pairs
4684 .iter()
4685 .map(|&pair| route_weights[pair])
4686 .collect::<Vec<_>>();
4687 (token_rows, slots, weights)
4688 })
4689 .collect::<Vec<_>>();
4690
4691 combine.ready = false;
4692 combine.output_generation = None;
4693 combine.broadcast_generation = None;
4694 let root = self
4695 .ranks
4696 .first()
4697 .ok_or("Step owner-grouped combine has no root rank")?;
4698 let _main = root.gpu.enter_main()?;
4699 if root.ctx().ordinal() != combine.root_device {
4700 return Err(format!(
4701 "Step owner-grouped combine root device changed {} != {}",
4702 root.ctx().ordinal(),
4703 combine.root_device
4704 )
4705 .into());
4706 }
4707 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4708 if token_rows.is_empty() {
4709 owner.active_pairs = 0;
4710 continue;
4711 }
4712 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4713 root.htod_i32_into(&mut owner.slots, &slots)?;
4714 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4715 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4716 owner.active_pairs = token_rows.len();
4717 }
4718 combine.tokens = plan.tokens;
4719 combine.pairs = shape.pairs;
4720 combine.projection_generation = plan.generation;
4721 combine.ready = true;
4722 Ok(())
4723 }
4724
4725 pub fn execute_step_grouped_expert_parallel_combine(
4726 &self,
4727 plan: &PreparedStepGroupedExpertParallelGate,
4728 combine: &mut PreparedPeerWeightedRouteCombine,
4729 ) -> Result<(), Box<dyn std::error::Error>> {
4730 if !plan.ready
4731 || plan.executed_generation != Some(plan.generation)
4732 || !combine.ready
4733 || combine.tokens != plan.tokens
4734 || combine.pairs != plan.pairs
4735 || combine.width != plan.input_width
4736 || combine.owners.len() != plan.owners.len()
4737 || combine.projection_generation != plan.generation
4738 {
4739 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4740 }
4741 combine.output_generation = None;
4742 combine.broadcast_generation = None;
4743 for owner in &plan.owners {
4744 if owner.rank == 0 || owner.global_pairs.is_empty() {
4745 continue;
4746 }
4747 let engine = &self.ranks[owner.rank];
4748 let _main = engine.gpu.enter_main()?;
4749 engine.stream().synchronize()?;
4750 }
4751 let root = self
4752 .ranks
4753 .first()
4754 .ok_or("Step owner-grouped combine has no root rank")?;
4755 let _main = root.gpu.enter_main()?;
4756 if root.ctx().ordinal() != combine.root_device {
4757 return Err("Step owner-grouped combine is not resident on the root device".into());
4758 }
4759 for (index, owner) in plan.owners.iter().enumerate() {
4760 let metadata = &combine.owners[index];
4761 if owner.global_pairs.len() != metadata.active_pairs {
4762 return Err(format!(
4763 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4764 owner.global_pairs.len(),
4765 metadata.active_pairs
4766 )
4767 .into());
4768 }
4769 if metadata.active_pairs == 0 {
4770 continue;
4771 }
4772 let values = metadata
4773 .active_pairs
4774 .checked_mul(combine.width)
4775 .ok_or("Step owner-grouped combine peer value count overflow")?;
4776 if owner.rank == 0 {
4777 root.scatter_slot(
4778 owner.down_workspace.output(),
4779 &metadata.token_rows,
4780 &metadata.slots,
4781 &metadata.weights,
4782 &mut combine.slots,
4783 &mut combine.weights,
4784 combine.width,
4785 combine.experts_per_token,
4786 metadata.active_pairs,
4787 )?;
4788 } else {
4789 let source = owner.down_workspace.output().slice(0..values);
4790 let mut destination = combine.peer_staging.slice_mut(0..values);
4791 root.stream().memcpy_dtod(&source, &mut destination)?;
4792 root.scatter_slot(
4793 &combine.peer_staging,
4794 &metadata.token_rows,
4795 &metadata.slots,
4796 &metadata.weights,
4797 &mut combine.slots,
4798 &mut combine.weights,
4799 combine.width,
4800 combine.experts_per_token,
4801 metadata.active_pairs,
4802 )?;
4803 }
4804 }
4805 root.reduce_slots_host(
4806 &combine.slots,
4807 &combine.weights,
4808 &mut combine.output,
4809 combine.width,
4810 combine.experts_per_token,
4811 combine.tokens,
4812 )?;
4813 combine.output_generation = Some(plan.generation);
4814 Ok(())
4815 }
4816
4817 pub fn collect_step_grouped_expert_parallel_combine(
4818 &self,
4819 plan: &PreparedStepGroupedExpertParallelGate,
4820 combine: &PreparedPeerWeightedRouteCombine,
4821 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4822 if !plan.ready
4823 || combine.output_generation != Some(plan.generation)
4824 || combine.projection_generation != plan.generation
4825 {
4826 return Err("Step owner-grouped combine output is stale or has not executed".into());
4827 }
4828 let root = self
4829 .ranks
4830 .first()
4831 .ok_or("Step owner-grouped combine has no root rank")?;
4832 let _main = root.gpu.enter_main()?;
4833 if root.ctx().ordinal() != combine.root_device {
4834 return Err("Step owner-grouped combine is not resident on the root device".into());
4835 }
4836 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4837 }
4838
4839 pub fn copy_step_grouped_expert_parallel_combine_root(
4844 &self,
4845 plan: &PreparedStepGroupedExpertParallelGate,
4846 combine: &PreparedPeerWeightedRouteCombine,
4847 destination: &Engine,
4848 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4849 if !plan.ready
4850 || combine.output_generation != Some(plan.generation)
4851 || combine.projection_generation != plan.generation
4852 {
4853 return Err("Step owner-grouped combine output is stale or has not executed".into());
4854 }
4855 let root = self
4856 .ranks
4857 .first()
4858 .ok_or("Step owner-grouped combine has no root rank")?;
4859 if root.ctx().ordinal() != combine.root_device
4860 || destination.ctx().ordinal() != combine.root_device
4861 {
4862 return Err(format!(
4863 "Step owner-grouped combine root/destination devices {}/{} != {}",
4864 root.ctx().ordinal(),
4865 destination.ctx().ordinal(),
4866 combine.root_device,
4867 )
4868 .into());
4869 }
4870 let values = combine
4871 .tokens
4872 .checked_mul(combine.width)
4873 .ok_or("Step owner-grouped combine copy size overflow")?;
4874 {
4875 let _main = root.gpu.enter_main()?;
4876 root.stream().synchronize()?;
4877 }
4878 let _main = destination.gpu.enter_main()?;
4879 let mut output = destination.uninit(values)?;
4880 destination
4881 .stream()
4882 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4883 Ok(output)
4884 }
4885
4886 pub fn broadcast_step_grouped_expert_parallel_combine(
4887 &self,
4888 plan: &PreparedStepGroupedExpertParallelGate,
4889 combine: &mut PreparedPeerWeightedRouteCombine,
4890 ) -> Result<(), Box<dyn std::error::Error>> {
4891 if !plan.ready
4892 || combine.output_generation != Some(plan.generation)
4893 || combine.projection_generation != plan.generation
4894 || combine.peer_devices.len() + 1 != self.ranks.len()
4895 || combine.peer_outputs.len() + 1 != self.ranks.len()
4896 {
4897 return Err("Step owner-grouped combine output cannot be broadcast".into());
4898 }
4899 combine.broadcast_generation = None;
4900 let values = combine
4901 .tokens
4902 .checked_mul(combine.width)
4903 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4904 {
4905 let root = self
4906 .ranks
4907 .first()
4908 .ok_or("Step owner-grouped combine has no root rank")?;
4909 let _main = root.gpu.enter_main()?;
4910 if root.ctx().ordinal() != combine.root_device {
4911 return Err("Step owner-grouped combine root device changed".into());
4912 }
4913 root.stream().synchronize()?;
4914 }
4915 let source = &combine.output;
4916 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4917 let engine = &self.ranks[index + 1];
4918 let _main = engine.gpu.enter_main()?;
4919 if engine.ctx().ordinal() != combine.peer_devices[index] {
4920 return Err(format!(
4921 "Step owner-grouped combine peer {} device changed",
4922 index + 1
4923 )
4924 .into());
4925 }
4926 let mut destination = destination_buffer.slice_mut(0..values);
4927 engine
4928 .stream()
4929 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4930 }
4931 combine.broadcast_generation = Some(plan.generation);
4932 Ok(())
4933 }
4934
4935 pub fn collect_step_grouped_expert_parallel_broadcast(
4936 &self,
4937 plan: &PreparedStepGroupedExpertParallelGate,
4938 combine: &PreparedPeerWeightedRouteCombine,
4939 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4940 if !plan.ready
4941 || combine.output_generation != Some(plan.generation)
4942 || combine.broadcast_generation != Some(plan.generation)
4943 || combine.peer_outputs.len() + 1 != self.ranks.len()
4944 {
4945 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4946 }
4947 let values = combine
4948 .tokens
4949 .checked_mul(combine.width)
4950 .ok_or("Step owner-grouped combine collection size overflow")?;
4951 let mut outputs = Vec::with_capacity(self.ranks.len());
4952 {
4953 let root = &self.ranks[0];
4954 let _main = root.gpu.enter_main()?;
4955 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4956 }
4957 for (index, output) in combine.peer_outputs.iter().enumerate() {
4958 let engine = &self.ranks[index + 1];
4959 let _main = engine.gpu.enter_main()?;
4960 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4961 }
4962 Ok(outputs)
4963 }
4964
4965 pub fn finish_step_grouped_expert_parallel_layer(
4967 &self,
4968 plan: &PreparedStepGroupedExpertParallelGate,
4969 combine: &PreparedPeerWeightedRouteCombine,
4970 shared: &ResidentReplicatedDeviceRows,
4971 residual: &ResidentReplicatedDeviceRows,
4972 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4973 validate_replicated_device_rows(&self.ranks, shared)?;
4974 validate_replicated_device_rows(&self.ranks, residual)?;
4975 if !plan.ready
4976 || plan.executed_generation != Some(plan.generation)
4977 || combine.output_generation != Some(plan.generation)
4978 || combine.broadcast_generation != Some(plan.generation)
4979 || combine.projection_generation != plan.generation
4980 || combine.peer_outputs.len() + 1 != self.ranks.len()
4981 || shared.tokens != combine.tokens
4982 || residual.tokens != combine.tokens
4983 || shared.width != combine.width
4984 || residual.width != combine.width
4985 {
4986 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4987 }
4988 let values = combine
4989 .tokens
4990 .checked_mul(combine.width)
4991 .ok_or("Step full-layer output size overflow")?;
4992 let mut ranks = Vec::with_capacity(self.ranks.len());
4993 for rank in 0..self.ranks.len() {
4994 let engine = &self.ranks[rank];
4995 let _main = engine.gpu.enter_main()?;
4996 let routed = if rank == 0 {
4997 &combine.output
4998 } else {
4999 &combine.peer_outputs[rank - 1]
5000 };
5001 let mut ffn = engine.uninit(values)?;
5002 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
5003 let mut output = engine.uninit(values)?;
5004 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5005 ranks.push(output);
5006 }
5007 Ok(ResidentReplicatedDeviceRows {
5008 ranks,
5009 tokens: combine.tokens,
5010 width: combine.width,
5011 })
5012 }
5013
5014 pub fn run_step_grouped_expert_parallel_combine(
5015 &self,
5016 plan: &PreparedStepGroupedExpertParallelGate,
5017 combine: &mut PreparedPeerWeightedRouteCombine,
5018 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5019 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5020 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5021 }
5022
5023 pub fn upload_tensor_parallel(
5024 &self,
5025 gate: E4m3ExpertBank<'_>,
5026 up: E4m3ExpertBank<'_>,
5027 down: E4m3ExpertBank<'_>,
5028 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5029 gate.validate()?;
5030 up.validate()?;
5031 down.validate()?;
5032 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5033 return Err("TP gate/up/down expert counts differ".into());
5034 }
5035 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5036 return Err("TP gate/up dimensions differ".into());
5037 }
5038 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5039 return Err(format!(
5040 "TP down {}x{} does not invert gate/up {}x{}",
5041 down.out_features, down.in_features, gate.out_features, gate.in_features
5042 )
5043 .into());
5044 }
5045 let tp = self.ranks.len();
5046 validate_column_bank_shape(gate, tp)?;
5047 validate_column_bank_shape(up, tp)?;
5048 validate_row_bank_shape(down, tp)?;
5049
5050 let mut gate_ranks = Vec::with_capacity(tp);
5051 let mut up_ranks = Vec::with_capacity(tp);
5052 let mut down_ranks = Vec::with_capacity(tp);
5053 for (rank, engine) in self.ranks.iter().enumerate() {
5054 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5055 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5056 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5057 }
5058 Ok(ResidentTensorParallel {
5059 bank: ResidentTpExpertBank {
5060 gate: gate_ranks,
5061 up: up_ranks,
5062 down: down_ranks,
5063 expert_count: gate.expert_count,
5064 input_width: gate.in_features,
5065 expert_width: gate.out_features,
5066 },
5067 })
5068 }
5069
5070 pub fn run_tensor_parallel_routes(
5071 &self,
5072 experts: &ResidentTensorParallel,
5073 input: &[f32],
5074 tokens: usize,
5075 selected: &[usize],
5076 route_weights: &[f32],
5077 experts_per_token: usize,
5078 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5079 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5080 validate_activations(input, tokens, experts.bank.input_width)?;
5081 let pairs = tokens
5082 .checked_mul(experts_per_token)
5083 .ok_or("TP route count overflow")?;
5084 if selected.len() != pairs || route_weights.len() != pairs {
5085 return Err(format!(
5086 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5087 {experts_per_token} ({pairs})",
5088 selected.len(),
5089 route_weights.len(),
5090 )
5091 .into());
5092 }
5093 if !route_weights.iter().all(|weight| weight.is_finite()) {
5094 return Err("TP route weights contain a non-finite value".into());
5095 }
5096
5097 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5098 for token in 0..tokens {
5099 let input_row =
5100 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5101 for slot in 0..experts_per_token {
5102 let pair = token * experts_per_token + slot;
5103 let expert = selected[pair];
5104 if expert >= experts.bank.expert_count {
5105 return Err(format!(
5106 "TP selected expert {expert} outside 0..{}",
5107 experts.bank.expert_count
5108 )
5109 .into());
5110 }
5111 let down = if self.native_p2p {
5112 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5113 } else {
5114 let gate =
5115 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5116 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5117 let activated: Vec<f32> = gate
5118 .iter()
5119 .zip(&up)
5120 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5121 .collect();
5122 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5123 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5124 };
5125 let weight = route_weights[pair];
5126 for (sum, value) in output
5127 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5128 .iter_mut()
5129 .zip(down)
5130 {
5131 *sum += weight * value;
5132 }
5133 }
5134 }
5135 Ok(output)
5136 }
5137
5138 fn run_column_bank_expert(
5139 &self,
5140 ranks: &[ResidentE4m3ExpertBankRank],
5141 expert: usize,
5142 input: &[f32],
5143 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5144 let local_out = ranks
5145 .first()
5146 .ok_or("TP column bank has no ranks")?
5147 .out_features;
5148 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5149 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5150 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5151 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5152 }
5153 Ok(gathered)
5154 }
5155
5156 fn run_row_bank_expert(
5157 &self,
5158 ranks: &[ResidentE4m3ExpertBankRank],
5159 expert: usize,
5160 input: &[f32],
5161 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5162 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5163 if input.len() != local_in * ranks.len() {
5164 return Err(format!(
5165 "TP row input {} != {} ranks x {local_in}",
5166 input.len(),
5167 ranks.len()
5168 )
5169 .into());
5170 }
5171 let out_features = ranks[0].out_features;
5172 let mut reduced = vec![0.0f32; out_features];
5173 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5174 let blocks = bank
5175 .k_blocks
5176 .ok_or("TP row bank is not packed in native K-block order")?;
5177 if blocks * FP8_BLOCK != local_in {
5178 return Err(format!(
5179 "TP row bank has {blocks} blocks but local input width is {local_in}"
5180 )
5181 .into());
5182 }
5183 for block in 0..blocks {
5184 let global_start = rank * local_in + block * FP8_BLOCK;
5185 let partial = run_resident_bank_expert_block(
5186 engine,
5187 bank,
5188 expert,
5189 block,
5190 &input[global_start..global_start + FP8_BLOCK],
5191 )?;
5192 for (sum, value) in reduced.iter_mut().zip(partial) {
5193 *sum += value;
5194 }
5195 }
5196 }
5197 Ok(reduced)
5198 }
5199
5200 fn run_tensor_parallel_expert_native(
5201 &self,
5202 bank: &ResidentTpExpertBank,
5203 expert: usize,
5204 input: &[f32],
5205 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5206 if !self.native_p2p || self.ranks.len() < 2 {
5207 return Err("native TP expert execution requires at least two P2P ranks".into());
5208 }
5209 let local_out = bank
5210 .gate
5211 .first()
5212 .ok_or("native TP gate bank has no ranks")?
5213 .out_features;
5214 if local_out * self.ranks.len() != bank.expert_width {
5215 return Err(format!(
5216 "native TP gate shards {}x{local_out} != expert width {}",
5217 self.ranks.len(),
5218 bank.expert_width
5219 )
5220 .into());
5221 }
5222
5223 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5226 let root_input = {
5227 let root = &self.ranks[0];
5228 let _main = root.gpu.enter_main()?;
5229 root.htod(input)?
5230 };
5231 rank_inputs.push(root_input);
5232 for engine in &self.ranks[1..] {
5233 let peer_input = {
5234 let _main = engine.gpu.enter_main()?;
5235 let mut peer_input = engine.uninit(input.len())?;
5236 engine
5237 .stream()
5238 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5239 peer_input
5240 };
5241 rank_inputs.push(peer_input);
5242 }
5243
5244 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5245 let mut up_shards = Vec::with_capacity(self.ranks.len());
5246 for rank in 0..self.ranks.len() {
5247 gate_shards.push(run_resident_bank_expert_device(
5248 &self.ranks[rank],
5249 &bank.gate[rank],
5250 expert,
5251 &rank_inputs[rank],
5252 1,
5253 )?);
5254 up_shards.push(run_resident_bank_expert_device(
5255 &self.ranks[rank],
5256 &bank.up[rank],
5257 expert,
5258 &rank_inputs[rank],
5259 1,
5260 )?);
5261 }
5262
5263 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5267 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5268 let activated = gate
5269 .iter()
5270 .zip(&up)
5271 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5272 .collect::<Vec<_>>();
5273 debug_assert_eq!(activated.len(), bank.expert_width);
5274
5275 let root_activated = {
5276 let root = &self.ranks[0];
5277 let _main = root.gpu.enter_main()?;
5278 root.htod(&activated)?
5279 };
5280 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5281 for (rank, engine) in self.ranks.iter().enumerate() {
5282 let start = rank * local_out;
5283 let source = root_activated.slice(start..start + local_out);
5284 let local = {
5285 let _main = engine.gpu.enter_main()?;
5286 let mut local = engine.uninit(local_out)?;
5287 engine.stream().memcpy_dtod(&source, &mut local)?;
5288 local
5289 };
5290 rank_activated.push(local);
5291 }
5292
5293 let out_features = bank
5294 .down
5295 .first()
5296 .ok_or("native TP down bank has no ranks")?
5297 .out_features;
5298 let mut reduced = {
5299 let root = &self.ranks[0];
5300 let _main = root.gpu.enter_main()?;
5301 root.htod(&vec![0.0f32; out_features])?
5302 };
5303 let mut remote_partial_keepalive = Vec::new();
5304 for rank in 0..self.ranks.len() {
5305 let down = &bank.down[rank];
5306 let blocks = down
5307 .k_blocks
5308 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5309 if blocks * FP8_BLOCK != local_out {
5310 return Err(format!(
5311 "native TP rank {rank} has {blocks} blocks but local activation width is \
5312 {local_out}"
5313 )
5314 .into());
5315 }
5316 for block in 0..blocks {
5317 let start = block * FP8_BLOCK;
5318 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5319 let partial = run_resident_bank_expert_block_device(
5320 &self.ranks[rank],
5321 down,
5322 expert,
5323 block,
5324 &input_block,
5325 )?;
5326 let root_partial = if rank == 0 {
5327 partial
5328 } else {
5329 let root = &self.ranks[0];
5330 let _main = root.gpu.enter_main()?;
5331 let mut peer_partial = root.uninit(out_features)?;
5332 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5333 remote_partial_keepalive.push(partial);
5334 peer_partial
5335 };
5336 let next = {
5337 let root = &self.ranks[0];
5338 let _main = root.gpu.enter_main()?;
5339 let mut next = root.uninit(out_features)?;
5340 root.add(&reduced, &root_partial, &mut next, out_features)?;
5341 next
5342 };
5343 reduced = next;
5344 }
5345 }
5346 let output = {
5347 let root = &self.ranks[0];
5348 let _main = root.gpu.enter_main()?;
5349 root.dtoh(&reduced)?
5350 };
5351 drop(remote_partial_keepalive);
5352 Ok(output)
5353 }
5354
5355 pub fn gather_native_column_shards_device(
5357 &self,
5358 shards: &[CudaSlice<f32>],
5359 tokens: usize,
5360 local_out: usize,
5361 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5362 let shard_len = tokens
5363 .checked_mul(local_out)
5364 .ok_or("native TP gather shard size overflow")?;
5365 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5366 return Err("native TP gather shard geometry mismatch".into());
5367 }
5368 for engine in &self.ranks[1..] {
5372 let _main = engine.gpu.enter_main()?;
5373 engine.stream().synchronize()?;
5374 }
5375 let root = &self.ranks[0];
5376 let _main = root.gpu.enter_main()?;
5377 let global_out = shards
5378 .len()
5379 .checked_mul(local_out)
5380 .ok_or("native TP gather output width overflow")?;
5381 let gathered_len = tokens
5382 .checked_mul(global_out)
5383 .ok_or("native TP gather output size overflow")?;
5384 let mut gathered = root.uninit(gathered_len)?;
5385 if self.bulk_p2p {
5386 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5387 if shards.len() > 1 {
5388 let mut staging = root.uninit(shard_len)?;
5389 for (rank, shard) in shards.iter().enumerate().skip(1) {
5390 root.stream().memcpy_dtod(shard, &mut staging)?;
5391 root.place_rows_strided(
5392 &staging,
5393 &mut gathered,
5394 local_out,
5395 tokens,
5396 global_out,
5397 rank * local_out,
5398 )?;
5399 }
5400 }
5401 } else {
5402 for token in 0..tokens {
5403 for (rank, shard) in shards.iter().enumerate() {
5404 let source = shard.slice(token * local_out..(token + 1) * local_out);
5405 let start = token * global_out + rank * local_out;
5406 let mut destination = gathered.slice_mut(start..start + local_out);
5407 root.stream().memcpy_dtod(&source, &mut destination)?;
5408 }
5409 }
5410 }
5411 Ok(gathered)
5412 }
5413
5414 pub fn gather_native_column_shards(
5415 &self,
5416 shards: &[CudaSlice<f32>],
5417 tokens: usize,
5418 local_out: usize,
5419 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5420 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5421 let root = &self.ranks[0];
5422 let _main = root.gpu.enter_main()?;
5423 root.dtoh(&gathered)
5424 }
5425
5426 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5427 &self.decode_v2
5428 }
5429
5430 pub(crate) fn decode_v2_ensure(
5439 &self,
5440 e: &Engine,
5441 q_m: &ResidentBf16ColumnParallel,
5442 k_m: &ResidentBf16ColumnParallel,
5443 v_m: &ResidentBf16ColumnParallel,
5444 o_m: &ResidentStepBf16RowParallel,
5445 heads: usize,
5446 ) -> Result<usize, Box<dyn std::error::Error>> {
5447 if self.ranks.len() > 1 && !self.native_p2p {
5448 return Err("step TP decode v2 requires native P2P ranks".into());
5449 }
5450 let ranks = self.ranks.len();
5451 let fused_door = step_tp_qkv_fused_enabled()?;
5455 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5456 ResidentBf16Weight::F32(_) => true,
5457 ResidentBf16Weight::Bf16(_) => fused_door,
5458 };
5459 for matrix in [q_m, k_m, v_m] {
5460 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5461 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5462 return Err("step TP decode v2 QKV geometry mismatch".into());
5463 }
5464 for rank in &matrix.ranks {
5465 if !arm_ok(&rank.weight) {
5466 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5467 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5468 .into());
5469 }
5470 }
5471 }
5472 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5473 for blocks in &o_m.ranks {
5474 for block in blocks {
5475 if !arm_ok(&block.weight) {
5476 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5477 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5478 .into());
5479 }
5480 }
5481 }
5482 if v_m.out_features != k_m.out_features
5483 || o_m.in_features != q_m.out_features
5484 || heads == 0
5485 || heads % ranks != 0
5486 {
5487 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5488 }
5489 let local_q_dim = q_m.out_features / ranks;
5490 let local_kv_dim = k_m.out_features / ranks;
5491 let o_out = o_m.out_features;
5492 let o_block_cols = o_m.canonical_chunk_cols;
5493 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5494 if blocks_per_rank == 0
5495 || o_m
5496 .ranks
5497 .iter()
5498 .any(|blocks| blocks.len() != blocks_per_rank)
5499 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5500 {
5501 return Err("step TP decode v2 O canonical block grid mismatch".into());
5502 }
5503
5504 let mut guard = self
5505 .decode_v2
5506 .lock()
5507 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5508 if let Some(index) = guard.iter().position(|ws| {
5509 ws.local_q_dim == local_q_dim
5510 && ws.local_kv_dim == local_kv_dim
5511 && ws.heads == heads
5512 && ws.o_out == o_out
5513 && ws.o_block_cols == o_block_cols
5514 && ws.blocks_per_rank == blocks_per_rank
5515 && ws.e_device == e.ctx().ordinal()
5516 && ws.q.len() == ranks
5517 }) {
5518 return Ok(index);
5519 }
5520
5521 let mut q_raw = Vec::with_capacity(ranks);
5522 let mut k_raw = Vec::with_capacity(ranks);
5523 let mut v_raw = Vec::with_capacity(ranks);
5524 let mut q = Vec::with_capacity(ranks);
5525 let mut k = Vec::with_capacity(ranks);
5526 let mut pos = Vec::with_capacity(ranks);
5527 let mut gate = Vec::with_capacity(ranks);
5528 let mut attn_out = Vec::with_capacity(ranks);
5529 let mut gated = Vec::with_capacity(ranks);
5530 let mut fuse_ctr = Vec::with_capacity(ranks);
5531 let mut o_partials = Vec::with_capacity(ranks);
5532 let mut ev_rank = Vec::with_capacity(ranks);
5533 let direct_join = oproj_direct_on();
5534 for (rank, engine) in self.ranks.iter().enumerate() {
5535 let _main = engine.gpu.enter_main()?;
5536 q_raw.push(engine.uninit(local_q_dim)?);
5537 k_raw.push(engine.uninit(local_kv_dim)?);
5538 v_raw.push(engine.uninit(local_kv_dim)?);
5539 q.push(engine.uninit(local_q_dim)?);
5540 k.push(engine.uninit(local_kv_dim)?);
5541 pos.push(engine.htod_i32(&[0])?);
5542 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5543 gate.push(engine.uninit(heads / ranks)?);
5544 attn_out.push(engine.uninit(local_q_dim)?);
5545 gated.push(engine.uninit(local_q_dim)?);
5546 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5547 for _ in 0..blocks_per_rank {
5548 if direct_join && rank != 0 {
5551 let root = &self.ranks[0];
5552 let _root_main = root.gpu.enter_main()?;
5553 rank_partials.push(root.uninit(o_out)?);
5554 } else {
5555 rank_partials.push(engine.uninit(o_out)?);
5556 }
5557 }
5558 o_partials.push(rank_partials);
5559 ev_rank.push(engine.ctx().new_event(None)?);
5560 }
5561 let root = &self.ranks[0];
5562 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5563 let _main = root.gpu.enter_main()?;
5564 (
5565 root.uninit(o_out)?,
5566 root.uninit(o_out)?,
5567 root.uninit(o_out)?,
5568 root.htod(&vec![0.0f32; o_out])?,
5569 root.uninit(ranks * local_kv_dim)?,
5570 root.uninit(ranks * local_kv_dim)?,
5571 root.ctx().new_event(None)?,
5572 root.ctx().new_event(None)?,
5573 )
5574 };
5575 let (gate_e, ev_entry) = {
5576 let _main = e.gpu.enter_main()?;
5577 (e.uninit(heads)?, e.ctx().new_event(None)?)
5578 };
5579 let raw_attn_in = Vec::new();
5580 let raw_pos = Vec::new();
5581 guard.push(StepTpDecodeV2Ws {
5582 tcol_q: Vec::new(),
5583 tcol_k: Vec::new(),
5584 tcol_v: Vec::new(),
5585 tcol_g: Vec::new(),
5586 tcol_in: Vec::new(),
5587 tcol_cap: 0,
5588 w8_aq: Vec::new(),
5589 w8_ad: Vec::new(),
5590 w8_in: 0,
5591 w8o_aq: Vec::new(),
5592 w8o_ad: Vec::new(),
5593 w8o_in: 0,
5594 w8t_aq: Vec::new(),
5595 w8t_ad: Vec::new(),
5596 w8t_in: 0,
5597 w8t_oaq: Vec::new(),
5598 w8t_oad: Vec::new(),
5599 w8t_oin: 0,
5600 w8t_cap: 0,
5601 fa2_q: Vec::new(),
5602 fa2_gate: Vec::new(),
5603 fa2_gated: Vec::new(),
5604 fa2_cap: 0,
5605 rope_k_t: Vec::new(),
5606 rope_ctr_t: Vec::new(),
5607 rope_pos_t: Vec::new(),
5608 rows_tabs: Vec::new(),
5609 rows_tab_t: Vec::new(),
5610 rows_tab_shadow: Vec::new(),
5611 tcol_gated: Vec::new(),
5612 tcol_opart: Vec::new(),
5613 tcol_opeer: None,
5614 tcol_omix: None,
5615 tcol_ocap: 0,
5616 q_raw,
5617 k_raw,
5618 v_raw,
5619 q,
5620 k,
5621 pos,
5622 fuse_ctr,
5623 gate,
5624 attn_out,
5625 gated,
5626 o_partials,
5627 ev_rank,
5628 peer_partial,
5629 reduce_a,
5630 reduce_b,
5631 zeros,
5632 k_shadow,
5633 v_shadow,
5634 ev_refresh,
5635 ev_oproj,
5636 gate_e,
5637 attn_in: Vec::new(),
5638 h_stage: None,
5639 pos_stage: None,
5640 raw_h_stage: 0,
5641 raw_pos_stage: 0,
5642 raw_attn_in,
5643 raw_pos,
5644 raw_o_partial1: 0,
5645 raw_peer_partial: 0,
5646 raw_k1: 0,
5647 raw_v1: 0,
5648 raw_k_shadow: 0,
5649 raw_v_shadow: 0,
5650 raw_mixed_stage_e: 0,
5651 raw_reduce_a: 0,
5652 raw_shadow_stage_e: (0, 0),
5653 ev_entry,
5654 e_device: e.ctx().ordinal(),
5655 local_q_dim,
5656 local_kv_dim,
5657 heads,
5658 o_out,
5659 o_block_cols,
5660 blocks_per_rank,
5661 });
5662 eprintln!(
5663 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5664 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5665 residency=persistent ordering=evented performance_claim=false"
5666 );
5667 Ok(guard.len() - 1)
5668 }
5669
5670 #[allow(clippy::too_many_arguments)]
5678 #[allow(clippy::too_many_arguments)]
5683 pub fn decode_v2_input_qkv_tcol(
5684 &self,
5685 ws_index: usize,
5686 e: &Engine,
5687 h_t: &CudaSlice<f32>,
5688 t: usize,
5689 q_m: &ResidentBf16ColumnParallel,
5690 k_m: &ResidentBf16ColumnParallel,
5691 v_m: &ResidentBf16ColumnParallel,
5692 gate_shards: Option<StepTpGateShards<'_>>,
5693 ) -> Result<(), Box<dyn std::error::Error>> {
5694 let ranks = self.ranks.len();
5695 let mut guard = self
5696 .decode_v2
5697 .lock()
5698 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5699 let ws = guard
5700 .get_mut(ws_index)
5701 .ok_or("step TP decode v2 workspace index out of range")?;
5702 let in_f = q_m.in_features;
5703 if h_t.len() < t * in_f || t == 0 || t > 32 {
5704 return Err("decode_v2_input_qkv_tcol geometry".into());
5705 }
5706 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5708 ws.tcol_q.clear();
5709 ws.tcol_k.clear();
5710 ws.tcol_v.clear();
5711 ws.tcol_g.clear();
5712 ws.tcol_in.clear();
5713 for engine in &self.ranks {
5714 let _m = engine.gpu.enter_main()?;
5715 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
5716 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
5717 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
5718 ws.tcol_g
5719 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
5720 ws.tcol_in.push(engine.uninit(32 * in_f)?);
5721 }
5722 ws.tcol_cap = 32;
5723 }
5724 use cudarc::driver::DevicePtr;
5726 let raw_src = {
5727 let _main = e.gpu.enter_main()?;
5728 let stream = e.stream();
5729 let (p, _g) = h_t.device_ptr(&stream);
5730 ws.ev_entry.record(&stream)?;
5731 p as u64
5732 };
5733 for rank in 0..ranks {
5734 let engine = &self.ranks[rank];
5735 let _main = engine.gpu.enter_main()?;
5736 engine.stream().wait(&ws.ev_entry)?;
5737 let raw_dst = {
5738 let stream = engine.stream();
5739 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5740 p as u64
5741 };
5742 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5743 let out_g = match &gate_shards {
5744 Some(_) => ws.heads / ranks,
5745 None => 0,
5746 };
5747 match (
5748 &q_m.ranks[rank].weight,
5749 &k_m.ranks[rank].weight,
5750 &v_m.ranks[rank].weight,
5751 ) {
5752 (
5753 ResidentBf16Weight::Bf16(wq),
5754 ResidentBf16Weight::Bf16(wk),
5755 ResidentBf16Weight::Bf16(wv),
5756 ) => {
5757 let wg = match &gate_shards {
5758 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5759 Some(StepTpGateShards::F32(_)) => {
5760 return Err(
5761 "tcol verify: gate shard class does not match bf16 QKV".into()
5762 );
5763 }
5764 None => wq,
5765 };
5766 let StepTpDecodeV2Ws {
5767 tcol_q,
5768 tcol_k,
5769 tcol_v,
5770 tcol_g,
5771 tcol_in,
5772 local_q_dim,
5773 local_kv_dim,
5774 w8t_aq,
5775 w8t_ad,
5776 w8t_in,
5777 w8t_cap,
5778 ..
5779 } = &mut *ws;
5780 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5783 let refk = *REFK
5784 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5785 if refk {
5786 let lq = *local_q_dim;
5787 let lkv = *local_kv_dim;
5788 let mut hrow = engine.uninit(in_f)?;
5789 let mut qr = engine.uninit(lq)?;
5790 let mut kr = engine.uninit(lkv)?;
5791 let mut vr = engine.uninit(lkv)?;
5792 let mut gr = engine.uninit(out_g.max(1))?;
5793 for c in 0..t {
5794 {
5795 let mut dst = hrow.slice_mut(0..in_f);
5796 engine.stream().memcpy_dtod(
5797 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5798 &mut dst,
5799 )?;
5800 }
5801 engine.matvec_bf16_qkvg_into(
5802 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5803 lq, lkv, out_g,
5804 )?;
5805 let stream = engine.stream();
5806 {
5807 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5808 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5809 }
5810 {
5811 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5812 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5813 }
5814 {
5815 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5816 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5817 }
5818 if out_g > 0 {
5819 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5820 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5821 }
5822 }
5823 } else if crate::step_tp_w8_on()
5824 && q_m.ranks[rank].q8.is_some()
5825 && k_m.ranks[rank].q8.is_some()
5826 && v_m.ranks[rank].q8.is_some()
5827 && in_f % 32 == 0
5828 {
5829 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
5835 w8t_aq.clear();
5836 w8t_ad.clear();
5837 for e_rank in &self.ranks {
5838 let _m = e_rank.gpu.enter_main()?;
5839 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
5840 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
5841 }
5842 *w8t_in = in_f;
5843 *w8t_cap = 32;
5844 }
5845 engine.quantize_q8_1_into(
5846 &tcol_in[rank],
5847 t,
5848 in_f,
5849 &mut w8t_aq[rank],
5850 &mut w8t_ad[rank],
5851 )?;
5852 engine.qmatvec_q8_0_qkv_rp_t_into(
5853 q_m.ranks[rank].q8.as_ref().unwrap(),
5854 k_m.ranks[rank].q8.as_ref().unwrap(),
5855 v_m.ranks[rank].q8.as_ref().unwrap(),
5856 &w8t_aq[rank],
5857 &w8t_ad[rank],
5858 &mut tcol_q[rank],
5859 &mut tcol_k[rank],
5860 &mut tcol_v[rank],
5861 in_f,
5862 *local_q_dim,
5863 *local_kv_dim,
5864 t,
5865 )?;
5866 if out_g > 0 {
5867 engine.matvec_bf16_rows_into(
5868 wg,
5869 &tcol_in[rank],
5870 &mut tcol_g[rank],
5871 in_f,
5872 out_g,
5873 t,
5874 )?;
5875 }
5876 } else {
5877 engine.matvec_bf16_qkvg_tcol_into(
5878 wq,
5879 wk,
5880 wv,
5881 wg,
5882 &tcol_in[rank],
5883 &mut tcol_q[rank],
5884 &mut tcol_k[rank],
5885 &mut tcol_v[rank],
5886 &mut tcol_g[rank],
5887 in_f,
5888 *local_q_dim,
5889 *local_kv_dim,
5890 out_g,
5891 t,
5892 )?;
5893 }
5894 }
5895 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5896 }
5897 }
5898 Ok(())
5899 }
5900
5901 pub(crate) fn decode_v2_oproj_tcol_eligible(
5905 &self,
5906 ws: &StepTpDecodeV2Ws,
5907 o_m: &ResidentStepBf16RowParallel,
5908 ) -> bool {
5909 self.ranks.len() == 2
5910 && ws.blocks_per_rank == 4
5911 && step_tp_qkv_fused_enabled().unwrap_or(false)
5912 && no_local_shadow_on()
5913 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5914 && o_m
5915 .ranks
5916 .iter()
5917 .flatten()
5918 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5919 }
5920
5921 pub(crate) fn decode_v2_stash_fa2(
5926 &self,
5927 ws: &mut StepTpDecodeV2Ws,
5928 e: &Engine,
5929 col: usize,
5930 ) -> Result<(), Box<dyn std::error::Error>> {
5931 let ranks = self.ranks.len();
5932 if col >= 32 {
5933 return Err("decode_v2_stash_fa2 column out of range".into());
5934 }
5935 let lq = ws.local_q_dim;
5936 let lg = (ws.heads / ranks).max(1);
5937 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
5938 ws.fa2_q.clear();
5939 ws.fa2_gate.clear();
5940 ws.fa2_gated.clear();
5941 ws.rope_k_t.clear();
5942 ws.rope_ctr_t.clear();
5943 ws.rope_pos_t.clear();
5944 ws.rows_tab_t.clear();
5945 for engine in &self.ranks {
5946 let _m = engine.gpu.enter_main()?;
5947 ws.fa2_q.push(engine.uninit(32 * lq)?);
5948 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5949 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5950 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
5951 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5952 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5953 ws.rows_tab_t
5954 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
5955 }
5956 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5957 ws.fa2_cap = 32;
5958 }
5959 for rank in 0..ranks {
5960 let engine = &self.ranks[rank];
5961 let _main = engine.gpu.enter_main()?;
5962 {
5963 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5964 engine
5965 .stream()
5966 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5967 }
5968 {
5969 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5970 engine
5971 .stream()
5972 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5973 }
5974 ws.ev_rank[rank].record(&engine.stream())?;
5975 }
5976 {
5977 let _main = e.gpu.enter_main()?;
5978 for ev in ws.ev_rank.iter() {
5979 e.stream().wait(ev)?;
5980 }
5981 }
5982 Ok(())
5983 }
5984
5985 #[allow(clippy::too_many_arguments)]
5992 pub(crate) fn decode_v2_spec_fa2_join(
5993 &self,
5994 ws_index: usize,
5995 e: &Engine,
5996 o_m: &ResidentStepBf16RowParallel,
5997 kv: &ResidentTpKvCache,
5998 head_dim: usize,
5999 window: usize,
6000 bucket_max: usize,
6001 scale: f32,
6002 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6003 let ranks = self.ranks.len();
6004 static ONCE: std::sync::Once = std::sync::Once::new();
6006 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
6007 {
6008 let mut guard = self
6009 .decode_v2
6010 .lock()
6011 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6012 let ws = guard
6013 .get_mut(ws_index)
6014 .ok_or("step TP decode v2 workspace index out of range")?;
6015 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6016 return Err("spec fa2 join without stashed columns".into());
6017 }
6018 let lq = ws.local_q_dim;
6019 let local_heads = (ws.heads / ranks).max(1);
6020 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6021 let capacity = kv.physical_capacity();
6022 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6023 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6025 ws.tcol_gated.clear();
6026 ws.tcol_opart.clear();
6027 for engine in &self.ranks {
6028 let _m = engine.gpu.enter_main()?;
6029 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6030 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6031 }
6032 let root = &self.ranks[0];
6033 let _m = root.gpu.enter_main()?;
6034 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6035 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6036 ws.tcol_ocap = 32;
6037 }
6038 for rank in 0..ranks {
6039 let engine = &self.ranks[rank];
6040 let _main = engine.gpu.enter_main()?;
6041 let rank_cache = kv
6042 .rank(rank)
6043 .ok_or("spec fa2 join lost its KV cache rank")?;
6044 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6045 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6046 {
6047 let StepTpDecodeV2Ws {
6048 fa2_q,
6049 fa2_gate,
6050 fa2_gated,
6051 ..
6052 } = &mut *ws;
6053 engine.fa_decode_dcw2(
6054 &fa2_q[rank],
6055 &k_ring,
6056 &v_ring,
6057 &mut fa2_gated[rank],
6058 head_dim,
6059 local_heads,
6060 local_kv_heads,
6061 rank_cache.len_d(),
6062 rank_cache.base_d(),
6063 window,
6064 bucket_max,
6065 scale,
6066 k_tok_bytes,
6067 v_tok_bytes,
6068 &fa2_gate[rank],
6069 )?;
6070 }
6071 let StepTpDecodeV2Ws {
6074 fa2_gated,
6075 tcol_gated,
6076 ..
6077 } = &mut *ws;
6078 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6079 engine
6080 .stream()
6081 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6082 }
6083 }
6084 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6085 }
6086
6087 #[allow(clippy::too_many_arguments)]
6097 pub(crate) fn decode_v2_rope_fa_rows(
6098 &self,
6099 ws_index: usize,
6100 e: &Engine,
6101 o_m: &ResidentStepBf16RowParallel,
6102 session_parts: &[Vec<[u64; 4]>],
6103 tab_keys: &[u64],
6104 positions: &[i32],
6105 stage_pos: bool,
6106 same_session: bool,
6107 q_norms: &[CudaSlice<f32>],
6108 k_norms: &[CudaSlice<f32>],
6109 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6110 t: usize,
6111 head_dim: usize,
6112 n_rot: usize,
6113 window: usize,
6114 max_ns: usize,
6115 scale: f32,
6116 k_tok_bytes: usize,
6117 v_tok_bytes: usize,
6118 eps: f32,
6119 rope_base: f32,
6120 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6121 use cudarc::driver::DevicePtr;
6122 let ranks = self.ranks.len();
6123 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6124 return Err("rope fa rows geometry".into());
6125 }
6126 {
6127 let mut guard = self
6128 .decode_v2
6129 .lock()
6130 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6131 let ws = guard
6132 .get_mut(ws_index)
6133 .ok_or("step TP decode v2 workspace index out of range")?;
6134 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6135 return Err("rope fa rows without tcol slabs".into());
6136 }
6137 let lq = ws.local_q_dim;
6138 let lkv = ws.local_kv_dim;
6139 let lg = (ws.heads / ranks).max(1);
6140 let local_heads = (ws.heads / ranks).max(1);
6141 let local_kv_heads = (lkv / head_dim).max(1);
6142 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6144 ws.fa2_q.clear();
6145 ws.fa2_gate.clear();
6146 ws.fa2_gated.clear();
6147 ws.rope_k_t.clear();
6148 ws.rope_ctr_t.clear();
6149 ws.rope_pos_t.clear();
6150 ws.rows_tab_t.clear();
6151 for engine in &self.ranks {
6152 let _m = engine.gpu.enter_main()?;
6153 ws.fa2_q.push(engine.uninit(32 * lq)?);
6154 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6155 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6156 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6157 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6158 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6159 ws.rows_tab_t
6160 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6161 }
6162 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6163 ws.fa2_cap = 32;
6164 }
6165 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6166 ws.tcol_gated.clear();
6167 ws.tcol_opart.clear();
6168 for engine in &self.ranks {
6169 let _m = engine.gpu.enter_main()?;
6170 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6171 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6172 }
6173 let root = &self.ranks[0];
6174 let _m = root.gpu.enter_main()?;
6175 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6176 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6177 ws.tcol_ocap = 32;
6178 }
6179 for rank in 0..ranks {
6180 let engine = &self.ranks[rank];
6181 let _main = engine.gpu.enter_main()?;
6182 if stage_pos {
6183 let host: Vec<i32> = positions[..t].to_vec();
6184 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6185 engine.stream().memcpy_htod(&host, &mut view)?;
6186 }
6187 let ctr_base = {
6206 let s = engine.stream();
6207 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6208 p as u64
6209 };
6210 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6211 if rows_tab_stale_scan() {
6217 if ws.rows_tab_shadow.len() != ranks {
6218 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6219 }
6220 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6221 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank]) {
6222 if prev != &host {
6223 let words = ["k", "v", "len", "base", "ctr", "back"];
6224 let moved: Vec<String> = (0..host.len())
6225 .filter(|&i| prev.get(i) != Some(&host[i]))
6226 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6227 .collect();
6228 let stale =
6229 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6230 eprintln!(
6231 "[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",
6232 tab_keys[rank],
6233 moved.join(",")
6234 );
6235 }
6236 }
6237 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
6238 }
6239 let legacy_memo = !rows_tab_restage_on();
6240 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6241 let tab = engine.stream().clone_htod(&host)?;
6242 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6243 }
6244 if !legacy_memo {
6245 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
6246 engine.stream().memcpy_htod(&host, &mut view)?;
6247 }
6248 let StepTpDecodeV2Ws {
6249 tcol_q,
6250 tcol_k,
6251 tcol_v,
6252 tcol_g,
6253 fa2_q,
6254 fa2_gated,
6255 rope_k_t,
6256 rope_pos_t,
6257 rows_tabs,
6258 rows_tab_t,
6259 ..
6260 } = &mut *ws;
6261 let tab = if legacy_memo {
6262 rows_tabs[rank]
6263 .get(&tab_keys[rank])
6264 .ok_or("rows tab memo lost its entry")?
6265 } else {
6266 &rows_tab_t[rank]
6267 };
6268 engine.qk_norm_rope_append_inc_dcw_rows(
6269 &tcol_q[rank],
6270 &tcol_k[rank],
6271 &tcol_v[rank],
6272 &q_norms[rank],
6273 &k_norms[rank],
6274 &mut fa2_q[rank],
6275 &mut rope_k_t[rank],
6276 tab,
6277 &rope_pos_t[rank],
6278 same_session,
6279 t,
6280 lkv,
6281 lkv,
6282 k_tok_bytes,
6283 v_tok_bytes,
6284 head_dim,
6285 n_rot,
6286 local_heads,
6287 local_kv_heads,
6288 eps,
6289 rope_base,
6290 1.0,
6291 rope_freqs[rank],
6292 )?;
6293 engine.fa_decode_dcw_rows(
6294 &fa2_q[rank],
6295 tab,
6296 &mut fa2_gated[rank],
6297 t,
6298 head_dim,
6299 local_heads,
6300 local_kv_heads,
6301 window,
6302 max_ns,
6303 scale,
6304 k_tok_bytes,
6305 v_tok_bytes,
6306 &tcol_g[rank],
6307 )?;
6308 let StepTpDecodeV2Ws {
6309 fa2_gated,
6310 tcol_gated,
6311 ..
6312 } = &mut *ws;
6313 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6314 engine
6315 .stream()
6316 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6317 }
6318 }
6319 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6320 }
6321
6322 #[allow(clippy::too_many_arguments)]
6329 pub(crate) fn decode_v2_fa_rows_join(
6330 &self,
6331 ws_index: usize,
6332 e: &Engine,
6333 o_m: &ResidentStepBf16RowParallel,
6334 tabs: &[&crate::CudaSlice<u64>],
6335 t: usize,
6336 head_dim: usize,
6337 window: usize,
6338 max_ns: usize,
6339 scale: f32,
6340 k_tok_bytes: usize,
6341 v_tok_bytes: usize,
6342 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6343 let ranks = self.ranks.len();
6344 if tabs.len() != ranks {
6345 return Err("fa rows join needs one table per rank".into());
6346 }
6347 {
6348 let mut guard = self
6349 .decode_v2
6350 .lock()
6351 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6352 let ws = guard
6353 .get_mut(ws_index)
6354 .ok_or("step TP decode v2 workspace index out of range")?;
6355 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6356 return Err("fa rows join without stashed rows".into());
6357 }
6358 let lq = ws.local_q_dim;
6359 let local_heads = (ws.heads / ranks).max(1);
6360 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6361 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6362 ws.tcol_gated.clear();
6363 ws.tcol_opart.clear();
6364 for engine in &self.ranks {
6365 let _m = engine.gpu.enter_main()?;
6366 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6367 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6368 }
6369 let root = &self.ranks[0];
6370 let _m = root.gpu.enter_main()?;
6371 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6372 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6373 ws.tcol_ocap = 32;
6374 }
6375 for rank in 0..ranks {
6376 let engine = &self.ranks[rank];
6377 let _main = engine.gpu.enter_main()?;
6378 {
6379 let StepTpDecodeV2Ws {
6380 fa2_q,
6381 fa2_gate,
6382 fa2_gated,
6383 ..
6384 } = &mut *ws;
6385 engine.fa_decode_dcw_rows(
6386 &fa2_q[rank],
6387 tabs[rank],
6388 &mut fa2_gated[rank],
6389 t,
6390 head_dim,
6391 local_heads,
6392 local_kv_heads,
6393 window,
6394 max_ns,
6395 scale,
6396 k_tok_bytes,
6397 v_tok_bytes,
6398 &fa2_gate[rank],
6399 )?;
6400 }
6401 let StepTpDecodeV2Ws {
6402 fa2_gated,
6403 tcol_gated,
6404 ..
6405 } = &mut *ws;
6406 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6407 engine
6408 .stream()
6409 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6410 }
6411 }
6412 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6413 }
6414
6415 pub(crate) fn decode_v2_stash_gated(
6420 &self,
6421 ws: &mut StepTpDecodeV2Ws,
6422 e: &Engine,
6423 col: usize,
6424 ) -> Result<(), Box<dyn std::error::Error>> {
6425 let ranks = self.ranks.len();
6426 if col >= 32 {
6430 return Err("decode_v2_stash_gated column out of range".into());
6431 }
6432 let lq = ws.local_q_dim;
6433 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6434 ws.tcol_gated.clear();
6435 ws.tcol_opart.clear();
6436 for engine in &self.ranks {
6437 let _m = engine.gpu.enter_main()?;
6438 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6439 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6440 }
6441 let root = &self.ranks[0];
6442 let _m = root.gpu.enter_main()?;
6443 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6444 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6445 ws.tcol_ocap = 32;
6446 }
6447 for rank in 0..ranks {
6448 let engine = &self.ranks[rank];
6449 let _main = engine.gpu.enter_main()?;
6450 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6451 engine
6452 .stream()
6453 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6454 ws.ev_rank[rank].record(&engine.stream())?;
6458 }
6459 {
6460 let _main = e.gpu.enter_main()?;
6461 for ev in ws.ev_rank.iter() {
6462 e.stream().wait(ev)?;
6463 }
6464 }
6465 Ok(())
6466 }
6467
6468 pub(crate) fn decode_v2_oproj_tcol(
6474 &self,
6475 ws_index: usize,
6476 e: &Engine,
6477 o_m: &ResidentStepBf16RowParallel,
6478 t: usize,
6479 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6480 let ranks = self.ranks.len();
6481 let mut guard = self
6482 .decode_v2
6483 .lock()
6484 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6485 let ws = guard
6486 .get_mut(ws_index)
6487 .ok_or("step TP decode v2 workspace index out of range")?;
6488 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6489 return Err("decode_v2_oproj_tcol geometry".into());
6490 }
6491 for rank in 0..ranks {
6492 let engine = &self.ranks[rank];
6493 let _main = engine.gpu.enter_main()?;
6494 let mut weights = Vec::with_capacity(4);
6495 for block in 0..4 {
6496 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6497 return Err("tcol o_proj requires bf16-resident O blocks".into());
6498 };
6499 weights.push(weight);
6500 }
6501 {
6502 let StepTpDecodeV2Ws {
6503 tcol_gated,
6504 tcol_opart,
6505 local_q_dim,
6506 o_block_cols,
6507 o_out,
6508 w8t_oaq,
6509 w8t_oad,
6510 w8t_oin,
6511 w8t_cap,
6512 ..
6513 } = &mut *ws;
6514 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6517 let refk = *REFK
6518 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6519 if refk {
6520 let lq = *local_q_dim;
6521 let mut xr = engine.uninit(lq)?;
6522 let mut yr = engine.uninit(*o_out)?;
6523 for c in 0..t {
6524 {
6525 let mut dst = xr.slice_mut(0..lq);
6526 engine.stream().memcpy_dtod(
6527 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6528 &mut dst,
6529 )?;
6530 }
6531 engine.matvec_bf16_b4_into(
6532 [weights[0], weights[1], weights[2], weights[3]],
6533 &xr,
6534 &mut yr,
6535 *o_block_cols,
6536 *o_out,
6537 )?;
6538 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6539 engine
6540 .stream()
6541 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6542 }
6543 } else if crate::step_tp_w8_on()
6544 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
6545 && (4 * *o_block_cols) % 32 == 0
6546 {
6547 let in_f = 4 * *o_block_cols;
6551 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
6552 w8t_oaq.clear();
6553 w8t_oad.clear();
6554 for e_rank in &self.ranks {
6555 let _m = e_rank.gpu.enter_main()?;
6556 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6557 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6558 }
6559 *w8t_oin = in_f;
6560 *w8t_cap = (*w8t_cap).max(32);
6561 }
6562 engine.quantize_q8_1_into(
6563 &tcol_gated[rank],
6564 t,
6565 in_f,
6566 &mut w8t_oaq[rank],
6567 &mut w8t_oad[rank],
6568 )?;
6569 engine.qmatvec_q8_0_b4_rp_t_into(
6570 [
6571 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6572 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6573 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6574 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6575 ],
6576 &w8t_oaq[rank],
6577 &w8t_oad[rank],
6578 &mut tcol_opart[rank],
6579 *o_block_cols,
6580 *o_out,
6581 t,
6582 )?;
6583 } else {
6584 engine.matvec_bf16_b4_tcol_into(
6585 [weights[0], weights[1], weights[2], weights[3]],
6586 &tcol_gated[rank],
6587 &mut tcol_opart[rank],
6588 *o_block_cols,
6589 *o_out,
6590 t,
6591 )?;
6592 }
6593 }
6594 if rank != 0 {
6595 ws.ev_rank[rank].record(&engine.stream())?;
6596 }
6597 }
6598 let root = &self.ranks[0];
6599 {
6600 let _main = root.gpu.enter_main()?;
6601 for ev in ws.ev_rank.iter().skip(1) {
6602 root.stream().wait(ev)?;
6603 }
6604 {
6605 let StepTpDecodeV2Ws {
6606 tcol_opart,
6607 tcol_opeer,
6608 tcol_omix,
6609 o_out,
6610 ..
6611 } = &mut *ws;
6612 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6613 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6614 {
6615 let mut dst = opeer.slice_mut(0..t * *o_out);
6616 root.stream()
6617 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6618 }
6619 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6622 }
6623 ws.ev_oproj.record(&root.stream())?;
6624 }
6625 let _main = e.gpu.enter_main()?;
6626 e.stream().wait(&ws.ev_oproj)?;
6627 let mut out = e.uninit(t * ws.o_out)?;
6628 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6629 e.stream().memcpy_dtod(
6630 &omix.slice(0..t * ws.o_out),
6631 &mut out.slice_mut(0..t * ws.o_out),
6632 )?;
6633 Ok(out)
6634 }
6635
6636 pub(crate) fn decode_v2_input_qkv(
6637 &self,
6638 ws: &mut StepTpDecodeV2Ws,
6639 e: &Engine,
6640 h: &CudaSlice<f32>,
6641 pos_d: &CudaSlice<i32>,
6642 gate_raw: Option<&CudaSlice<f32>>,
6643 gate_shards: Option<StepTpGateShards<'_>>,
6644 decode_input: &mut ResidentReplicatedDeviceRows,
6645 q_m: &ResidentBf16ColumnParallel,
6646 k_m: &ResidentBf16ColumnParallel,
6647 v_m: &ResidentBf16ColumnParallel,
6648 q_norm: &[CudaSlice<f32>],
6649 k_norm: &[CudaSlice<f32>],
6650 head_dim: usize,
6651 n_rot: usize,
6652 rope_base: f32,
6653 rope_freqs: &[Option<&CudaSlice<f32>>],
6654 rms_eps: f32,
6655 defer_norm_rope: bool,
6656 tcol_col: Option<usize>,
6657 ) -> Result<(), Box<dyn std::error::Error>> {
6658 let ranks = self.ranks.len();
6659 validate_replicated_device_rows(&self.ranks, decode_input)?;
6660 if decode_input.tokens != 1
6661 || decode_input.width != q_m.in_features
6662 || pos_d.len() != 1
6663 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6664 || gate_raw.is_none() != gate_shards.is_some()
6665 || gate_shards.as_ref().is_some_and(|shards| match shards {
6666 StepTpGateShards::F32(shards) => shards.len() != ranks,
6667 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6668 })
6669 || q_norm.len() != ranks
6670 || k_norm.len() != ranks
6671 || rope_freqs.len() != ranks
6672 || e.ctx().ordinal() != ws.e_device
6673 {
6674 return Err("step TP decode v2 input geometry mismatch".into());
6675 }
6676
6677 let qkv_fused = step_tp_qkv_fused_enabled()?;
6678 if gate_shards.is_some() && !qkv_fused {
6679 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6680 }
6681 let values = decode_input.width;
6682 if h.len() != values {
6683 return Err(format!(
6684 "step TP decode v2 hidden width {} != replicated width {values}",
6685 h.len()
6686 )
6687 .into());
6688 }
6689
6690 if qkv_fused {
6691 if ws.h_stage.is_none() {
6695 use cudarc::driver::DevicePtr;
6696 let _main = e.gpu.enter_main()?;
6697 let h_stage = e.uninit(values)?;
6698 let pos_stage = e.htod_i32(&[0])?;
6699 {
6700 let stream = e.stream();
6701 let (hp, _g0) = h_stage.device_ptr(&stream);
6702 let (pp, _g1) = pos_stage.device_ptr(&stream);
6703 ws.raw_h_stage = hp as u64;
6704 ws.raw_pos_stage = pp as u64;
6705 }
6706 ws.h_stage = Some(h_stage);
6707 ws.pos_stage = Some(pos_stage);
6708 for rank in 0..ranks {
6709 use cudarc::driver::DevicePtr;
6710 let engine = &self.ranks[rank];
6711 let _rmain = engine.gpu.enter_main()?;
6712 let attn_in = engine.uninit(values)?;
6713 let (dp, pp) = {
6714 let stream = engine.stream();
6715 let (dp, _g2) = attn_in.device_ptr(&stream);
6716 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6717 (dp as u64, pp as u64)
6718 };
6719 ws.raw_attn_in.push(dp);
6720 ws.raw_pos.push(pp);
6721 ws.attn_in.push(attn_in);
6722 }
6723 {
6724 use cudarc::driver::DevicePtr;
6725 let root = &self.ranks[0];
6726 let _rmain = root.gpu.enter_main()?;
6727 let stream = root.stream();
6728 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6729 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6730 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6731 ws.raw_peer_partial = a as u64;
6732 ws.raw_k_shadow = b as u64;
6733 ws.raw_v_shadow = c as u64;
6734 }
6735 {
6736 use cudarc::driver::DevicePtr;
6737 let rank1 = &self.ranks[1];
6738 let _rmain = rank1.gpu.enter_main()?;
6739 let stream = rank1.stream();
6740 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6741 let (b, _g) = ws.k[1].device_ptr(&stream);
6742 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6743 ws.raw_o_partial1 = a as u64;
6744 ws.raw_k1 = b as u64;
6745 ws.raw_v1 = c as u64;
6746 }
6747 }
6748 {
6749 let _main = e.gpu.enter_main()?;
6750 {
6751 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6754 let mut dst = h_stage.slice_mut(0..values);
6755 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6756 }
6757 {
6758 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6759 let mut dst = pos_stage.slice_mut(0..1);
6760 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6761 }
6762 ws.ev_entry.record(&e.stream())?;
6763 }
6764 for rank in 0..ranks {
6765 let engine = &self.ranks[rank];
6766 let _main = engine.gpu.enter_main()?;
6767 engine.stream().wait(&ws.ev_entry)?;
6768 }
6769 } else {
6770 {
6772 let _main = e.gpu.enter_main()?;
6773 if let Some(gate_raw) = gate_raw {
6774 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6775 e.stream()
6776 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6777 }
6778 ws.ev_entry.record(&e.stream())?;
6779 }
6780 {
6781 let root = &self.ranks[0];
6782 let _main = root.gpu.enter_main()?;
6783 root.stream().wait(&ws.ev_entry)?;
6784 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6785 root.stream()
6786 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6787 ws.ev_refresh.record(&root.stream())?;
6788 }
6789 for rank in 1..ranks {
6790 let engine = &self.ranks[rank];
6791 let _main = engine.gpu.enter_main()?;
6792 engine.stream().wait(&ws.ev_refresh)?;
6793 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6794 let mut destination = peer_rows[0].slice_mut(0..values);
6795 engine
6796 .stream()
6797 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6798 }
6799 }
6800 for rank in 0..ranks {
6801 self.decode_v2_input_qkv_rank(
6802 ws,
6803 pos_d,
6804 decode_input,
6805 q_m,
6806 k_m,
6807 v_m,
6808 q_norm,
6809 k_norm,
6810 head_dim,
6811 n_rot,
6812 rope_base,
6813 rope_freqs,
6814 rms_eps,
6815 gate_shards.as_ref(),
6816 qkv_fused,
6817 defer_norm_rope,
6818 rank,
6819 tcol_col,
6820 )?;
6821 }
6822 Ok(())
6823 }
6824
6825 #[allow(clippy::too_many_arguments)]
6828 pub(crate) fn decode_v2_input_qkv_rank(
6829 &self,
6830 ws: &mut StepTpDecodeV2Ws,
6831 pos_d: &CudaSlice<i32>,
6832 decode_input: &mut ResidentReplicatedDeviceRows,
6833 q_m: &ResidentBf16ColumnParallel,
6834 k_m: &ResidentBf16ColumnParallel,
6835 v_m: &ResidentBf16ColumnParallel,
6836 q_norm: &[CudaSlice<f32>],
6837 k_norm: &[CudaSlice<f32>],
6838 head_dim: usize,
6839 n_rot: usize,
6840 rope_base: f32,
6841 rope_freqs: &[Option<&CudaSlice<f32>>],
6842 rms_eps: f32,
6843 gate_shards: Option<&StepTpGateShards<'_>>,
6844 qkv_fused: bool,
6845 defer_norm_rope: bool,
6846 rank: usize,
6847 tcol_col: Option<usize>,
6848 ) -> Result<(), Box<dyn std::error::Error>> {
6849 let ranks = self.ranks.len();
6850 let local_heads = ws.local_q_dim / head_dim;
6851 let local_kv_heads = ws.local_kv_dim / head_dim;
6852 let engine = &self.ranks[rank];
6853 let _main = engine.gpu.enter_main()?;
6854 let ws_e_device = ws.e_device;
6855 if qkv_fused && tcol_col.is_some() {
6860 let c = tcol_col.expect("checked");
6861 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6862 return Err("tcol select without precompute".into());
6863 }
6864 if engine.ctx().ordinal() != ws_e_device {
6868 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6869 }
6870 let StepTpDecodeV2Ws {
6871 tcol_q,
6872 tcol_k,
6873 tcol_v,
6874 tcol_g,
6875 q_raw,
6876 k_raw,
6877 v_raw,
6878 gate,
6879 local_q_dim,
6880 local_kv_dim,
6881 heads,
6882 ..
6883 } = &mut *ws;
6884 let lg = *heads / ranks;
6885 let stream = engine.stream();
6886 {
6887 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6888 stream.memcpy_dtod(
6889 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6890 &mut dst,
6891 )?;
6892 }
6893 {
6894 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6895 stream.memcpy_dtod(
6896 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6897 &mut dst,
6898 )?;
6899 }
6900 {
6901 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6902 stream.memcpy_dtod(
6903 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6904 &mut dst,
6905 )?;
6906 }
6907 if lg > 0 {
6908 let mut dst = gate[rank].slice_mut(0..lg);
6909 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6910 }
6911 if !defer_norm_rope {
6912 } else {
6916 return Ok(());
6917 }
6918 }
6919 if qkv_fused {
6920 let same_dev = engine.ctx().ordinal() == ws.e_device;
6925 if !same_dev {
6926 raw_copy_bytes(
6927 ws.raw_attn_in[rank],
6928 ws.raw_h_stage,
6929 q_m.in_features * 4,
6930 engine,
6931 )?;
6932 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6933 }
6934 let StepTpDecodeV2Ws {
6935 q_raw,
6936 k_raw,
6937 v_raw,
6938 gate,
6939 gate_e,
6940 attn_in,
6941 h_stage,
6942 heads,
6943 local_q_dim,
6944 local_kv_dim,
6945 w8_aq,
6946 w8_ad,
6947 w8_in,
6948 ..
6949 } = &mut *ws;
6950 let input_ref: &CudaSlice<f32> = if same_dev {
6951 h_stage
6952 .as_ref()
6953 .ok_or("step TP decode v2 stage not armed")?
6954 } else {
6955 &attn_in[rank]
6956 };
6957 match (
6958 &q_m.ranks[rank].weight,
6959 &k_m.ranks[rank].weight,
6960 &v_m.ranks[rank].weight,
6961 ) {
6962 (
6963 ResidentBf16Weight::F32(wq),
6964 ResidentBf16Weight::F32(wk),
6965 ResidentBf16Weight::F32(wv),
6966 ) => {
6967 let (wg, out_g) = match &gate_shards {
6968 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6969 Some(StepTpGateShards::Bf16(_)) => {
6970 return Err("step TP decode v2 gate shard class does not \
6971 match the F32 projections"
6972 .into());
6973 }
6974 None => (&*gate_e, 0),
6976 };
6977 engine.matvec_f32_qkv_into(
6978 wq,
6979 wk,
6980 wv,
6981 wg,
6982 input_ref,
6983 &mut q_raw[rank],
6984 &mut k_raw[rank],
6985 &mut v_raw[rank],
6986 &mut gate[rank],
6987 q_m.in_features,
6988 *local_q_dim,
6989 *local_kv_dim,
6990 out_g,
6991 )?;
6992 }
6993 (
6994 ResidentBf16Weight::Bf16(wq),
6995 ResidentBf16Weight::Bf16(wk),
6996 ResidentBf16Weight::Bf16(wv),
6997 ) => {
6998 let (wg, out_g) = match &gate_shards {
6999 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
7000 Some(StepTpGateShards::F32(_)) => {
7001 return Err("step TP decode v2 gate shard class does not \
7002 match the bf16 projections"
7003 .into());
7004 }
7005 None => (wq, 0),
7006 };
7007 let in_f = q_m.in_features;
7014 let q8_ready = crate::step_tp_w8_on()
7015 && q_m.ranks[rank].q8.is_some()
7016 && k_m.ranks[rank].q8.is_some()
7017 && v_m.ranks[rank].q8.is_some();
7018 if q8_ready {
7019 if *w8_in != in_f || w8_aq.len() != ranks {
7020 w8_aq.clear();
7021 w8_ad.clear();
7022 for e_rank in &self.ranks {
7023 let _m = e_rank.gpu.enter_main()?;
7024 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7025 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7026 }
7027 *w8_in = in_f;
7028 }
7029 engine.quantize_q8_1_into(
7030 input_ref,
7031 1,
7032 in_f,
7033 &mut w8_aq[rank],
7034 &mut w8_ad[rank],
7035 )?;
7036 engine.qmatvec_q8_0_qkv_rp_into(
7041 q_m.ranks[rank].q8.as_ref().unwrap(),
7042 k_m.ranks[rank].q8.as_ref().unwrap(),
7043 v_m.ranks[rank].q8.as_ref().unwrap(),
7044 &w8_aq[rank],
7045 &w8_ad[rank],
7046 &mut q_raw[rank],
7047 &mut k_raw[rank],
7048 &mut v_raw[rank],
7049 in_f,
7050 *local_q_dim,
7051 *local_kv_dim,
7052 )?;
7053 if out_g > 0 {
7054 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7055 }
7056 } else {
7057 engine.matvec_bf16_qkvg_into(
7058 wq,
7059 wk,
7060 wv,
7061 wg,
7062 input_ref,
7063 &mut q_raw[rank],
7064 &mut k_raw[rank],
7065 &mut v_raw[rank],
7066 &mut gate[rank],
7067 q_m.in_features,
7068 *local_q_dim,
7069 *local_kv_dim,
7070 out_g,
7071 )?;
7072 }
7073 }
7074 _ => {
7075 return Err("step TP decode v2 QKV projections mix residency classes".into());
7076 }
7077 }
7078 } else {
7079 for (matrix, local_out, raw) in [
7080 (q_m, ws.local_q_dim, &mut ws.q_raw),
7081 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7082 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7083 ] {
7084 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7085 return Err("step TP decode v2 lost its F32 projection residency".into());
7086 };
7087 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7088 engine.linear_f32_resident_canonical_rows_t1_into(
7089 &decode_input.ranks[rank],
7090 values_w,
7091 &mut raw[rank],
7092 matrix.in_features,
7093 local_out,
7094 chunk_rows,
7095 )?;
7096 }
7097 }
7098 if qkv_fused && defer_norm_rope {
7099 } else if qkv_fused {
7101 let StepTpDecodeV2Ws {
7104 q_raw,
7105 k_raw,
7106 q,
7107 k,
7108 pos,
7109 pos_stage,
7110 ..
7111 } = &mut *ws;
7112 let same_dev = engine.ctx().ordinal() == ws_e_device;
7113 let pos_ref: &CudaSlice<i32> = if same_dev {
7114 pos_stage
7115 .as_ref()
7116 .ok_or("step TP decode v2 pos stage not armed")?
7117 } else {
7118 &pos[rank]
7119 };
7120 engine.qk_norm_rope_into(
7121 &q_raw[rank],
7122 &k_raw[rank],
7123 &q_norm[rank],
7124 &k_norm[rank],
7125 &mut q[rank],
7126 &mut k[rank],
7127 pos_ref,
7128 head_dim,
7129 n_rot,
7130 local_heads,
7131 local_kv_heads,
7132 rms_eps,
7133 rope_base,
7134 1.0,
7135 rope_freqs[rank],
7136 )?;
7137 } else {
7138 engine.rms_norm(
7139 &ws.q_raw[rank],
7140 &q_norm[rank],
7141 &mut ws.q[rank],
7142 head_dim,
7143 local_heads,
7144 rms_eps,
7145 )?;
7146 engine.rms_norm(
7147 &ws.k_raw[rank],
7148 &k_norm[rank],
7149 &mut ws.k[rank],
7150 head_dim,
7151 local_kv_heads,
7152 rms_eps,
7153 )?;
7154 {
7155 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7156 engine
7157 .stream()
7158 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7159 }
7160 engine.rope_neox2(
7161 &mut ws.q[rank],
7162 &mut ws.k[rank],
7163 &ws.pos[rank],
7164 head_dim,
7165 n_rot,
7166 local_heads,
7167 local_kv_heads,
7168 1,
7169 rope_base,
7170 1.0,
7171 rope_freqs[rank],
7172 )?;
7173 }
7174 if gate_shards.is_none() {
7175 let gate_start = rank * (ws.heads / ranks);
7176 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7177 engine.stream().memcpy_dtod(
7178 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7179 &mut gate_dst,
7180 )?;
7181 }
7182 Ok(())
7183 }
7184
7185 pub(crate) fn decode_v2_finish_rank_partial(
7189 &self,
7190 ws: &mut StepTpDecodeV2Ws,
7191 o_m: &ResidentStepBf16RowParallel,
7192 o_fused: bool,
7193 rank: usize,
7194 ) -> Result<(), Box<dyn std::error::Error>> {
7195 let engine = &self.ranks[rank];
7196 let _main = engine.gpu.enter_main()?;
7197 if o_fused {
7198 let StepTpDecodeV2Ws {
7199 gated,
7200 o_partials,
7201 o_block_cols,
7202 o_out,
7203 w8o_aq,
7204 w8o_ad,
7205 w8o_in,
7206 ..
7207 } = &mut *ws;
7208 let all_f32 = o_m.ranks[rank]
7209 .iter()
7210 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7211 if all_f32 {
7212 let mut weights = Vec::with_capacity(4);
7213 for block in 0..4 {
7214 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7215 unreachable!("all_f32 checked above");
7216 };
7217 weights.push(weight);
7218 }
7219 engine.matvec_f32_b4_into(
7220 [weights[0], weights[1], weights[2], weights[3]],
7221 &gated[rank],
7222 &mut o_partials[rank][0],
7223 *o_block_cols,
7224 *o_out,
7225 )?;
7226 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
7227 let in_f = 4 * *o_block_cols;
7232 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
7233 w8o_aq.clear();
7234 w8o_ad.clear();
7235 for e_rank in &self.ranks {
7236 let _m = e_rank.gpu.enter_main()?;
7237 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7238 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7239 }
7240 *w8o_in = in_f;
7241 }
7242 engine.quantize_q8_1_into(
7243 &gated[rank],
7244 1,
7245 in_f,
7246 &mut w8o_aq[rank],
7247 &mut w8o_ad[rank],
7248 )?;
7249 engine.qmatvec_q8_0_b4_rp_into(
7250 [
7251 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7252 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7253 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7254 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7255 ],
7256 &w8o_aq[rank],
7257 &w8o_ad[rank],
7258 &mut o_partials[rank][0],
7259 *o_block_cols,
7260 *o_out,
7261 )?;
7262 } else {
7263 let mut weights = Vec::with_capacity(4);
7264 for block in 0..4 {
7265 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7266 return Err("step TP decode v2 O projections mix residency classes".into());
7267 };
7268 weights.push(weight);
7269 }
7270 engine.matvec_bf16_b4_into(
7271 [weights[0], weights[1], weights[2], weights[3]],
7272 &gated[rank],
7273 &mut o_partials[rank][0],
7274 *o_block_cols,
7275 *o_out,
7276 )?;
7277 }
7278 } else {
7279 for block in 0..ws.blocks_per_rank {
7280 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7281 return Err("step TP decode v2 lost its F32 O residency".into());
7282 };
7283 let x =
7284 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7285 let w = weight.slice(0..weight.len());
7286 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7287 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7288 }
7289 }
7290 Ok(())
7291 }
7292
7293 pub(crate) fn decode_v2_finish(
7301 &self,
7302 ws: &mut StepTpDecodeV2Ws,
7303 e: &Engine,
7304 o_m: &ResidentStepBf16RowParallel,
7305 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7306 let ranks = self.ranks.len();
7307 if e.ctx().ordinal() != ws.e_device {
7308 return Err("step TP decode v2 finish engine changed".into());
7309 }
7310 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7315
7316 for rank in 0..ranks {
7319 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7320 if rank == 0 {
7321 continue;
7324 }
7325 let engine = &self.ranks[rank];
7326 let _main = engine.gpu.enter_main()?;
7327 ws.ev_rank[rank].record(&engine.stream())?;
7328 }
7329
7330 let root = &self.ranks[0];
7332 #[allow(unused_assignments)]
7333 let mut final_in_a = false;
7334 {
7335 let _main = root.gpu.enter_main()?;
7336 for ev in ws.ev_rank.iter().skip(1) {
7337 root.stream().wait(ev)?;
7338 }
7339 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7340 ws.ev_oproj.record(&root.stream())?;
7346 let _main = e.gpu.enter_main()?;
7347 e.stream().wait(&ws.ev_oproj)?;
7348 let mut output = e.uninit(ws.o_out)?;
7349 if oproj_tail_on() && oproj_tail_eligible() {
7350 use cudarc::driver::DevicePtr;
7353 let stream = e.stream();
7354 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7355 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7356 set_oproj_tail((p0 as u64, p1 as u64));
7357 return Ok(output);
7358 }
7359 e.add(
7360 &ws.o_partials[0][0],
7361 &ws.o_partials[1][0],
7362 &mut output,
7363 ws.o_out,
7364 )?;
7365 return Ok(output);
7366 }
7367 if o_fused {
7368 self.decode_v2_finish_root_fused(ws)?;
7369 ws.ev_oproj.record(&root.stream())?;
7370 let _main = e.gpu.enter_main()?;
7371 e.stream().wait(&ws.ev_oproj)?;
7372 let mut output = e.uninit(ws.o_out)?;
7373 e.stream().memcpy_dtod(
7374 &ws.reduce_a.slice(0..ws.o_out),
7375 &mut output.slice_mut(0..ws.o_out),
7376 )?;
7377 return Ok(output);
7378 }
7379 let mut first = true;
7380 let mut current_is_a = false;
7381 for rank in 0..ranks {
7382 for block in 0..ws.blocks_per_rank {
7383 let use_peer = rank != 0;
7384 if use_peer {
7385 root.stream()
7386 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
7387 }
7388 match (first, current_is_a, use_peer) {
7390 (true, _, true) => {
7391 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7392 }
7393 (true, _, false) => root.add(
7394 &ws.zeros,
7395 &ws.o_partials[0][block],
7396 &mut ws.reduce_a,
7397 ws.o_out,
7398 )?,
7399 (false, true, true) => {
7400 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7401 }
7402 (false, true, false) => root.add(
7403 &ws.reduce_a,
7404 &ws.o_partials[0][block],
7405 &mut ws.reduce_b,
7406 ws.o_out,
7407 )?,
7408 (false, false, true) => {
7409 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7410 }
7411 (false, false, false) => root.add(
7412 &ws.reduce_b,
7413 &ws.o_partials[0][block],
7414 &mut ws.reduce_a,
7415 ws.o_out,
7416 )?,
7417 }
7418 current_is_a = first || !current_is_a;
7419 first = false;
7420 }
7421 }
7422 final_in_a = current_is_a;
7423
7424 for rank in 0..ranks {
7425 let start = rank * ws.local_kv_dim;
7426 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
7427 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
7428 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
7429 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
7430 }
7431 ws.ev_oproj.record(&root.stream())?;
7432 }
7433
7434 let _main = e.gpu.enter_main()?;
7438 e.stream().wait(&ws.ev_oproj)?;
7439 let mut output = e.uninit(ws.o_out)?;
7440 let source = if final_in_a {
7441 &ws.reduce_a
7442 } else {
7443 &ws.reduce_b
7444 };
7445 e.stream().memcpy_dtod(
7446 &source.slice(0..ws.o_out),
7447 &mut output.slice_mut(0..ws.o_out),
7448 )?;
7449 Ok(output)
7450 }
7451
7452 pub fn run_routed_experts(
7453 &self,
7454 experts: &ResidentExpertParallel,
7455 input: &[f32],
7456 tokens: usize,
7457 selected: &[usize],
7458 route_weights: &[f32],
7459 experts_per_token: usize,
7460 activation_limit: Option<f32>,
7461 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7462 validate_step_expert_activation_limit(activation_limit)?;
7463 validate_ep_residency(&self.ranks, experts)?;
7464 validate_activations(input, tokens, experts.input_width)?;
7465 let pairs = tokens
7466 .checked_mul(experts_per_token)
7467 .ok_or("EP route count overflow")?;
7468 if selected.len() != pairs || route_weights.len() != pairs {
7469 return Err(format!(
7470 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7471 {experts_per_token} ({pairs})",
7472 selected.len(),
7473 route_weights.len(),
7474 )
7475 .into());
7476 }
7477 if !route_weights.iter().all(|weight| weight.is_finite()) {
7478 return Err("EP route weights contain a non-finite value".into());
7479 }
7480 if self.native_p2p {
7481 return self.run_routed_experts_native(
7482 experts,
7483 input,
7484 tokens,
7485 selected,
7486 route_weights,
7487 experts_per_token,
7488 activation_limit,
7489 );
7490 }
7491
7492 let mut output = vec![0.0f32; tokens * experts.input_width];
7493 let per_rank = experts.expert_count / experts.ranks.len();
7494 for token in 0..tokens {
7495 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7496 for slot in 0..experts_per_token {
7497 let pair = token * experts_per_token + slot;
7498 let expert = selected[pair];
7499 if expert >= experts.expert_count {
7500 return Err(format!(
7501 "EP selected expert {expert} outside 0..{}",
7502 experts.expert_count
7503 )
7504 .into());
7505 }
7506 let owner = expert / per_rank;
7507 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7508 let rank = &experts.ranks[owner];
7509 let engine = &self.ranks[owner];
7510 let gate =
7511 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7512 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7513 let activated: Vec<f32> = gate
7514 .iter()
7515 .zip(&up)
7516 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7517 .collect();
7518 debug_assert_eq!(activated.len(), experts.expert_width);
7519 let down =
7520 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7521 let weight = route_weights[pair];
7522 for (sum, value) in output
7523 [token * experts.input_width..(token + 1) * experts.input_width]
7524 .iter_mut()
7525 .zip(down)
7526 {
7527 *sum += weight * value;
7528 }
7529 }
7530 }
7531 Ok(output)
7532 }
7533
7534 fn run_routed_experts_native(
7535 &self,
7536 experts: &ResidentExpertParallel,
7537 input: &[f32],
7538 tokens: usize,
7539 selected: &[usize],
7540 route_weights: &[f32],
7541 experts_per_token: usize,
7542 activation_limit: Option<f32>,
7543 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7544 if !self.native_p2p || self.ranks.len() < 2 {
7545 return Err("native EP execution requires at least two P2P ranks".into());
7546 }
7547 if self.ep_device_arithmetic {
7548 return self.run_routed_experts_native_device(
7549 experts,
7550 input,
7551 tokens,
7552 selected,
7553 route_weights,
7554 experts_per_token,
7555 activation_limit,
7556 );
7557 }
7558 let mut output = vec![0.0f32; tokens * experts.input_width];
7559 let per_rank = experts.expert_count / experts.ranks.len();
7560 for token in 0..tokens {
7561 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7562 let mut rank_inputs = (0..self.ranks.len())
7563 .map(|_| None)
7564 .collect::<Vec<Option<CudaSlice<f32>>>>();
7565 rank_inputs[0] = Some({
7566 let root = &self.ranks[0];
7567 let _main = root.gpu.enter_main()?;
7568 root.htod(input_row)?
7569 });
7570
7571 for slot in 0..experts_per_token {
7572 let pair = token * experts_per_token + slot;
7573 let expert = selected[pair];
7574 if expert >= experts.expert_count {
7575 return Err(format!(
7576 "EP selected expert {expert} outside 0..{}",
7577 experts.expert_count
7578 )
7579 .into());
7580 }
7581 let owner = expert / per_rank;
7582 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7583 if rank_inputs[owner].is_none() {
7584 let peer_input = {
7585 let root_input = rank_inputs[0]
7586 .as_ref()
7587 .ok_or("native EP lost its root input")?;
7588 let engine = &self.ranks[owner];
7589 let _main = engine.gpu.enter_main()?;
7590 let mut peer_input = engine.uninit(experts.input_width)?;
7591 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7592 peer_input
7593 };
7594 rank_inputs[owner] = Some(peer_input);
7595 }
7596
7597 let rank = &experts.ranks[owner];
7598 let engine = &self.ranks[owner];
7599 let owner_input = rank_inputs[owner]
7600 .as_ref()
7601 .ok_or("native EP owner input is absent after dispatch")?;
7602 let gate = run_resident_bank_expert_device(
7603 engine,
7604 &rank.gate,
7605 local_expert,
7606 owner_input,
7607 1,
7608 )?;
7609 let up = run_resident_bank_expert_device(
7610 engine,
7611 &rank.up,
7612 local_expert,
7613 owner_input,
7614 1,
7615 )?;
7616 let (gate, up) = {
7617 let _main = engine.gpu.enter_main()?;
7618 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
7619 };
7620 let activated = gate
7621 .iter()
7622 .zip(&up)
7623 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7624 .collect::<Vec<_>>();
7625 debug_assert_eq!(activated.len(), experts.expert_width);
7626 let activated = {
7627 let _main = engine.gpu.enter_main()?;
7628 engine.htod(&activated)?
7629 };
7630 let down = run_resident_bank_expert_device(
7631 engine,
7632 &rank.down,
7633 local_expert,
7634 &activated,
7635 1,
7636 )?;
7637 let down = if owner == 0 {
7638 let _main = engine.gpu.enter_main()?;
7639 engine.dtoh(&down)?
7640 } else {
7641 let root = &self.ranks[0];
7642 let _main = root.gpu.enter_main()?;
7643 let mut root_down = root.uninit(experts.input_width)?;
7644 root.stream().memcpy_dtod(&down, &mut root_down)?;
7645 root.dtoh(&root_down)?
7646 };
7647 let weight = route_weights[pair];
7648 for (sum, value) in output
7649 [token * experts.input_width..(token + 1) * experts.input_width]
7650 .iter_mut()
7651 .zip(down)
7652 {
7653 *sum += weight * value;
7654 }
7655 }
7656 }
7657 Ok(output)
7658 }
7659
7660 fn run_routed_experts_native_device(
7661 &self,
7662 experts: &ResidentExpertParallel,
7663 input: &[f32],
7664 tokens: usize,
7665 selected: &[usize],
7666 route_weights: &[f32],
7667 experts_per_token: usize,
7668 activation_limit: Option<f32>,
7669 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7670 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
7671 return Err(
7672 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
7673 );
7674 }
7675 let mut output = Vec::with_capacity(tokens * experts.input_width);
7676 let per_rank = experts.expert_count / experts.ranks.len();
7677 let root = &self.ranks[0];
7678 for token in 0..tokens {
7679 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7680 let mut rank_inputs = (0..self.ranks.len())
7681 .map(|_| None)
7682 .collect::<Vec<Option<CudaSlice<f32>>>>();
7683 rank_inputs[0] = Some({
7684 let _main = root.gpu.enter_main()?;
7685 root.htod(input_row)?
7686 });
7687 let mut root_output = {
7688 let _main = root.gpu.enter_main()?;
7689 root.zeros(experts.input_width)?
7690 };
7691 let mut remote_down_keepalive = Vec::new();
7692
7693 for slot in 0..experts_per_token {
7694 let pair = token * experts_per_token + slot;
7695 let expert = selected[pair];
7696 if expert >= experts.expert_count {
7697 return Err(format!(
7698 "EP selected expert {expert} outside 0..{}",
7699 experts.expert_count
7700 )
7701 .into());
7702 }
7703 let owner = expert / per_rank;
7704 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7705 if rank_inputs[owner].is_none() {
7706 let peer_input = {
7707 let root_input = rank_inputs[0]
7708 .as_ref()
7709 .ok_or("native EP lost its root input")?;
7710 let engine = &self.ranks[owner];
7711 let _main = engine.gpu.enter_main()?;
7712 let mut peer_input = engine.uninit(experts.input_width)?;
7713 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7714 peer_input
7715 };
7716 rank_inputs[owner] = Some(peer_input);
7717 }
7718
7719 let rank = &experts.ranks[owner];
7720 let engine = &self.ranks[owner];
7721 let owner_input = rank_inputs[owner]
7722 .as_ref()
7723 .ok_or("native EP owner input is absent after dispatch")?;
7724 let gate = run_resident_bank_expert_device(
7725 engine,
7726 &rank.gate,
7727 local_expert,
7728 owner_input,
7729 1,
7730 )?;
7731 let up = run_resident_bank_expert_device(
7732 engine,
7733 &rank.up,
7734 local_expert,
7735 owner_input,
7736 1,
7737 )?;
7738 let activated = {
7739 let _main = engine.gpu.enter_main()?;
7740 let mut activated = engine.uninit(experts.expert_width)?;
7741 if let Some(limit) = activation_limit {
7742 engine.silu_clamped_mul_host_expf(
7743 &gate,
7744 &up,
7745 limit,
7746 &mut activated,
7747 experts.expert_width,
7748 )?;
7749 } else {
7750 engine.silu_mul_host_expf(
7751 &gate,
7752 &up,
7753 &mut activated,
7754 experts.expert_width,
7755 )?;
7756 }
7757 activated
7758 };
7759 let down = run_resident_bank_expert_device(
7760 engine,
7761 &rank.down,
7762 local_expert,
7763 &activated,
7764 1,
7765 )?;
7766 let root_down = if owner == 0 {
7767 down
7768 } else {
7769 let _main = root.gpu.enter_main()?;
7770 let mut root_down = root.uninit(experts.input_width)?;
7771 root.stream().memcpy_dtod(&down, &mut root_down)?;
7772 remote_down_keepalive.push(down);
7776 root_down
7777 };
7778 let _main = root.gpu.enter_main()?;
7779 let mut destination = root_output.slice_mut(0..experts.input_width);
7780 root.axpy_host_into(
7781 &root_down.slice(0..root_down.len()),
7782 route_weights[pair],
7783 &mut destination,
7784 experts.input_width,
7785 )?;
7786 }
7787
7788 let _main = root.gpu.enter_main()?;
7789 let root_output = root.dtoh(&root_output)?;
7790 drop(remote_down_keepalive);
7791 output.extend(root_output);
7792 }
7793 Ok(output)
7794 }
7795}
7796
7797fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7798 if matrix.out_features % tp != 0 {
7799 return Err(format!(
7800 "column-parallel out_features {} is not divisible by TP={tp}",
7801 matrix.out_features
7802 ));
7803 }
7804 let local_out = matrix.out_features / tp;
7805 if local_out % FP8_BLOCK != 0 {
7806 return Err(format!(
7807 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7808 E4M3 scale block"
7809 ));
7810 }
7811 Ok(())
7812}
7813
7814fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7815 if !matches!(tp, 1 | 2 | 4 | 8) {
7816 return Err(format!(
7817 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7818 ));
7819 }
7820 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7821 return Err(format!(
7822 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7823 ));
7824 }
7825 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7826 let local_out = out_features / tp;
7827 if local_out % canonical_rows != 0 {
7828 return Err(format!(
7829 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7830 {canonical_rows}-row chunks"
7831 ));
7832 }
7833 Ok(canonical_rows)
7834}
7835
7836fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7837 if !matches!(tp, 1 | 2 | 4 | 8) {
7838 return Err(format!(
7839 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7840 ));
7841 }
7842 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7843 return Err(format!(
7844 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7845 ));
7846 }
7847 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7848 let local_in = in_features / tp;
7849 if local_in % canonical_cols != 0 {
7850 return Err(format!(
7851 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7852 {canonical_cols}-column chunks"
7853 ));
7854 }
7855 Ok(canonical_cols)
7856}
7857
7858fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7859 if matrix.in_features % tp != 0 {
7860 return Err(format!(
7861 "row-parallel in_features {} is not divisible by TP={tp}",
7862 matrix.in_features
7863 ));
7864 }
7865 let local_in = matrix.in_features / tp;
7866 if local_in % FP8_BLOCK != 0 {
7867 return Err(format!(
7868 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7869 E4M3 scale block"
7870 ));
7871 }
7872 Ok(())
7873}
7874
7875fn upload_rank(
7876 engine: &Engine,
7877 matrix: E4m3BlockMatrix<'_>,
7878) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7879 let _main = engine.gpu.enter_main()?;
7880 matrix.validate()?;
7881 Ok(ResidentE4m3Rank {
7882 codes: engine.htod_bytes(matrix.codes)?,
7883 scales: engine.htod(matrix.scales)?,
7884 out_features: matrix.out_features,
7885 in_features: matrix.in_features,
7886 })
7887}
7888
7889fn upload_bf16_rank(
7890 engine: &Engine,
7891 matrix: Bf16Matrix<'_>,
7892 f32_mirror: bool,
7893) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7894 let _main = engine.gpu.enter_main()?;
7895 matrix.validate()?;
7896 let bytes = engine.htod_bytes(matrix.bytes)?;
7897 let weight = if f32_mirror {
7898 let values = matrix
7899 .out_features
7900 .checked_mul(matrix.in_features)
7901 .ok_or("resident BF16 mirror element count overflow")?;
7902 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7903 } else {
7904 ResidentBf16Weight::Bf16(bytes)
7905 };
7906 let q8 = if crate::step_tp_w8_on() && matrix.in_features % 32 == 0 {
7910 if let ResidentBf16Weight::Bf16(bytes) = &weight {
7911 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
7918 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
7919 engine.encode_q8_0_from_bf16(
7920 bytes,
7921 &mut interleaved,
7922 matrix.in_features,
7923 matrix.out_features,
7924 )?;
7925 let mirror =
7926 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
7927 Some(mirror)
7928 } else {
7929 None
7930 }
7931 } else {
7932 None
7933 };
7934 Ok(ResidentBf16Rank {
7935 weight,
7936 out_features: matrix.out_features,
7937 in_features: matrix.in_features,
7938 q8,
7939 })
7940}
7941
7942fn upload_expert_bank_rank(
7943 engine: &Engine,
7944 bank: E4m3ExpertBank<'_>,
7945 expert_range: Range<usize>,
7946) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7947 let _main = engine.gpu.enter_main()?;
7948 bank.validate()?;
7949 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7950 return Err(format!(
7951 "invalid EP expert range {expert_range:?} for {} experts",
7952 bank.expert_count
7953 )
7954 .into());
7955 }
7956 let code_stride = bank.out_features * bank.in_features;
7957 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7958 Ok(ResidentE4m3ExpertBankRank {
7959 codes: engine.htod_bytes(
7960 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7961 )?,
7962 scales: engine.htod(
7963 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7964 )?,
7965 expert_range,
7966 out_features: bank.out_features,
7967 in_features: bank.in_features,
7968 code_stride,
7969 scale_stride,
7970 k_blocks: None,
7971 })
7972}
7973
7974fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7975 if bank.out_features % tp != 0 {
7976 return Err(format!(
7977 "TP expert output width {} is not divisible by TP={tp}",
7978 bank.out_features
7979 ));
7980 }
7981 let local_out = bank.out_features / tp;
7982 if local_out % FP8_BLOCK != 0 {
7983 return Err(format!(
7984 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7985 ));
7986 }
7987 Ok(())
7988}
7989
7990fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7991 if bank.in_features % tp != 0 {
7992 return Err(format!(
7993 "TP expert input width {} is not divisible by TP={tp}",
7994 bank.in_features
7995 ));
7996 }
7997 let local_in = bank.in_features / tp;
7998 if local_in % FP8_BLOCK != 0 {
7999 return Err(format!(
8000 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
8001 ));
8002 }
8003 Ok(())
8004}
8005
8006fn upload_column_bank_rank(
8007 engine: &Engine,
8008 bank: E4m3ExpertBank<'_>,
8009 tp: usize,
8010 rank: usize,
8011) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8012 let _main = engine.gpu.enter_main()?;
8013 let packed = pack_column_bank_rank(bank, tp, rank)?;
8014 Ok(ResidentE4m3ExpertBankRank {
8015 codes: engine.htod_bytes(&packed.codes)?,
8016 scales: engine.htod(&packed.scales)?,
8017 expert_range: packed.expert_range,
8018 out_features: packed.out_features,
8019 in_features: packed.in_features,
8020 code_stride: packed.code_stride,
8021 scale_stride: packed.scale_stride,
8022 k_blocks: packed.k_blocks,
8023 })
8024}
8025
8026fn pack_column_bank_rank(
8027 bank: E4m3ExpertBank<'_>,
8028 tp: usize,
8029 rank: usize,
8030) -> Result<PackedE4m3ExpertBankRank, String> {
8031 bank.validate()?;
8032 validate_column_bank_shape(bank, tp)?;
8033 if rank >= tp {
8034 return Err(format!("TP rank {rank} outside 0..{tp}"));
8035 }
8036 let local_out = bank.out_features / tp;
8037 let full_code_stride = bank.out_features * bank.in_features;
8038 let local_code_stride = local_out * bank.in_features;
8039 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8040 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8041 let local_scale_rows = local_out / FP8_BLOCK;
8042 let local_scale_stride = local_scale_rows * scale_cols;
8043 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8044 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8045 let row_start = rank * local_out;
8046 let scale_row_start = rank * local_scale_rows;
8047 for expert in 0..bank.expert_count {
8048 let code_start = expert * full_code_stride + row_start * bank.in_features;
8049 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8050 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8051 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8052 }
8053 Ok(PackedE4m3ExpertBankRank {
8054 codes,
8055 scales,
8056 expert_range: 0..bank.expert_count,
8057 out_features: local_out,
8058 in_features: bank.in_features,
8059 code_stride: local_code_stride,
8060 scale_stride: local_scale_stride,
8061 k_blocks: None,
8062 })
8063}
8064
8065fn upload_row_bank_rank(
8066 engine: &Engine,
8067 bank: E4m3ExpertBank<'_>,
8068 tp: usize,
8069 rank: usize,
8070) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8071 let _main = engine.gpu.enter_main()?;
8072 let packed = pack_row_bank_rank(bank, tp, rank)?;
8073 Ok(ResidentE4m3ExpertBankRank {
8074 codes: engine.htod_bytes(&packed.codes)?,
8075 scales: engine.htod(&packed.scales)?,
8076 expert_range: packed.expert_range,
8077 out_features: packed.out_features,
8078 in_features: packed.in_features,
8079 code_stride: packed.code_stride,
8080 scale_stride: packed.scale_stride,
8081 k_blocks: packed.k_blocks,
8082 })
8083}
8084
8085fn pack_row_bank_rank(
8086 bank: E4m3ExpertBank<'_>,
8087 tp: usize,
8088 rank: usize,
8089) -> Result<PackedE4m3ExpertBankRank, String> {
8090 bank.validate()?;
8091 validate_row_bank_shape(bank, tp)?;
8092 if rank >= tp {
8093 return Err(format!("TP rank {rank} outside 0..{tp}"));
8094 }
8095 let local_in = bank.in_features / tp;
8096 let full_code_stride = bank.out_features * bank.in_features;
8097 let local_code_stride = bank.out_features * local_in;
8098 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8099 let local_scale_cols = local_in / FP8_BLOCK;
8100 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8101 let full_scale_stride = scale_rows * full_scale_cols;
8102 let local_scale_stride = scale_rows * local_scale_cols;
8103 let global_block_start = rank * local_scale_cols;
8104 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8105 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8106 for expert in 0..bank.expert_count {
8107 let expert_code_start = expert * full_code_stride;
8108 let expert_scale_start = expert * full_scale_stride;
8109 for local_block in 0..local_scale_cols {
8110 let global_block = global_block_start + local_block;
8111 let column_start = global_block * FP8_BLOCK;
8112 for row in 0..bank.out_features {
8113 let start = expert_code_start + row * bank.in_features + column_start;
8114 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8115 }
8116 for row in 0..scale_rows {
8117 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8118 }
8119 }
8120 }
8121 Ok(PackedE4m3ExpertBankRank {
8122 codes,
8123 scales,
8124 expert_range: 0..bank.expert_count,
8125 out_features: bank.out_features,
8126 in_features: local_in,
8127 code_stride: local_code_stride,
8128 scale_stride: local_scale_stride,
8129 k_blocks: Some(local_scale_cols),
8130 })
8131}
8132
8133fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8134 if engines.len() != ranks.len() {
8135 return Err(format!(
8136 "resident TP rank count {} != runtime rank count {}",
8137 ranks.len(),
8138 engines.len()
8139 ));
8140 }
8141 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8142 let device = engine.ctx().ordinal();
8143 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8144 return Err(format!(
8145 "resident TP rank {rank} is not owned by runtime device {device}"
8146 ));
8147 }
8148 }
8149 Ok(())
8150}
8151
8152fn validate_tp_bank_residency(
8153 engines: &[Engine],
8154 experts: &ResidentTpExpertBank,
8155) -> Result<(), String> {
8156 if engines.len() != experts.gate.len()
8157 || engines.len() != experts.up.len()
8158 || engines.len() != experts.down.len()
8159 {
8160 return Err(format!(
8161 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8162 experts.gate.len(),
8163 experts.up.len(),
8164 experts.down.len(),
8165 engines.len()
8166 ));
8167 }
8168 for (rank, engine) in engines.iter().enumerate() {
8169 let device = engine.ctx().ordinal();
8170 for (projection, bank) in [
8171 ("gate", &experts.gate[rank]),
8172 ("up", &experts.up[rank]),
8173 ("down", &experts.down[rank]),
8174 ] {
8175 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8176 return Err(format!(
8177 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8178 {device}"
8179 ));
8180 }
8181 }
8182 }
8183 Ok(())
8184}
8185
8186fn validate_ep_residency(
8187 engines: &[Engine],
8188 experts: &ResidentExpertParallel,
8189) -> Result<(), String> {
8190 if engines.len() != experts.ranks.len() {
8191 return Err(format!(
8192 "resident EP rank count {} != runtime rank count {}",
8193 experts.ranks.len(),
8194 engines.len()
8195 ));
8196 }
8197 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
8198 let device = engine.ctx().ordinal();
8199 for (projection, bank) in [
8200 ("gate", &resident.gate),
8201 ("up", &resident.up),
8202 ("down", &resident.down),
8203 ] {
8204 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8205 return Err(format!(
8206 "resident EP rank {rank} {projection} bank is not owned by runtime device \
8207 {device}"
8208 ));
8209 }
8210 }
8211 }
8212 Ok(())
8213}
8214
8215fn run_rank(
8216 engine: &Engine,
8217 matrix: E4m3BlockMatrix<'_>,
8218 activations: &[f32],
8219 tokens: usize,
8220) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8221 let _main = engine.gpu.enter_main()?;
8222 let codes = engine.htod_bytes(matrix.codes)?;
8223 let scales = engine.htod(matrix.scales)?;
8224 let activations = engine.htod(activations)?;
8225 let output = engine.qmatvec_mmq_fp8_blk(
8226 &codes,
8227 &scales,
8228 &activations,
8229 tokens,
8230 matrix.in_features,
8231 matrix.out_features,
8232 )?;
8233 engine.dtoh(&output)
8234}
8235
8236fn run_resident_rank(
8237 engine: &Engine,
8238 matrix: &ResidentE4m3Rank,
8239 activations: &[f32],
8240 tokens: usize,
8241) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8242 let _main = engine.gpu.enter_main()?;
8243 let activations = engine.htod(activations)?;
8244 let output = engine.qmatvec_mmq_fp8_blk(
8245 &matrix.codes,
8246 &matrix.scales,
8247 &activations,
8248 tokens,
8249 matrix.in_features,
8250 matrix.out_features,
8251 )?;
8252 engine.dtoh(&output)
8253}
8254
8255fn run_resident_bf16_rank(
8256 engine: &Engine,
8257 matrix: &ResidentBf16Rank,
8258 activations: &[f32],
8259 tokens: usize,
8260 canonical_chunk_rows: Option<usize>,
8261) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8262 let _main = engine.gpu.enter_main()?;
8263 let activations = engine.htod(activations)?;
8264 let output = run_resident_bf16_rank_device(
8265 engine,
8266 matrix,
8267 &activations,
8268 tokens,
8269 canonical_chunk_rows,
8270 false,
8271 )?;
8272 engine.dtoh(&output)
8273}
8274
8275fn run_resident_bf16_rank_device(
8276 engine: &Engine,
8277 matrix: &ResidentBf16Rank,
8278 activations: &CudaSlice<f32>,
8279 tokens: usize,
8280 canonical_chunk_rows: Option<usize>,
8281 strided_chunk_output: bool,
8282) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8283 let _main = engine.gpu.enter_main()?;
8284 if activations.ordinal() != engine.ctx().ordinal() {
8285 return Err(format!(
8286 "resident BF16 activation device {} != rank device {}",
8287 activations.ordinal(),
8288 engine.ctx().ordinal()
8289 )
8290 .into());
8291 }
8292 if activations.len() != tokens * matrix.in_features {
8293 return Err(format!(
8294 "resident BF16 activation count {} != {tokens}x{}",
8295 activations.len(),
8296 matrix.in_features
8297 )
8298 .into());
8299 }
8300 match (&matrix.weight, canonical_chunk_rows) {
8301 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8302 .linear_bf16_resident_canonical_rows(
8303 activations,
8304 bytes,
8305 tokens,
8306 matrix.in_features,
8307 matrix.out_features,
8308 rows,
8309 ),
8310 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8311 activations,
8312 bytes,
8313 tokens,
8314 matrix.in_features,
8315 matrix.out_features,
8316 ),
8317 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8318 .linear_f32_resident_canonical_rows_strided(
8319 activations,
8320 values,
8321 tokens,
8322 matrix.in_features,
8323 matrix.out_features,
8324 rows,
8325 ),
8326 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8327 activations,
8328 values,
8329 tokens,
8330 matrix.in_features,
8331 matrix.out_features,
8332 rows,
8333 ),
8334 (ResidentBf16Weight::F32(values), None) => engine.linear(
8335 activations,
8336 values,
8337 tokens,
8338 matrix.in_features,
8339 matrix.out_features,
8340 ),
8341 }
8342}
8343
8344fn validate_resident_bf16_ranks(
8345 engines: &[Engine],
8346 ranks: &[ResidentBf16Rank],
8347) -> Result<(), String> {
8348 if engines.len() != ranks.len() {
8349 return Err(format!(
8350 "resident BF16 TP rank count {} != runtime rank count {}",
8351 ranks.len(),
8352 engines.len(),
8353 ));
8354 }
8355 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8356 let device = engine.ctx().ordinal();
8357 if matrix.weight.ordinal() != device {
8358 return Err(format!(
8359 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8360 ));
8361 }
8362 }
8363 Ok(())
8364}
8365
8366fn validate_step_bf16_row_residency(
8367 engines: &[Engine],
8368 matrix: &ResidentStepBf16RowParallel,
8369) -> Result<(), String> {
8370 if engines.len() != matrix.ranks.len() {
8371 return Err(format!(
8372 "resident Step BF16 row rank count {} != runtime rank count {}",
8373 matrix.ranks.len(),
8374 engines.len(),
8375 ));
8376 }
8377 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8378 if matrix.canonical_chunk_cols != canonical_cols {
8379 return Err(format!(
8380 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8381 matrix.canonical_chunk_cols
8382 ));
8383 }
8384 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8385 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8386 if blocks.len() != blocks_per_rank {
8387 return Err(format!(
8388 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8389 blocks.len()
8390 ));
8391 }
8392 let device = engine.ctx().ordinal();
8393 for (block, resident) in blocks.iter().enumerate() {
8394 if resident.weight.ordinal() != device
8395 || resident.in_features != canonical_cols
8396 || resident.out_features != matrix.out_features
8397 {
8398 return Err(format!(
8399 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8400 device or geometry"
8401 ));
8402 }
8403 }
8404 }
8405 Ok(())
8406}
8407
8408fn validate_replicated_device_rows(
8409 engines: &[Engine],
8410 rows: &ResidentReplicatedDeviceRows,
8411) -> Result<(), String> {
8412 let rank_lengths = rows
8413 .ranks
8414 .iter()
8415 .map(|rank_rows| rank_rows.len())
8416 .collect::<Vec<_>>();
8417 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8418 if rows
8419 .ranks
8420 .iter()
8421 .zip(engines)
8422 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8423 {
8424 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8425 }
8426 Ok(())
8427}
8428
8429fn replicated_device_row_values(
8430 tokens: usize,
8431 width: usize,
8432 expected_ranks: usize,
8433 rank_lengths: &[usize],
8434) -> Result<usize, String> {
8435 let values = tokens
8436 .checked_mul(width)
8437 .ok_or("replicated device row size overflow")?;
8438 if tokens == 0
8439 || width == 0
8440 || expected_ranks == 0
8441 || rank_lengths.len() != expected_ranks
8442 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8443 {
8444 return Err(format!(
8445 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8446 tokens,
8447 width,
8448 rank_lengths.len(),
8449 expected_ranks
8450 ));
8451 }
8452 Ok(values)
8453}
8454
8455fn replicated_device_row_source_values(
8456 tokens: usize,
8457 width: usize,
8458 source_len: usize,
8459 source_device: usize,
8460 root_device: usize,
8461) -> Result<usize, String> {
8462 let values = tokens
8463 .checked_mul(width)
8464 .ok_or("replicated device row size overflow")?;
8465 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8466 return Err(format!(
8467 "replicated device row source has inconsistent geometry/device \
8468 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8469 ));
8470 }
8471 Ok(values)
8472}
8473
8474fn bf16_column_shard(
8475 matrix: Bf16Matrix<'_>,
8476 tp: usize,
8477 rank: usize,
8478) -> Result<Bf16Matrix<'_>, String> {
8479 matrix.validate()?;
8480 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8481 return Err(format!(
8482 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8483 matrix.out_features
8484 ));
8485 }
8486 let local_out = matrix.out_features / tp;
8487 let row_bytes = matrix.in_features * 2;
8488 let start = rank * local_out * row_bytes;
8489 Ok(Bf16Matrix {
8490 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8491 out_features: local_out,
8492 in_features: matrix.in_features,
8493 })
8494}
8495
8496fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8497 matrix.validate()?;
8498 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8499 return Err(format!(
8500 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8501 matrix.in_features
8502 ));
8503 }
8504 let local_in = matrix.in_features / tp;
8505 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8506 for row in 0..matrix.out_features {
8507 let start = (row * matrix.in_features + rank * local_in) * 2;
8508 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8509 }
8510 Ok(bytes)
8511}
8512
8513fn bf16_row_block(
8514 matrix: Bf16Matrix<'_>,
8515 col_start: usize,
8516 block_cols: usize,
8517) -> Result<Vec<u8>, String> {
8518 matrix.validate()?;
8519 let col_end = col_start
8520 .checked_add(block_cols)
8521 .ok_or("BF16 row block column overflow")?;
8522 if block_cols == 0 || col_end > matrix.in_features {
8523 return Err(format!(
8524 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8525 matrix.in_features
8526 ));
8527 }
8528 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8529 for row in 0..matrix.out_features {
8530 let start = (row * matrix.in_features + col_start) * 2;
8531 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8532 }
8533 Ok(bytes)
8534}
8535
8536fn run_resident_bank_expert(
8537 engine: &Engine,
8538 bank: &ResidentE4m3ExpertBankRank,
8539 local_expert: usize,
8540 activations: &[f32],
8541 tokens: usize,
8542) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8543 let _main = engine.gpu.enter_main()?;
8544 if bank.k_blocks.is_some() {
8545 return Err("block-major TP row bank requires canonical block execution".into());
8546 }
8547 let local_count = bank.expert_range.end - bank.expert_range.start;
8548 if local_expert >= local_count {
8549 return Err(format!(
8550 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8551 bank.expert_range
8552 )
8553 .into());
8554 }
8555 validate_activations(activations, tokens, bank.in_features)?;
8556 let activations = engine.htod(activations)?;
8557 let weight = bank
8558 .codes
8559 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8560 let scales = bank
8561 .scales
8562 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8563 let input = activations.slice(0..activations.len());
8564 let output = engine.qmatvec_mmq_fp8_blk_view(
8565 &weight,
8566 &scales,
8567 &input,
8568 tokens,
8569 bank.in_features,
8570 bank.out_features,
8571 )?;
8572 engine.dtoh(&output)
8573}
8574
8575fn run_resident_bank_expert_block(
8576 engine: &Engine,
8577 bank: &ResidentE4m3ExpertBankRank,
8578 local_expert: usize,
8579 block: usize,
8580 activations: &[f32],
8581) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8582 let _main = engine.gpu.enter_main()?;
8583 let local_count = bank.expert_range.end - bank.expert_range.start;
8584 if local_expert >= local_count {
8585 return Err(format!(
8586 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8587 bank.expert_range
8588 )
8589 .into());
8590 }
8591 let blocks = bank
8592 .k_blocks
8593 .ok_or("TP row bank is not packed in native K-block order")?;
8594 if block >= blocks {
8595 return Err(format!("TP row block {block} outside 0..{blocks}").into());
8596 }
8597 validate_activations(activations, 1, FP8_BLOCK)?;
8598 let block_code_stride = bank.out_features * FP8_BLOCK;
8599 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8600 if bank.in_features != blocks * FP8_BLOCK
8601 || bank.code_stride != blocks * block_code_stride
8602 || bank.scale_stride != blocks * block_scale_stride
8603 {
8604 return Err("TP row bank block-major geometry is inconsistent".into());
8605 }
8606
8607 let expert_code_start = local_expert * bank.code_stride;
8608 let expert_scale_start = local_expert * bank.scale_stride;
8609 let weight = bank.codes.slice(
8610 expert_code_start + block * block_code_stride
8611 ..expert_code_start + (block + 1) * block_code_stride,
8612 );
8613 let scales = bank.scales.slice(
8614 expert_scale_start + block * block_scale_stride
8615 ..expert_scale_start + (block + 1) * block_scale_stride,
8616 );
8617 let activations = engine.htod(activations)?;
8618 let input = activations.slice(0..activations.len());
8619 let output = engine.qmatvec_mmq_fp8_blk_view(
8620 &weight,
8621 &scales,
8622 &input,
8623 1,
8624 FP8_BLOCK,
8625 bank.out_features,
8626 )?;
8627 engine.dtoh(&output)
8628}
8629
8630fn run_resident_bank_expert_device(
8631 engine: &Engine,
8632 bank: &ResidentE4m3ExpertBankRank,
8633 local_expert: usize,
8634 activations: &CudaSlice<f32>,
8635 tokens: usize,
8636) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8637 let _main = engine.gpu.enter_main()?;
8638 if bank.k_blocks.is_some() {
8639 return Err("block-major TP row bank requires canonical block execution".into());
8640 }
8641 let local_count = bank.expert_range.end - bank.expert_range.start;
8642 if local_expert >= local_count {
8643 return Err(format!(
8644 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8645 bank.expert_range
8646 )
8647 .into());
8648 }
8649 let expected = tokens
8650 .checked_mul(bank.in_features)
8651 .ok_or("native TP activation size overflow")?;
8652 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
8653 return Err(format!(
8654 "native TP activation len/device {}/{} != expected {expected}/{}",
8655 activations.len(),
8656 activations.ordinal(),
8657 engine.ctx().ordinal()
8658 )
8659 .into());
8660 }
8661 let weight = bank
8662 .codes
8663 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8664 let scales = bank
8665 .scales
8666 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8667 let input = activations.slice(0..activations.len());
8668 engine.qmatvec_mmq_fp8_blk_view(
8669 &weight,
8670 &scales,
8671 &input,
8672 tokens,
8673 bank.in_features,
8674 bank.out_features,
8675 )
8676}
8677
8678fn run_resident_bank_expert_block_device(
8679 engine: &Engine,
8680 bank: &ResidentE4m3ExpertBankRank,
8681 local_expert: usize,
8682 block: usize,
8683 activations: &cudarc::driver::CudaView<'_, f32>,
8684) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8685 let _main = engine.gpu.enter_main()?;
8686 let local_count = bank.expert_range.end - bank.expert_range.start;
8687 if local_expert >= local_count {
8688 return Err(format!(
8689 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8690 bank.expert_range
8691 )
8692 .into());
8693 }
8694 let blocks = bank
8695 .k_blocks
8696 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
8697 if block >= blocks {
8698 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
8699 }
8700 let activation_device = activations.stream().context().ordinal();
8701 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
8702 return Err(format!(
8703 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
8704 activations.len(),
8705 activation_device,
8706 engine.ctx().ordinal()
8707 )
8708 .into());
8709 }
8710 let block_code_stride = bank.out_features * FP8_BLOCK;
8711 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8712 if bank.in_features != blocks * FP8_BLOCK
8713 || bank.code_stride != blocks * block_code_stride
8714 || bank.scale_stride != blocks * block_scale_stride
8715 {
8716 return Err("native TP row bank block-major geometry is inconsistent".into());
8717 }
8718 let expert_code_start = local_expert * bank.code_stride;
8719 let expert_scale_start = local_expert * bank.scale_stride;
8720 let weight = bank.codes.slice(
8721 expert_code_start + block * block_code_stride
8722 ..expert_code_start + (block + 1) * block_code_stride,
8723 );
8724 let scales = bank.scales.slice(
8725 expert_scale_start + block * block_scale_stride
8726 ..expert_scale_start + (block + 1) * block_scale_stride,
8727 );
8728 engine.qmatvec_mmq_fp8_blk_view(
8729 &weight,
8730 &scales,
8731 activations,
8732 1,
8733 FP8_BLOCK,
8734 bank.out_features,
8735 )
8736}
8737
8738fn configure_native_p2p(
8739 ranks: &[Engine],
8740 devices: &[usize],
8741) -> Result<(), Box<dyn std::error::Error>> {
8742 if ranks.len() != devices.len() || ranks.len() < 2 {
8743 return Err("native TP P2P setup requires matching multi-rank devices".into());
8744 }
8745 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8746 if engine.ctx().ordinal() != device {
8747 return Err(format!(
8748 "native TP rank {rank} context device {} != requested device {device}",
8749 engine.ctx().ordinal()
8750 )
8751 .into());
8752 }
8753 }
8754
8755 for src in 0..ranks.len() {
8756 for dst in 0..ranks.len() {
8757 if src == dst {
8758 continue;
8759 }
8760 let mut can_access = 0;
8761 unsafe {
8762 cudarc::driver::sys::cuDeviceCanAccessPeer(
8763 &mut can_access,
8764 ranks[src].ctx().cu_device(),
8765 ranks[dst].ctx().cu_device(),
8766 )
8767 .result()?;
8768 }
8769 if can_access == 0 {
8770 return Err(format!(
8771 "native TP requires P2P, but dev{} cannot access dev{}",
8772 devices[src], devices[dst]
8773 )
8774 .into());
8775 }
8776 ranks[src].ctx().bind_to_thread()?;
8777 let rc =
8778 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8779 use cudarc::driver::sys::cudaError_enum as E;
8780 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8781 return Err(format!(
8782 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8783 devices[src], devices[dst]
8784 )
8785 .into());
8786 }
8787 }
8788 }
8789
8790 for &owner in devices {
8791 for &accessor in devices {
8792 if owner == accessor {
8793 continue;
8794 }
8795 let device = cudarc::driver::result::device::get(owner as i32)?;
8796 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8797 unsafe {
8798 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8799 }
8800 let desc = cudarc::driver::sys::CUmemAccessDesc {
8801 location: cudarc::driver::sys::CUmemLocation {
8802 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8803 id: accessor as i32,
8804 },
8805 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8806 };
8807 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8808 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8809 return Err(format!(
8810 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8811 {rc:?}"
8812 )
8813 .into());
8814 }
8815 }
8816 }
8817
8818 for src in 0..ranks.len() {
8819 for dst in 0..ranks.len() {
8820 if src == dst {
8821 continue;
8822 }
8823 for &words in NATIVE_P2P_PROBE_WORDS {
8824 let expected = (0..words)
8825 .map(|index| {
8826 (index as u32)
8827 .wrapping_mul(0x9e37_79b9)
8828 .wrapping_add(((src as u32) << 16) | dst as u32)
8829 })
8830 .collect::<Vec<_>>();
8831 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8832 let source = ranks[src].htod_u32_v(&expected)?;
8833 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8834 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8835 let actual = ranks[dst].dtoh_u32(&destination)?;
8836 if actual != expected {
8837 let mismatches = actual
8838 .iter()
8839 .zip(&expected)
8840 .filter(|(actual, expected)| actual != expected)
8841 .count();
8842 return Err(format!(
8843 "native TP peer probe dev{}->dev{} failed at {} bytes: \
8844 {mismatches}/{} words differ",
8845 devices[src],
8846 devices[dst],
8847 words * std::mem::size_of::<u32>(),
8848 expected.len()
8849 )
8850 .into());
8851 }
8852 }
8853 }
8854 }
8855 ranks[0].ctx().bind_to_thread()?;
8856 eprintln!(
8857 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8858 directions={} byte_ladder={:?} mismatches=0",
8859 ranks.len() * (ranks.len() - 1),
8860 NATIVE_P2P_PROBE_WORDS
8861 .iter()
8862 .map(|words| words * std::mem::size_of::<u32>())
8863 .collect::<Vec<_>>(),
8864 );
8865 Ok(())
8866}
8867
8868fn validate_activations(
8869 activations: &[f32],
8870 tokens: usize,
8871 in_features: usize,
8872) -> Result<(), String> {
8873 let expected = tokens
8874 .checked_mul(in_features)
8875 .ok_or_else(|| "activation size overflow".to_string())?;
8876 if activations.len() != expected {
8877 return Err(format!(
8878 "activation count {} != {tokens}x{in_features} ({expected})",
8879 activations.len()
8880 ));
8881 }
8882 if !activations.iter().all(|value| value.is_finite()) {
8883 return Err("activations contain a non-finite value".to_string());
8884 }
8885 Ok(())
8886}
8887
8888fn column_shard(
8889 matrix: E4m3BlockMatrix<'_>,
8890 tp: usize,
8891 rank: usize,
8892) -> Result<E4m3BlockMatrix<'_>, String> {
8893 let local_out = matrix.out_features / tp;
8894 let row_start = rank * local_out;
8895 let code_start = row_start * matrix.in_features;
8896 let code_end = code_start + local_out * matrix.in_features;
8897 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8898 let local_scale_rows = local_out / FP8_BLOCK;
8899 let scale_start = rank * local_scale_rows * scale_cols;
8900 let scale_end = scale_start + local_scale_rows * scale_cols;
8901 Ok(E4m3BlockMatrix {
8902 codes: &matrix.codes[code_start..code_end],
8903 scales: &matrix.scales[scale_start..scale_end],
8904 out_features: local_out,
8905 in_features: matrix.in_features,
8906 })
8907}
8908
8909fn row_shard(
8910 matrix: E4m3BlockMatrix<'_>,
8911 tp: usize,
8912 rank: usize,
8913) -> Result<(Vec<u8>, Vec<f32>), String> {
8914 let local_in = matrix.in_features / tp;
8915 let col_start = rank * local_in;
8916 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8917 for row in 0..matrix.out_features {
8918 let start = row * matrix.in_features + col_start;
8919 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8920 }
8921
8922 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8923 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8924 let local_scale_cols = local_in / FP8_BLOCK;
8925 let scale_col_start = rank * local_scale_cols;
8926 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8927 for row in 0..scale_rows {
8928 let start = row * scale_cols + scale_col_start;
8929 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8930 }
8931 Ok((codes, scales))
8932}
8933
8934fn activation_shard(
8935 activations: &[f32],
8936 tokens: usize,
8937 in_features: usize,
8938 tp: usize,
8939 rank: usize,
8940) -> Vec<f32> {
8941 let local_in = in_features / tp;
8942 let col_start = rank * local_in;
8943 let mut shard = Vec::with_capacity(tokens * local_in);
8944 for token in 0..tokens {
8945 let start = token * in_features + col_start;
8946 shard.extend_from_slice(&activations[start..start + local_in]);
8947 }
8948 shard
8949}
8950
8951#[derive(Clone, Copy)]
8971pub struct Nvfp4BlockMatrix<'a> {
8972 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8976 pub in_features: usize,
8977}
8978
8979impl Nvfp4BlockMatrix<'_> {
8980 pub fn validate(&self) -> Result<(), String> {
8981 if self.in_features == 0 || self.out_features == 0 {
8982 return Err("NVFP4 matrix has a zero dimension".to_string());
8983 }
8984 if self.in_features % 64 != 0 {
8985 return Err(format!(
8986 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8987 self.in_features
8988 ));
8989 }
8990 if self.codes.len() != self.out_features * self.in_features / 2 {
8991 return Err(format!(
8992 "NVFP4 code bytes {} != {}x{}/2",
8993 self.codes.len(),
8994 self.out_features,
8995 self.in_features
8996 ));
8997 }
8998 if self.scales.len() != self.out_features * self.in_features / 16 {
8999 return Err(format!(
9000 "NVFP4 scale bytes {} != {}x{}/16",
9001 self.scales.len(),
9002 self.out_features,
9003 self.in_features
9004 ));
9005 }
9006 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9007 return Err(format!(
9008 "NVFP4 macro scale {} is not finite-positive",
9009 self.macro_scale
9010 ));
9011 }
9012 Ok(())
9013 }
9014}
9015
9016#[derive(Clone, Copy)]
9018pub struct Nvfp4ExpertBank<'a> {
9019 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9023 pub out_features: usize,
9024 pub in_features: usize,
9025}
9026
9027impl Nvfp4ExpertBank<'_> {
9028 pub fn validate(&self) -> Result<(), String> {
9029 if self.expert_count == 0 {
9030 return Err("NVFP4 expert bank is empty".to_string());
9031 }
9032 if self.macros.len() != self.expert_count {
9033 return Err(format!(
9034 "NVFP4 bank macros {} != expert count {}",
9035 self.macros.len(),
9036 self.expert_count
9037 ));
9038 }
9039 self.expert(0).map(|_| ())
9040 }
9041
9042 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9043 if expert >= self.expert_count {
9044 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9045 }
9046 let code_stride = self.out_features * self.in_features / 2;
9047 let scale_stride = self.out_features * self.in_features / 16;
9048 if self.codes.len() != self.expert_count * code_stride
9049 || self.scales.len() != self.expert_count * scale_stride
9050 {
9051 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9052 }
9053 let matrix = Nvfp4BlockMatrix {
9054 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9055 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9056 macro_scale: self.macros[expert],
9057 out_features: self.out_features,
9058 in_features: self.in_features,
9059 };
9060 matrix.validate()?;
9061 Ok(matrix)
9062 }
9063}
9064
9065pub struct ResidentNvfp4Rank {
9067 blocks: crate::CudaSlice<u8>,
9068 macro_scale: f32,
9069 out_features: usize,
9070 in_features: usize,
9071 row_bytes: usize,
9072}
9073
9074pub struct ResidentNvfp4ColumnParallel {
9075 ranks: Vec<ResidentNvfp4Rank>,
9076 pub out_features: usize,
9077 pub in_features: usize,
9078}
9079
9080pub struct ResidentNvfp4RowParallel {
9081 ranks: Vec<ResidentNvfp4Rank>,
9082 pub out_features: usize,
9083 pub in_features: usize,
9084}
9085
9086pub struct ResidentTpNvfp4Expert {
9087 gate: ResidentNvfp4ColumnParallel,
9088 up: ResidentNvfp4ColumnParallel,
9089 down: ResidentNvfp4RowParallel,
9090 pub input_width: usize,
9091 pub expert_width: usize,
9092}
9093
9094pub struct ResidentNvfp4ColumnBankRank {
9098 bank: crate::CudaSlice<u8>,
9102 expert_bytes: usize,
9103 local_out: usize,
9104 in_features: usize,
9105 row_bytes: usize,
9106}
9107
9108impl ResidentNvfp4ColumnBankRank {
9109 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9110 self.bank
9111 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9112 }
9113}
9114
9115pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9121
9122pub struct ResidentNvfp4RowBankRank {
9123 bank: crate::CudaSlice<u8>,
9125 expert_bytes: usize,
9126 device_rank: usize, out_features: usize,
9128 local_in: usize,
9129 row_bytes: usize,
9130}
9131
9132impl ResidentNvfp4RowBankRank {
9133 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9134 self.bank
9135 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9136 }
9137}
9138
9139impl ResidentNvfp4TensorParallel {
9140 pub(crate) fn device_workspace_handle(
9141 &self,
9142 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
9143 &self.device_workspace
9144 }
9145}
9146
9147pub struct ResidentNvfp4TensorParallel {
9148 gate: Vec<ResidentNvfp4ColumnBankRank>,
9149 up: Vec<ResidentNvfp4ColumnBankRank>,
9150 down: Vec<ResidentNvfp4RowBankRank>,
9151 macros_gate: Vec<f32>,
9152 macros_up: Vec<f32>,
9153 macros_down: Vec<f32>,
9154 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
9158 macros_up_dev: Vec<crate::CudaSlice<f32>>,
9159 macros_down_dev: Vec<crate::CudaSlice<f32>>,
9160 pub expert_count: usize,
9161 pub input_width: usize,
9162 pub expert_width: usize,
9163 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
9166 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
9170 pub(crate) ep2: bool,
9174}
9175
9176struct RoutesGraph {
9183 exec: cudarc::driver::sys::CUgraphExec,
9184 parent: cudarc::driver::sys::CUgraph,
9185 _children: Vec<cudarc::driver::CudaGraph>,
9186}
9187unsafe impl Send for RoutesGraph {}
9190
9191impl Drop for RoutesGraph {
9192 fn drop(&mut self) {
9193 unsafe {
9194 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
9195 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
9196 }
9197 }
9198}
9199
9200impl Nvfp4DeviceRoutesWorkspace {
9201 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
9202 self.in_stage_e.as_ref()
9203 }
9204 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9205 self.in_stage_e.as_mut()
9206 }
9207 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9208 self.out_stage_e.as_mut()
9209 }
9210 pub(crate) fn arm_stages(
9212 &mut self,
9213 e: &Engine,
9214 width: usize,
9215 n_sel: usize,
9216 ) -> Result<(), Box<dyn std::error::Error>> {
9217 let _main = e.gpu.enter_main()?;
9218 if self.in_stage_e.is_none() {
9219 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9220 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9221 }
9222 if self.dev_route_e.is_none() {
9223 self.dev_route_e = Some((
9224 e.htod_i32(&vec![0i32; n_sel])?,
9225 e.htod(&vec![0.0f32; n_sel])?,
9226 ));
9227 }
9228 Ok(())
9229 }
9230
9231 pub(crate) fn in_and_out_stages_mut(
9233 &mut self,
9234 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9235 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9236 (Some(input), Some(output)) => Some((input, output)),
9237 _ => None,
9238 }
9239 }
9240 pub(crate) fn dev_route_e_mut(
9241 &mut self,
9242 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9243 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9244 }
9245}
9246
9247pub struct Nvfp4DeviceRoutesWorkspace {
9248 gate_out: Vec<crate::CudaSlice<f32>>,
9251 up_out: Vec<crate::CudaSlice<f32>>,
9252 act_q: Vec<crate::CudaSlice<i8>>,
9253 act_d: Vec<crate::CudaSlice<f32>>,
9254 sel: Vec<crate::CudaSlice<i32>>,
9255 partial: Vec<crate::CudaSlice<f32>>,
9256 accumulator: Vec<crate::CudaSlice<f32>>,
9257 combine_w: Vec<crate::CudaSlice<f32>>,
9259 route_w: Vec<crate::CudaSlice<f32>>,
9262 in_q: Vec<crate::CudaSlice<i8>>,
9265 in_d: Vec<crate::CudaSlice<f32>>,
9266 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9270 prestaged: bool,
9273 rank1_routed: bool,
9276 fence_flags_raw: u64,
9280 fence_ticket: u32,
9281 ev_input: Option<(CudaEvent, usize)>,
9283 in_stage_e: Option<crate::CudaSlice<f32>>,
9286 out_stage_e: Option<crate::CudaSlice<f32>>,
9287 routes_graph: Option<RoutesGraph>,
9288 raw_dev_route_e: Option<(u64, u64)>,
9290 raw_combine: Option<(u64, u64, u64, u64)>,
9291 raw_input: Vec<u64>,
9292 raw_sel: Vec<u64>,
9293 raw_route_w: Vec<u64>,
9294 remote: crate::CudaSlice<f32>,
9295 combined: crate::CudaSlice<f32>,
9296 n_sel: usize,
9297 input: Vec<crate::CudaSlice<f32>>,
9301 ev_rank: Vec<CudaEvent>,
9302 ev_done: Option<CudaEvent>,
9303 ev_entry: Option<(CudaEvent, usize)>,
9304}
9305
9306struct ResidentNvfp4EpRank {
9308 gate: Vec<crate::CudaSlice<u8>>,
9309 up: Vec<crate::CudaSlice<u8>>,
9310 down: Vec<crate::CudaSlice<u8>>,
9311 #[allow(dead_code)]
9312 expert_range: Range<usize>,
9313}
9314
9315pub struct ResidentNvfp4ExpertParallel {
9316 ranks: Vec<ResidentNvfp4EpRank>,
9317 macros_gate: Vec<f32>,
9318 macros_up: Vec<f32>,
9319 macros_down: Vec<f32>,
9320 pub expert_count: usize,
9321 pub input_width: usize,
9322 pub expert_width: usize,
9323 gate_row_bytes: usize,
9324 down_row_bytes: usize,
9325}
9326
9327fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9328 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9329 matrix.codes,
9330 matrix.scales,
9331 matrix.out_features,
9332 matrix.in_features,
9333 )
9334}
9335
9336fn nvfp4_row_bytes(in_features: usize) -> usize {
9337 in_features / 64 * 36 }
9339
9340pub(crate) fn fuse_rope_append_on() -> bool {
9346 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9347 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9348}
9349
9350pub(crate) fn no_local_shadow_on() -> bool {
9351 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9352 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9353}
9354
9355fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9363 let row_bytes = nvfp4_row_bytes(in_features);
9364 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
9365 let n_slots = in_features / 32;
9366 let mut out = Vec::with_capacity(v1.len());
9367 for row in 0..out_features {
9368 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
9369 for g in 0..n_slots {
9370 let (sblk, h) = (g / 2, g % 2);
9371 let b = &r[sblk * 36..sblk * 36 + 36];
9372 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
9373 }
9374 for g in 0..n_slots {
9375 let (sblk, h) = (g / 2, g % 2);
9376 let b = &r[sblk * 36..sblk * 36 + 36];
9377 out.push(b[2 * h]);
9378 out.push(b[2 * h + 1]);
9379 }
9380 }
9381 out
9382}
9383
9384fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
9388 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9389 let v1 = nvfp4_repack_matrix(matrix);
9390 if slot_major {
9391 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9392 } else {
9393 v1
9394 }
9395}
9396
9397fn nvfp4_column_shard<'a>(
9400 matrix: Nvfp4BlockMatrix<'a>,
9401 tp: usize,
9402 rank: usize,
9403) -> Result<Nvfp4BlockMatrix<'a>, String> {
9404 if matrix.out_features % tp != 0 {
9405 return Err(format!(
9406 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
9407 matrix.out_features
9408 ));
9409 }
9410 let local_out = matrix.out_features / tp;
9411 let code_row = matrix.in_features / 2;
9412 let scale_row = matrix.in_features / 16;
9413 Ok(Nvfp4BlockMatrix {
9414 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
9415 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
9416 macro_scale: matrix.macro_scale,
9417 out_features: local_out,
9418 in_features: matrix.in_features,
9419 })
9420}
9421
9422fn nvfp4_row_shard(
9425 matrix: Nvfp4BlockMatrix<'_>,
9426 tp: usize,
9427 rank: usize,
9428) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
9429 if matrix.in_features % tp != 0 {
9430 return Err(format!(
9431 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
9432 matrix.in_features
9433 ));
9434 }
9435 let local_in = matrix.in_features / tp;
9436 if local_in % 64 != 0 {
9437 return Err(format!(
9438 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
9439 ));
9440 }
9441 let code_row = matrix.in_features / 2;
9442 let scale_row = matrix.in_features / 16;
9443 let local_code = local_in / 2;
9444 let local_scale = local_in / 16;
9445 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
9446 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
9447 for row in 0..matrix.out_features {
9448 let code_start = row * code_row + rank * local_code;
9449 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
9450 let scale_start = row * scale_row + rank * local_scale;
9451 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
9452 }
9453 Ok((codes, scales, local_in))
9454}
9455
9456fn run_rank_nvfp4(
9460 engine: &Engine,
9461 matrix: Nvfp4BlockMatrix<'_>,
9462 activations: &[f32],
9463 tokens: usize,
9464) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9465 matrix.validate()?;
9466 validate_activations(activations, tokens, matrix.in_features)?;
9467 let _main = engine.gpu.enter_main()?;
9468 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
9469 let activations = engine.htod(activations)?;
9470 let output = engine.qmatvec_nvfp4_fast(
9471 &blocks.slice(0..blocks.len()),
9472 &activations,
9473 tokens,
9474 matrix.in_features,
9475 matrix.out_features,
9476 nvfp4_row_bytes(matrix.in_features),
9477 )?;
9478 engine.dtoh(&output)
9479}
9480
9481fn upload_rank_nvfp4(
9482 engine: &Engine,
9483 matrix: Nvfp4BlockMatrix<'_>,
9484) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
9485 matrix.validate()?;
9486 let _main = engine.gpu.enter_main()?;
9487 Ok(ResidentNvfp4Rank {
9488 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
9489 macro_scale: matrix.macro_scale,
9490 out_features: matrix.out_features,
9491 in_features: matrix.in_features,
9492 row_bytes: nvfp4_row_bytes(matrix.in_features),
9493 })
9494}
9495
9496fn run_resident_rank_nvfp4(
9497 engine: &Engine,
9498 rank: &ResidentNvfp4Rank,
9499 activations: &[f32],
9500 tokens: usize,
9501) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9502 validate_activations(activations, tokens, rank.in_features)?;
9503 let _main = engine.gpu.enter_main()?;
9504 let activations = engine.htod(activations)?;
9505 let output = engine.qmatvec_nvfp4_fast(
9506 &rank.blocks.slice(0..rank.blocks.len()),
9507 &activations,
9508 tokens,
9509 rank.in_features,
9510 rank.out_features,
9511 rank.row_bytes,
9512 )?;
9513 engine.dtoh(&output)
9514}
9515
9516fn apply_macro(values: &mut [f32], macro_scale: f32) {
9517 for value in values.iter_mut() {
9518 *value *= macro_scale;
9519 }
9520}
9521
9522impl TpE4m3HostBounce {
9523 pub fn full_nvfp4(
9525 &self,
9526 matrix: Nvfp4BlockMatrix<'_>,
9527 activations: &[f32],
9528 tokens: usize,
9529 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9530 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
9531 apply_macro(&mut output, matrix.macro_scale);
9532 Ok(output)
9533 }
9534
9535 pub fn column_parallel_nvfp4(
9538 &self,
9539 matrix: Nvfp4BlockMatrix<'_>,
9540 activations: &[f32],
9541 tokens: usize,
9542 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
9543 matrix.validate()?;
9544 validate_activations(activations, tokens, matrix.in_features)?;
9545 let tp = self.ranks.len();
9546 let local_out = matrix.out_features / tp;
9547 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9548 let mut rank_outputs = Vec::with_capacity(tp);
9549 for (rank_index, rank) in self.ranks.iter().enumerate() {
9550 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
9551 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
9552 let row_start = rank_index * local_out;
9553 for token in 0..tokens {
9554 gathered[token * matrix.out_features + row_start
9555 ..token * matrix.out_features + row_start + local_out]
9556 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9557 }
9558 rank_outputs.push(output);
9559 }
9560 apply_macro(&mut gathered, matrix.macro_scale);
9561 Ok(ColumnParallelResult {
9562 gathered,
9563 rank_outputs,
9564 })
9565 }
9566
9567 pub fn row_parallel_nvfp4(
9570 &self,
9571 matrix: Nvfp4BlockMatrix<'_>,
9572 activations: &[f32],
9573 tokens: usize,
9574 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
9575 matrix.validate()?;
9576 validate_activations(activations, tokens, matrix.in_features)?;
9577 let tp = self.ranks.len();
9578 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9579 let mut rank_partials = Vec::with_capacity(tp);
9580 for (rank_index, rank) in self.ranks.iter().enumerate() {
9581 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
9582 let local_activations =
9583 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9584 let shard = Nvfp4BlockMatrix {
9585 codes: &codes,
9586 scales: &scales,
9587 macro_scale: matrix.macro_scale,
9588 out_features: matrix.out_features,
9589 in_features: local_in,
9590 };
9591 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
9592 for (sum, value) in reduced.iter_mut().zip(&partial) {
9593 *sum += *value;
9594 }
9595 rank_partials.push(partial);
9596 }
9597 apply_macro(&mut reduced, matrix.macro_scale);
9598 Ok(RowParallelResult {
9599 reduced,
9600 rank_partials,
9601 })
9602 }
9603
9604 pub fn upload_expert_nvfp4(
9605 &self,
9606 gate: Nvfp4BlockMatrix<'_>,
9607 up: Nvfp4BlockMatrix<'_>,
9608 down: Nvfp4BlockMatrix<'_>,
9609 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
9610 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9611 return Err("NVFP4 TP expert gate/up dimensions differ".into());
9612 }
9613 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9614 return Err(format!(
9615 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
9616 down.out_features, down.in_features, gate.out_features, gate.in_features
9617 )
9618 .into());
9619 }
9620 let tp = self.ranks.len();
9621 let mut gate_ranks = Vec::with_capacity(tp);
9622 let mut up_ranks = Vec::with_capacity(tp);
9623 let mut down_ranks = Vec::with_capacity(tp);
9624 for (rank_index, engine) in self.ranks.iter().enumerate() {
9625 gate_ranks.push(upload_rank_nvfp4(
9626 engine,
9627 nvfp4_column_shard(gate, tp, rank_index)?,
9628 )?);
9629 up_ranks.push(upload_rank_nvfp4(
9630 engine,
9631 nvfp4_column_shard(up, tp, rank_index)?,
9632 )?);
9633 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
9634 down_ranks.push(upload_rank_nvfp4(
9635 engine,
9636 Nvfp4BlockMatrix {
9637 codes: &codes,
9638 scales: &scales,
9639 macro_scale: down.macro_scale,
9640 out_features: down.out_features,
9641 in_features: local_in,
9642 },
9643 )?);
9644 }
9645 Ok(ResidentTpNvfp4Expert {
9646 gate: ResidentNvfp4ColumnParallel {
9647 ranks: gate_ranks,
9648 out_features: gate.out_features,
9649 in_features: gate.in_features,
9650 },
9651 up: ResidentNvfp4ColumnParallel {
9652 ranks: up_ranks,
9653 out_features: up.out_features,
9654 in_features: up.in_features,
9655 },
9656 down: ResidentNvfp4RowParallel {
9657 ranks: down_ranks,
9658 out_features: down.out_features,
9659 in_features: down.in_features,
9660 },
9661 input_width: gate.in_features,
9662 expert_width: gate.out_features,
9663 })
9664 }
9665
9666 fn column_parallel_resident_nvfp4(
9667 &self,
9668 matrix: &ResidentNvfp4ColumnParallel,
9669 activations: &[f32],
9670 tokens: usize,
9671 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9672 validate_activations(activations, tokens, matrix.in_features)?;
9673 let local_out = matrix.out_features / self.ranks.len();
9674 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9675 let mut macro_scale = None;
9676 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9677 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
9678 let row_start = rank_index * local_out;
9679 for token in 0..tokens {
9680 gathered[token * matrix.out_features + row_start
9681 ..token * matrix.out_features + row_start + local_out]
9682 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9683 }
9684 macro_scale = Some(shard.macro_scale);
9685 }
9686 apply_macro(
9687 &mut gathered,
9688 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
9689 );
9690 Ok(gathered)
9691 }
9692
9693 fn row_parallel_resident_nvfp4(
9694 &self,
9695 matrix: &ResidentNvfp4RowParallel,
9696 activations: &[f32],
9697 tokens: usize,
9698 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9699 validate_activations(activations, tokens, matrix.in_features)?;
9700 let tp = self.ranks.len();
9701 let local_in = matrix.in_features / tp;
9702 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9703 let mut macro_scale = None;
9704 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9705 if shard.in_features != local_in {
9706 return Err(format!(
9707 "NVFP4 resident row shard in_features {} != expected {local_in}",
9708 shard.in_features
9709 )
9710 .into());
9711 }
9712 let local_activations =
9713 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9714 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9715 for (sum, value) in reduced.iter_mut().zip(&partial) {
9716 *sum += *value;
9717 }
9718 macro_scale = Some(shard.macro_scale);
9719 }
9720 apply_macro(
9721 &mut reduced,
9722 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9723 );
9724 Ok(reduced)
9725 }
9726
9727 pub fn run_expert_nvfp4(
9728 &self,
9729 expert: &ResidentTpNvfp4Expert,
9730 input: &[f32],
9731 tokens: usize,
9732 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9733 validate_activations(input, tokens, expert.input_width)?;
9734 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9735 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9736 let activated: Vec<f32> = gate
9737 .iter()
9738 .zip(&up)
9739 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9740 .collect();
9741 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9742 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9743 }
9744
9745 pub fn upload_tensor_parallel_nvfp4(
9747 &self,
9748 gate: Nvfp4ExpertBank<'_>,
9749 up: Nvfp4ExpertBank<'_>,
9750 down: Nvfp4ExpertBank<'_>,
9751 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9752 gate.validate()?;
9753 up.validate()?;
9754 down.validate()?;
9755 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9756 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9757 }
9758 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9759 return Err("NVFP4 TP gate/up dimensions differ".into());
9760 }
9761 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9762 return Err(format!(
9763 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9764 down.out_features, down.in_features, gate.out_features, gate.in_features
9765 )
9766 .into());
9767 }
9768 let tp = self.ranks.len();
9769 if gate.out_features % tp != 0 {
9770 return Err(format!(
9771 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9772 gate.out_features
9773 )
9774 .into());
9775 }
9776 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9777 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9778 {
9779 return Err(format!(
9780 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9781 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9782 down.in_features
9783 )
9784 .into());
9785 }
9786 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9787 return Err(format!(
9788 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9789 ({NVFP4_CANONICAL_ROW_SHARDS})"
9790 )
9791 .into());
9792 }
9793
9794 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9795 let mut gate_ranks = Vec::with_capacity(tp);
9796 let mut up_ranks = Vec::with_capacity(tp);
9797 let mut macros_gate_dev = Vec::with_capacity(tp);
9798 let mut macros_up_dev = Vec::with_capacity(tp);
9799 let mut macros_down_dev = Vec::with_capacity(tp);
9800 for (rank_index, engine) in self.ranks.iter().enumerate() {
9801 let _main = engine.gpu.enter_main()?;
9802 let mut gate_host: Vec<u8> = Vec::new();
9808 let mut up_host: Vec<u8> = Vec::new();
9809 let mut owned = 0usize;
9810 for expert in 0..gate.expert_count {
9811 if ep2 {
9812 if expert % 2 != rank_index {
9813 continue;
9814 }
9815 owned += 1;
9816 gate_host
9817 .extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?, true));
9818 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?, true));
9819 } else {
9820 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9821 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, false));
9822 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9823 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, false));
9824 }
9825 }
9826 let bank_experts = if ep2 { owned } else { gate.expert_count };
9827 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9828 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9829 let local_out = if ep2 {
9830 gate.out_features
9831 } else {
9832 gate.out_features / tp
9833 };
9834 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9835 bank: engine.htod_bytes(&gate_host)?,
9836 expert_bytes: gate_expert_bytes,
9837 local_out,
9838 in_features: gate.in_features,
9839 row_bytes: nvfp4_row_bytes(gate.in_features),
9840 });
9841 up_ranks.push(ResidentNvfp4ColumnBankRank {
9842 bank: engine.htod_bytes(&up_host)?,
9843 expert_bytes: up_expert_bytes,
9844 local_out,
9845 in_features: up.in_features,
9846 row_bytes: nvfp4_row_bytes(up.in_features),
9847 });
9848 macros_gate_dev.push(engine.htod(gate.macros)?);
9849 macros_up_dev.push(engine.htod(up.macros)?);
9850 macros_down_dev.push(engine.htod(down.macros)?);
9851 }
9852 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9856 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9857 let device_rank = shard_index % tp;
9858 let engine = &self.ranks[device_rank];
9859 let _main = engine.gpu.enter_main()?;
9860 let mut down_host: Vec<u8> = Vec::new();
9861 let mut owned = 0usize;
9862 for expert in 0..down.expert_count {
9863 let down_matrix = down.expert(expert)?;
9864 if ep2 {
9865 if expert % 2 != device_rank {
9868 continue;
9869 }
9870 owned += 1;
9871 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix, true));
9872 } else {
9873 let (codes, scales, local_in) =
9874 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9875 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
9876 Nvfp4BlockMatrix {
9877 codes: &codes,
9878 scales: &scales,
9879 macro_scale: down_matrix.macro_scale,
9880 out_features: down_matrix.out_features,
9881 in_features: local_in,
9882 },
9883 false,
9884 ));
9885 }
9886 }
9887 let bank_experts = if ep2 { owned } else { down.expert_count };
9888 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9889 let local_in = if ep2 {
9890 down.in_features
9891 } else {
9892 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9893 };
9894 down_ranks.push(ResidentNvfp4RowBankRank {
9895 bank: engine.htod_bytes(&down_host)?,
9896 expert_bytes: down_expert_bytes,
9897 device_rank,
9898 out_features: down.out_features,
9899 local_in,
9900 row_bytes: nvfp4_row_bytes(local_in),
9901 });
9902 }
9903 Ok(ResidentNvfp4TensorParallel {
9904 gate: gate_ranks,
9905 up: up_ranks,
9906 down: down_ranks,
9907 macros_gate: gate.macros.to_vec(),
9908 macros_up: up.macros.to_vec(),
9909 macros_down: down.macros.to_vec(),
9910 macros_gate_dev,
9911 macros_up_dev,
9912 macros_down_dev,
9913 expert_count: gate.expert_count,
9914 input_width: gate.in_features,
9915 expert_width: gate.out_features,
9916 device_workspace: std::sync::Mutex::new(None),
9917 prime_tables: std::sync::Mutex::new(Vec::new()),
9918 ep2,
9919 })
9920 }
9921
9922 fn run_full_bank_expert_nvfp4(
9926 &self,
9927 ranks: &[ResidentNvfp4ColumnBankRank],
9928 macros: &[f32],
9929 expert: usize,
9930 input: &[f32],
9931 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9932 let owner = expert & 1;
9933 let slot = expert >> 1;
9934 let bank = ranks
9935 .get(owner)
9936 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9937 let engine = &self.ranks[owner];
9938 let _main = engine.gpu.enter_main()?;
9939 let activations = engine.htod(input)?;
9940 let output = engine.qmatvec_nvfp4_fast_v2(
9943 &bank.expert(slot),
9944 &activations,
9945 1,
9946 bank.in_features,
9947 bank.local_out,
9948 bank.row_bytes,
9949 )?;
9950 let mut out = engine.dtoh(&output)?;
9951 apply_macro(&mut out, macros[expert]);
9952 Ok(out)
9953 }
9954
9955 fn run_full_down_expert_nvfp4(
9958 &self,
9959 shards: &[ResidentNvfp4RowBankRank],
9960 macros: &[f32],
9961 expert: usize,
9962 input: &[f32],
9963 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9964 let owner = expert & 1;
9965 let slot = expert >> 1;
9966 let shard = shards
9967 .get(owner)
9968 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9969 let engine = &self.ranks[owner];
9970 let _main = engine.gpu.enter_main()?;
9971 let activations = engine.htod(input)?;
9972 let output = engine.qmatvec_nvfp4_fast_v2(
9974 &shard.expert(slot),
9975 &activations,
9976 1,
9977 shard.local_in,
9978 shard.out_features,
9979 shard.row_bytes,
9980 )?;
9981 let mut out = engine.dtoh(&output)?;
9982 apply_macro(&mut out, macros[expert]);
9983 Ok(out)
9984 }
9985
9986 fn run_column_bank_expert_nvfp4(
9987 &self,
9988 ranks: &[ResidentNvfp4ColumnBankRank],
9989 macros: &[f32],
9990 expert: usize,
9991 input: &[f32],
9992 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9993 let local_out = ranks
9994 .first()
9995 .ok_or("NVFP4 TP column bank has no ranks")?
9996 .local_out;
9997 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9998 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9999 let _main = engine.gpu.enter_main()?;
10000 let activations = engine.htod(input)?;
10001 let output = engine.qmatvec_nvfp4_fast(
10002 &bank.expert(expert),
10003 &activations,
10004 1,
10005 bank.in_features,
10006 bank.local_out,
10007 bank.row_bytes,
10008 )?;
10009 let output = engine.dtoh(&output)?;
10010 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
10011 }
10012 apply_macro(&mut gathered, macros[expert]);
10013 Ok(gathered)
10014 }
10015
10016 fn run_row_bank_expert_nvfp4(
10020 &self,
10021 shards: &[ResidentNvfp4RowBankRank],
10022 macros: &[f32],
10023 expert: usize,
10024 input: &[f32],
10025 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10026 let out_features = shards
10027 .first()
10028 .ok_or("NVFP4 TP row bank has no canonical shards")?
10029 .out_features;
10030 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
10031 let mut reduced = vec![0.0f32; out_features];
10032 for (shard_index, shard) in shards.iter().enumerate() {
10033 let engine = self
10034 .ranks
10035 .get(shard.device_rank)
10036 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
10037 let _main = engine.gpu.enter_main()?;
10038 let local_activations =
10039 activation_shard(input, 1, in_features, shards.len(), shard_index);
10040 let activations = engine.htod(&local_activations)?;
10041 let output = engine.qmatvec_nvfp4_fast(
10042 &shard.expert(expert),
10043 &activations,
10044 1,
10045 shard.local_in,
10046 shard.out_features,
10047 shard.row_bytes,
10048 )?;
10049 let partial = engine.dtoh(&output)?;
10050 for (sum, value) in reduced.iter_mut().zip(&partial) {
10051 *sum += *value;
10052 }
10053 }
10054 apply_macro(&mut reduced, macros[expert]);
10055 Ok(reduced)
10056 }
10057
10058 pub fn upload_expert_parallel_nvfp4(
10062 &self,
10063 gate: Nvfp4ExpertBank<'_>,
10064 up: Nvfp4ExpertBank<'_>,
10065 down: Nvfp4ExpertBank<'_>,
10066 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10067 gate.validate()?;
10068 up.validate()?;
10069 down.validate()?;
10070 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10071 return Err("NVFP4 EP gate/up/down expert counts differ".into());
10072 }
10073 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10074 return Err("NVFP4 EP gate/up dimensions differ".into());
10075 }
10076 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10077 return Err(format!(
10078 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
10079 down.out_features, down.in_features, gate.out_features, gate.in_features
10080 )
10081 .into());
10082 }
10083 let world = self.ranks.len();
10084 if gate.expert_count % world != 0 {
10085 return Err(format!(
10086 "NVFP4 EP expert count {} is not divisible by {world} ranks",
10087 gate.expert_count
10088 )
10089 .into());
10090 }
10091 let experts_per_rank = gate.expert_count / world;
10092 let mut ranks = Vec::with_capacity(world);
10093 for (rank_index, engine) in self.ranks.iter().enumerate() {
10094 let _main = engine.gpu.enter_main()?;
10095 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10096 let mut gate_experts = Vec::with_capacity(experts_per_rank);
10097 let mut up_experts = Vec::with_capacity(experts_per_rank);
10098 let mut down_experts = Vec::with_capacity(experts_per_rank);
10099 for expert in expert_range.clone() {
10100 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
10101 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
10102 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
10103 }
10104 ranks.push(ResidentNvfp4EpRank {
10105 gate: gate_experts,
10106 up: up_experts,
10107 down: down_experts,
10108 expert_range,
10109 });
10110 }
10111 Ok(ResidentNvfp4ExpertParallel {
10112 ranks,
10113 macros_gate: gate.macros.to_vec(),
10114 macros_up: up.macros.to_vec(),
10115 macros_down: down.macros.to_vec(),
10116 expert_count: gate.expert_count,
10117 input_width: gate.in_features,
10118 expert_width: gate.out_features,
10119 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
10120 down_row_bytes: nvfp4_row_bytes(down.in_features),
10121 })
10122 }
10123
10124 #[allow(clippy::too_many_arguments)]
10130 pub fn run_routed_experts_nvfp4(
10131 &self,
10132 experts: &ResidentNvfp4ExpertParallel,
10133 input: &[f32],
10134 tokens: usize,
10135 selected: &[usize],
10136 route_weights: &[f32],
10137 experts_per_token: usize,
10138 activation_limit: Option<f32>,
10139 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10140 validate_activations(input, tokens, experts.input_width)?;
10141 let pairs = tokens
10142 .checked_mul(experts_per_token)
10143 .ok_or("NVFP4 EP route count overflow")?;
10144 if selected.len() != pairs || route_weights.len() != pairs {
10145 return Err(format!(
10146 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
10147 {experts_per_token} ({pairs})",
10148 selected.len(),
10149 route_weights.len(),
10150 )
10151 .into());
10152 }
10153 if !route_weights.iter().all(|weight| weight.is_finite()) {
10154 return Err("NVFP4 EP route weights contain a non-finite value".into());
10155 }
10156 let experts_per_rank = experts.expert_count / experts.ranks.len();
10157 let mut output = vec![0.0f32; tokens * experts.input_width];
10158 for token in 0..tokens {
10159 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
10160 for slot in 0..experts_per_token {
10161 let pair = token * experts_per_token + slot;
10162 let expert = selected[pair];
10163 if expert >= experts.expert_count {
10164 return Err(format!(
10165 "NVFP4 EP selected expert {expert} outside 0..{}",
10166 experts.expert_count
10167 )
10168 .into());
10169 }
10170 let owner = expert / experts_per_rank;
10171 let local = expert - owner * experts_per_rank;
10172 let rank = &experts.ranks[owner];
10173 let engine = &self.ranks[owner];
10174 let _main = engine.gpu.enter_main()?;
10175 let device_input = engine.htod(input_row)?;
10176 let gate_out = engine.qmatvec_nvfp4_fast(
10177 &rank.gate[local].slice(0..rank.gate[local].len()),
10178 &device_input,
10179 1,
10180 experts.input_width,
10181 experts.expert_width,
10182 experts.gate_row_bytes,
10183 )?;
10184 let up_out = engine.qmatvec_nvfp4_fast(
10185 &rank.up[local].slice(0..rank.up[local].len()),
10186 &device_input,
10187 1,
10188 experts.input_width,
10189 experts.expert_width,
10190 experts.gate_row_bytes,
10191 )?;
10192 let mut gate_host = engine.dtoh(&gate_out)?;
10193 let mut up_host = engine.dtoh(&up_out)?;
10194 apply_macro(&mut gate_host, experts.macros_gate[expert]);
10195 apply_macro(&mut up_host, experts.macros_up[expert]);
10196 let activated: Vec<f32> = gate_host
10197 .iter()
10198 .zip(&up_host)
10199 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
10200 .collect();
10201 let device_activated = engine.htod(&activated)?;
10202 let down_out = engine.qmatvec_nvfp4_fast(
10203 &rank.down[local].slice(0..rank.down[local].len()),
10204 &device_activated,
10205 1,
10206 experts.expert_width,
10207 experts.input_width,
10208 experts.down_row_bytes,
10209 )?;
10210 let mut down_host = engine.dtoh(&down_out)?;
10211 apply_macro(&mut down_host, experts.macros_down[expert]);
10212 let weight = route_weights[pair];
10213 for (sum, value) in output
10214 [token * experts.input_width..(token + 1) * experts.input_width]
10215 .iter_mut()
10216 .zip(down_host)
10217 {
10218 *sum += weight * value;
10219 }
10220 }
10221 }
10222 Ok(output)
10223 }
10224
10225 pub fn run_tensor_parallel_routes_nvfp4_device(
10239 &self,
10240 experts: &ResidentNvfp4TensorParallel,
10241 input: &[f32],
10242 selected: &[usize],
10243 route_weights: &[f32],
10244 experts_per_token: usize,
10245 activation_limit: Option<f32>,
10246 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10247 validate_activations(input, 1, experts.input_width)?;
10248 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10249 return Err(format!(
10250 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
10251 selected.len(),
10252 route_weights.len(),
10253 )
10254 .into());
10255 }
10256 if !route_weights.iter().all(|weight| weight.is_finite()) {
10257 return Err("NVFP4 device route weights contain a non-finite value".into());
10258 }
10259 let world = self.ranks.len();
10260 if world != NVFP4_CANONICAL_ROW_SHARDS {
10261 return Err(format!(
10262 "NVFP4 device routes require world == canonical shard grid \
10263 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10264 )
10265 .into());
10266 }
10267 let local_out = if experts.ep2 {
10268 experts.expert_width
10269 } else {
10270 experts.expert_width / world
10271 };
10272
10273 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10277 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10278 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10279 let started = timing.then(std::time::Instant::now);
10280
10281 let n_sel = experts_per_token;
10282 let mut workspace_guard = experts
10283 .device_workspace
10284 .lock()
10285 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10286 if workspace_guard.is_none() {
10287 let mut gate_out = Vec::with_capacity(world);
10288 let mut up_out = Vec::with_capacity(world);
10289 let mut act_q = Vec::with_capacity(world);
10290 let mut act_d = Vec::with_capacity(world);
10291 let mut sel = Vec::with_capacity(world);
10292 let mut partial = Vec::with_capacity(world);
10293 let mut accumulator = Vec::with_capacity(world);
10294 let mut combine_w = Vec::with_capacity(world);
10295 let mut route_w = Vec::with_capacity(world);
10296 let mut in_q = Vec::with_capacity(world);
10297 let mut in_d = Vec::with_capacity(world);
10298 let mut input = Vec::with_capacity(world);
10299 let mut ev_rank = Vec::with_capacity(world);
10300 let moe_direct = moe_direct_on();
10301 for (rank, engine) in self.ranks.iter().enumerate() {
10302 let _main = engine.gpu.enter_main()?;
10303 gate_out.push(engine.uninit(n_sel * local_out)?);
10304 up_out.push(engine.uninit(n_sel * local_out)?);
10305 act_q.push(engine.uninit_i8(n_sel * local_out)?);
10306 act_d.push(engine.uninit(n_sel * local_out / 32)?);
10307 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
10308 partial.push(engine.uninit(n_sel * experts.input_width)?);
10309 if moe_direct && rank != 0 {
10311 let root = &self.ranks[0];
10312 let _root_main = root.gpu.enter_main()?;
10313 accumulator.push(root.zeros(experts.input_width)?);
10314 } else {
10315 accumulator.push(engine.zeros(experts.input_width)?);
10316 }
10317 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10318 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10319 in_q.push(engine.uninit_i8(experts.input_width)?);
10320 in_d.push(engine.uninit(experts.input_width / 32)?);
10321 input.push(engine.uninit(experts.input_width)?);
10322 ev_rank.push(engine.ctx().new_event(None)?);
10323 }
10324 let root = &self.ranks[0];
10325 let _main = root.gpu.enter_main()?;
10326 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
10327 prestaged: false,
10328 rank1_routed: false,
10329 ev_input: None,
10330 fence_flags_raw: 0,
10331 fence_ticket: 0,
10332 gate_out,
10333 up_out,
10334 act_q,
10335 act_d,
10336 sel,
10337 partial,
10338 accumulator,
10339 combine_w,
10340 route_w,
10341 in_q,
10342 in_d,
10343 dev_route_e: None,
10344 in_stage_e: None,
10345 out_stage_e: None,
10346 routes_graph: None,
10347 raw_dev_route_e: None,
10348 raw_combine: None,
10349 raw_input: Vec::new(),
10350 raw_sel: Vec::new(),
10351 raw_route_w: Vec::new(),
10352 remote: root.uninit(experts.input_width)?,
10353 combined: root.uninit(experts.input_width)?,
10354 n_sel,
10355 input,
10356 ev_rank,
10357 ev_done: Some(root.ctx().new_event(None)?),
10358 ev_entry: None,
10359 });
10360 }
10361 let workspace = workspace_guard
10362 .as_mut()
10363 .expect("NVFP4 device routes workspace initialized above");
10364 if experts.ep2 {
10367 return Ok(vec![0.0f32; experts.input_width]);
10368 }
10369 if workspace.n_sel != n_sel {
10370 return Err(format!(
10371 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10372 workspace.n_sel
10373 )
10374 .into());
10375 }
10376 for &expert in selected {
10377 if expert >= experts.expert_count {
10378 return Err(format!(
10379 "NVFP4 device selected expert {expert} outside 0..{}",
10380 experts.expert_count
10381 )
10382 .into());
10383 }
10384 }
10385 let sel_i32 = selected
10386 .iter()
10387 .map(|&expert| expert as i32)
10388 .collect::<Vec<_>>();
10389
10390 for (rank_index, engine) in self.ranks.iter().enumerate() {
10397 let _main = engine.gpu.enter_main()?;
10398 let device_input = engine.htod(input)?;
10399 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
10400 engine.quantize_q8_1_into(
10401 &device_input,
10402 1,
10403 experts.input_width,
10404 &mut in_q[rank_index],
10405 &mut in_d[rank_index],
10406 )?;
10407 }
10409 self.nvfp4_routes_batched_sweeps(
10410 experts,
10411 workspace,
10412 selected,
10413 route_weights,
10414 &sel_i32,
10415 local_out,
10416 n_sel,
10417 activation_limit,
10418 false,
10419 )?;
10420
10421 let root = &self.ranks[0];
10424 for engine in &self.ranks[1..] {
10425 let _main = engine.gpu.enter_main()?;
10426 engine.stream().synchronize()?;
10427 }
10428 let _main = root.gpu.enter_main()?;
10429 root.stream()
10430 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10431 root.add(
10432 &workspace.accumulator[0],
10433 &workspace.remote,
10434 &mut workspace.combined,
10435 experts.input_width,
10436 )?;
10437 let output = root.dtoh(&workspace.combined)?;
10438 if let Some(started) = started {
10439 use std::sync::atomic::Ordering;
10440 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10441 + started.elapsed().as_nanos() as u64;
10442 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10443 if calls % 430 == 0 {
10444 eprintln!(
10445 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10446 ns as f64 / 1.0e6,
10447 ns as f64 / calls as f64 / 1.0e3,
10448 );
10449 }
10450 }
10451 Ok(output)
10452 }
10453
10454 #[allow(clippy::too_many_arguments)]
10459 fn nvfp4_routes_batched_sweeps(
10460 &self,
10461 experts: &ResidentNvfp4TensorParallel,
10462 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10463 selected: &[usize],
10464 route_weights: &[f32],
10465 sel_i32: &[i32],
10466 local_out: usize,
10467 n_sel: usize,
10468 activation_limit: Option<f32>,
10469 device_routed: bool,
10470 ) -> Result<(), Box<dyn std::error::Error>> {
10471 for rank_index in 0..self.ranks.len() {
10472 self.nvfp4_routes_batched_sweeps_rank(
10473 experts,
10474 workspace,
10475 selected,
10476 route_weights,
10477 sel_i32,
10478 local_out,
10479 n_sel,
10480 activation_limit,
10481 device_routed,
10482 rank_index,
10483 )?;
10484 }
10485 Ok(())
10486 }
10487
10488 #[allow(clippy::too_many_arguments)]
10491 fn nvfp4_routes_batched_sweeps_rank(
10492 &self,
10493 experts: &ResidentNvfp4TensorParallel,
10494 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10495 selected: &[usize],
10496 route_weights: &[f32],
10497 sel_i32: &[i32],
10498 local_out: usize,
10499 n_sel: usize,
10500 activation_limit: Option<f32>,
10501 device_routed: bool,
10502 rank_index: usize,
10503 ) -> Result<(), Box<dyn std::error::Error>> {
10504 {
10505 let engine = &self.ranks[rank_index];
10506 let _main = engine.gpu.enter_main()?;
10507 if experts.ep2 {
10512 if !device_routed {
10513 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
10514 }
10515 let gate_bank = &experts.gate[rank_index];
10516 let up_bank = &experts.up[rank_index];
10517 if gate_bank.local_out != experts.expert_width
10518 || gate_bank.expert_bytes != up_bank.expert_bytes
10519 {
10520 return Err("NVFP4 EP2 bank geometry drifted".into());
10521 }
10522 {
10523 let Nvfp4DeviceRoutesWorkspace {
10524 sel,
10525 gate_out,
10526 up_out,
10527 in_q,
10528 in_d,
10529 ..
10530 } = &mut *workspace;
10531 engine.qmatvec_nvfp4_sel_gu_ep_into(
10532 &gate_bank.bank,
10533 &up_bank.bank,
10534 &sel[rank_index],
10535 &in_q[rank_index],
10536 &in_d[rank_index],
10537 &mut gate_out[rank_index],
10538 &mut up_out[rank_index],
10539 n_sel,
10540 gate_bank.in_features,
10541 gate_bank.local_out,
10542 gate_bank.row_bytes,
10543 gate_bank.expert_bytes,
10544 rank_index,
10545 )?;
10546 }
10547 {
10548 let Nvfp4DeviceRoutesWorkspace {
10549 gate_out,
10550 up_out,
10551 sel,
10552 act_q,
10553 act_d,
10554 ..
10555 } = &mut *workspace;
10556 engine.silu_mul_scaled_q8_1_sel_ep_into(
10557 &gate_out[rank_index],
10558 &up_out[rank_index],
10559 &experts.macros_gate_dev[rank_index],
10560 &experts.macros_up_dev[rank_index],
10561 &sel[rank_index],
10562 activation_limit,
10563 &mut act_q[rank_index],
10564 &mut act_d[rank_index],
10565 local_out,
10566 n_sel,
10567 rank_index,
10568 )?;
10569 }
10570 let shard = &experts.down[rank_index];
10571 if shard.device_rank != rank_index || shard.local_in != local_out {
10572 return Err("NVFP4 EP2 down bank placement drifted".into());
10573 }
10574 {
10575 let Nvfp4DeviceRoutesWorkspace {
10576 sel,
10577 act_q,
10578 act_d,
10579 route_w,
10580 accumulator,
10581 ..
10582 } = &mut *workspace;
10583 engine.qmatvec_nvfp4_sel_down8_ep_into(
10584 &shard.bank,
10585 &sel[rank_index],
10586 &act_q[rank_index],
10587 &act_d[rank_index],
10588 &route_w[rank_index],
10589 &experts.macros_down_dev[rank_index],
10590 &mut accumulator[rank_index],
10591 n_sel,
10592 shard.local_in,
10593 shard.out_features,
10594 shard.row_bytes,
10595 shard.expert_bytes,
10596 local_out,
10597 local_out / 32,
10598 rank_index,
10599 )?;
10600 }
10601 return Ok(());
10602 }
10603 if !device_routed {
10604 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
10605 let folded = (0..n_sel)
10608 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
10609 .collect::<Vec<_>>();
10610 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
10611 engine.stream().memcpy_htod(&folded, &mut view)?;
10612 }
10613 let gate_bank = &experts.gate[rank_index];
10614 let up_bank = &experts.up[rank_index];
10615 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
10616 engine.qmatvec_nvfp4_sel_into(
10617 &gate_bank.bank,
10618 &workspace.sel[rank_index],
10619 aq,
10620 ad,
10621 &mut workspace.gate_out[rank_index],
10622 n_sel,
10623 gate_bank.in_features,
10624 gate_bank.local_out,
10625 gate_bank.row_bytes,
10626 gate_bank.expert_bytes,
10627 0,
10628 0,
10629 )?;
10630 engine.qmatvec_nvfp4_sel_into(
10631 &up_bank.bank,
10632 &workspace.sel[rank_index],
10633 aq,
10634 ad,
10635 &mut workspace.up_out[rank_index],
10636 n_sel,
10637 up_bank.in_features,
10638 up_bank.local_out,
10639 up_bank.row_bytes,
10640 up_bank.expert_bytes,
10641 0,
10642 0,
10643 )?;
10644 {
10648 let Nvfp4DeviceRoutesWorkspace {
10649 gate_out,
10650 up_out,
10651 sel,
10652 act_q,
10653 act_d,
10654 ..
10655 } = &mut *workspace;
10656 engine.silu_mul_scaled_q8_1_sel_into(
10657 &gate_out[rank_index],
10658 &up_out[rank_index],
10659 &experts.macros_gate_dev[rank_index],
10660 &experts.macros_up_dev[rank_index],
10661 &sel[rank_index],
10662 activation_limit,
10663 &mut act_q[rank_index],
10664 &mut act_d[rank_index],
10665 local_out,
10666 n_sel,
10667 )?;
10668 }
10669 let shard = &experts.down[rank_index];
10670 if shard.device_rank != rank_index || shard.local_in != local_out {
10671 return Err(
10672 "NVFP4 device routes: down canonical shard placement drifted from \
10673 the gate/up column split"
10674 .into(),
10675 );
10676 }
10677 {
10678 let Nvfp4DeviceRoutesWorkspace {
10679 sel,
10680 act_q,
10681 act_d,
10682 partial,
10683 ..
10684 } = &mut *workspace;
10685 engine.qmatvec_nvfp4_sel_into(
10686 &shard.bank,
10687 &sel[rank_index],
10688 &act_q[rank_index],
10689 &act_d[rank_index],
10690 &mut partial[rank_index],
10691 n_sel,
10692 shard.local_in,
10693 shard.out_features,
10694 shard.row_bytes,
10695 shard.expert_bytes,
10696 local_out,
10697 local_out / 32,
10698 )?;
10699 }
10700 {
10704 let Nvfp4DeviceRoutesWorkspace {
10705 partial,
10706 combine_w,
10707 route_w,
10708 sel,
10709 accumulator,
10710 ..
10711 } = &mut *workspace;
10712 if device_routed {
10713 engine.axpy_rows_seq_md_into(
10714 &partial[rank_index],
10715 &route_w[rank_index],
10716 &experts.macros_down_dev[rank_index],
10717 &sel[rank_index],
10718 &mut accumulator[rank_index],
10719 experts.input_width,
10720 n_sel,
10721 )?;
10722 } else {
10723 engine.axpy_rows_seq_into(
10724 &partial[rank_index],
10725 &combine_w[rank_index],
10726 &mut accumulator[rank_index],
10727 experts.input_width,
10728 n_sel,
10729 )?;
10730 }
10731 }
10732 }
10733 Ok(())
10734 }
10735
10736 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10744 &self,
10745 experts: &ResidentNvfp4TensorParallel,
10746 e: &Engine,
10747 input_dev: &crate::CudaSlice<f32>,
10748 selected: &[usize],
10749 route_weights: &[f32],
10750 experts_per_token: usize,
10751 activation_limit: Option<f32>,
10752 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10753 if input_dev.len() != experts.input_width {
10754 return Err(format!(
10755 "NVFP4 device-io routes input {} != width {}",
10756 input_dev.len(),
10757 experts.input_width
10758 )
10759 .into());
10760 }
10761 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10762 return Err(format!(
10763 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10764 selected.len(),
10765 route_weights.len(),
10766 )
10767 .into());
10768 }
10769 if !route_weights.iter().all(|weight| weight.is_finite()) {
10770 return Err("NVFP4 device route weights contain a non-finite value".into());
10771 }
10772 let world = self.ranks.len();
10773 if world != NVFP4_CANONICAL_ROW_SHARDS {
10774 return Err(format!(
10775 "NVFP4 device routes require world == canonical shard grid \
10776 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10777 )
10778 .into());
10779 }
10780 let local_out = experts.expert_width / world;
10781 let n_sel = experts_per_token;
10782
10783 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10784 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10785 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10786 let started = timing.then(std::time::Instant::now);
10787
10788 let mut workspace_guard = experts
10789 .device_workspace
10790 .lock()
10791 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10792 if workspace_guard.is_none() {
10793 drop(workspace_guard);
10794 let zero = vec![0.0f32; experts.input_width];
10797 let zero_sel = vec![0usize; n_sel];
10798 let zero_w = vec![0.0f32; n_sel];
10799 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10800 experts,
10801 &zero,
10802 &zero_sel,
10803 &zero_w,
10804 n_sel,
10805 activation_limit,
10806 )?;
10807 workspace_guard = experts
10808 .device_workspace
10809 .lock()
10810 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10811 }
10812 let workspace = workspace_guard
10813 .as_mut()
10814 .expect("NVFP4 device routes workspace initialized above");
10815 if workspace.n_sel != n_sel {
10816 return Err(format!(
10817 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10818 workspace.n_sel
10819 )
10820 .into());
10821 }
10822 for &expert in selected {
10823 if expert >= experts.expert_count {
10824 return Err(format!(
10825 "NVFP4 device selected expert {expert} outside 0..{}",
10826 experts.expert_count
10827 )
10828 .into());
10829 }
10830 }
10831 let sel_i32 = selected
10832 .iter()
10833 .map(|&expert| expert as i32)
10834 .collect::<Vec<_>>();
10835
10836 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10840 if *device != e.ctx().ordinal() {
10841 return Err("NVFP4 device-io routes engine changed".into());
10842 }
10843 } else {
10844 let _main = e.gpu.enter_main()?;
10845 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10846 }
10847 {
10848 let _main = e.gpu.enter_main()?;
10849 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10850 ev_entry.record(&e.stream())?;
10851 }
10852 for (rank_index, engine) in self.ranks.iter().enumerate() {
10853 let _main = engine.gpu.enter_main()?;
10854 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10855 engine.stream().wait(ev_entry)?;
10856 {
10857 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10858 engine
10859 .stream()
10860 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10861 }
10862 {
10863 let Nvfp4DeviceRoutesWorkspace {
10864 input, in_q, in_d, ..
10865 } = &mut *workspace;
10866 engine.quantize_q8_1_into(
10867 &input[rank_index],
10868 1,
10869 experts.input_width,
10870 &mut in_q[rank_index],
10871 &mut in_d[rank_index],
10872 )?;
10873 }
10874 }
10875 self.nvfp4_routes_batched_sweeps(
10876 experts,
10877 workspace,
10878 selected,
10879 route_weights,
10880 &sel_i32,
10881 local_out,
10882 n_sel,
10883 activation_limit,
10884 false,
10885 )?;
10886
10887 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10893 let _main = engine.gpu.enter_main()?;
10894 workspace.ev_rank[rank_index].record(&engine.stream())?;
10895 }
10896 if moe_direct_on() && self.ranks.len() == 2 {
10897 {
10904 let root = &self.ranks[0];
10905 let _main = root.gpu.enter_main()?;
10906 workspace
10907 .ev_done
10908 .as_ref()
10909 .expect("device routes done event")
10910 .record(&root.stream())?;
10911 }
10912 let _main = e.gpu.enter_main()?;
10913 e.stream().wait(
10914 workspace
10915 .ev_done
10916 .as_ref()
10917 .expect("device routes done event"),
10918 )?;
10919 for ev in workspace.ev_rank.iter().skip(1) {
10920 e.stream().wait(ev)?;
10921 }
10922 let mut output = e.uninit(experts.input_width)?;
10923 e.add(
10924 &workspace.accumulator[0],
10925 &workspace.accumulator[1],
10926 &mut output,
10927 experts.input_width,
10928 )?;
10929 let output = output;
10930 if let Some(started) = started {
10931 use std::sync::atomic::Ordering;
10932 let ns = TIMING_NS
10933 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10934 + started.elapsed().as_nanos() as u64;
10935 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10936 if calls % 430 == 0 {
10937 eprintln!(
10938 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10939 ns as f64 / 1.0e6,
10940 ns as f64 / calls as f64 / 1.0e3,
10941 );
10942 }
10943 }
10944 return Ok(output);
10945 }
10946 {
10947 let root = &self.ranks[0];
10948 let _main = root.gpu.enter_main()?;
10949 for ev in workspace.ev_rank.iter().skip(1) {
10950 root.stream().wait(ev)?;
10951 }
10952 root.stream()
10953 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10954 {
10955 let Nvfp4DeviceRoutesWorkspace {
10956 accumulator,
10957 remote,
10958 combined,
10959 ..
10960 } = &mut *workspace;
10961 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10962 }
10963 workspace
10964 .ev_done
10965 .as_ref()
10966 .expect("device routes done event")
10967 .record(&root.stream())?;
10968 }
10969 let output = {
10970 let _main = e.gpu.enter_main()?;
10971 e.stream().wait(
10972 workspace
10973 .ev_done
10974 .as_ref()
10975 .expect("device routes done event"),
10976 )?;
10977 let mut output = e.uninit(experts.input_width)?;
10980 e.stream().memcpy_dtod(
10981 &workspace.combined.slice(0..experts.input_width),
10982 &mut output.slice_mut(0..experts.input_width),
10983 )?;
10984 output
10985 };
10986 if let Some(started) = started {
10987 use std::sync::atomic::Ordering;
10988 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10989 + started.elapsed().as_nanos() as u64;
10990 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10991 if calls % 430 == 0 {
10992 eprintln!(
10993 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10994 ns as f64 / 1.0e6,
10995 ns as f64 / calls as f64 / 1.0e3,
10996 );
10997 }
10998 }
10999 Ok(output)
11000 }
11001
11002 #[allow(clippy::too_many_arguments)]
11008 pub fn nvfp4_routes_prestage(
11013 &self,
11014 experts: &ResidentNvfp4TensorParallel,
11015 e: &Engine,
11016 input_dev: &crate::CudaSlice<f32>,
11017 ) -> Result<bool, Box<dyn std::error::Error>> {
11018 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
11019 }
11020
11021 pub fn nvfp4_routes_prestage_with(
11027 &self,
11028 experts: &ResidentNvfp4TensorParallel,
11029 e: &Engine,
11030 input_dev: &crate::CudaSlice<f32>,
11031 rank1_router: impl FnOnce(
11032 &Engine,
11033 &crate::CudaSlice<f32>,
11034 &mut crate::CudaSlice<i32>,
11035 &mut crate::CudaSlice<f32>,
11036 ) -> Result<bool, Box<dyn std::error::Error>>,
11037 ) -> Result<bool, Box<dyn std::error::Error>> {
11038 if !routes_prestage_on() || step_tp_graph_enabled()? {
11039 return Ok(false);
11040 }
11041 if input_dev.len() != experts.input_width {
11042 return Err("NVFP4 prestage input width mismatch".into());
11043 }
11044 let mut workspace_guard = experts
11045 .device_workspace
11046 .lock()
11047 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11048 let Some(workspace) = workspace_guard.as_mut() else {
11049 return Ok(false);
11050 };
11051 if workspace.ev_input.is_none() {
11052 let _main = e.gpu.enter_main()?;
11053 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11054 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
11055 return Err("NVFP4 prestage engine changed".into());
11056 }
11057 {
11058 let _main = e.gpu.enter_main()?;
11059 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
11060 ev.record(&e.stream())?;
11061 }
11062 for (rank_index, engine) in self.ranks.iter().enumerate() {
11063 let _main = engine.gpu.enter_main()?;
11064 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
11065 engine.stream().wait(ev)?;
11066 {
11067 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11068 engine
11069 .stream()
11070 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11071 }
11072 {
11073 let Nvfp4DeviceRoutesWorkspace {
11074 input, in_q, in_d, ..
11075 } = &mut *workspace;
11076 engine.quantize_q8_1_into(
11077 &input[rank_index],
11078 1,
11079 experts.input_width,
11080 &mut in_q[rank_index],
11081 &mut in_d[rank_index],
11082 )?;
11083 }
11084 }
11085 if self.ranks.len() == 2 {
11086 let rank1 = &self.ranks[1];
11087 let _r1 = rank1.gpu.enter_main()?;
11088 let Nvfp4DeviceRoutesWorkspace {
11089 input,
11090 sel,
11091 route_w,
11092 ..
11093 } = &mut *workspace;
11094 let (in1, rest_sel) = (&input[1], &mut sel[1]);
11095 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
11096 workspace.rank1_routed = true;
11097 }
11098 }
11099 workspace.prestaged = true;
11100 Ok(true)
11101 }
11102
11103 #[allow(clippy::too_many_arguments)]
11120 fn determ_stage_bytes(v: &[u8]) -> u64 {
11129 v.iter().fold(0u64, |a, b| {
11130 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
11131 })
11132 }
11133
11134 fn determ_stage_i32(v: &[i32]) -> u64 {
11139 v.iter().fold(0u64, |a, b| {
11140 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
11141 })
11142 }
11143
11144 fn determ_stage_sum(v: &[f32]) -> u64 {
11145 v.iter().fold(0u64, |a, x| {
11146 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
11147 })
11148 }
11149
11150 pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
11151 &self,
11152 experts: &ResidentNvfp4TensorParallel,
11153 e: &Engine,
11154 z_t: &crate::CudaSlice<f32>,
11155 t: usize,
11156 sel: &[i32],
11157 w: &[f32],
11158 n_used: usize,
11159 activation_limit: Option<f32>,
11160 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11161 let world = self.ranks.len();
11162 if world != NVFP4_CANONICAL_ROW_SHARDS {
11163 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
11164 }
11165 let bank_qt = if experts.ep2 {
11170 crate::QT_NVFP4_V2
11171 } else {
11172 crate::QT_NVFP4
11173 };
11174 let width = experts.input_width;
11175 let n_expert = experts.expert_count;
11176 let n_pairs = t * n_used;
11177 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
11178 return Err("NVFP4 grouped prime geometry".into());
11179 }
11180 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
11189 let g_t0 = std::time::Instant::now();
11190 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11193 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
11194 let s_id = s_id as usize;
11195 if s_id >= n_expert {
11196 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
11197 }
11198 buckets[s_id].push(p as i32);
11199 }
11200 let mut ex_ids: Vec<i32> = Vec::new();
11201 let mut ex_off: Vec<i32> = vec![0];
11202 let mut ex_pairs: Vec<i32> = Vec::new();
11203 for (e_id, b) in buckets.iter().enumerate() {
11204 if !b.is_empty() {
11205 ex_ids.push(e_id as i32);
11206 ex_pairs.extend_from_slice(b);
11207 ex_off.push(ex_pairs.len() as i32);
11208 }
11209 }
11210 let n_active = ex_ids.len();
11211 if n_active == 0 {
11212 return Ok(e.zeros(t * width)?);
11213 }
11214 if n_active > 512 {
11215 return Err("grouped prime n_active > 512 (direct lane cap)".into());
11216 }
11217 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11218 let mut inv = vec![0i32; n_pairs];
11221 for (row, &pair) in ex_pairs.iter().enumerate() {
11222 inv[pair as usize] = row as i32;
11223 }
11224 let mg: Vec<f32> = ex_pairs
11226 .iter()
11227 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
11228 .collect();
11229 let mu: Vec<f32> = ex_pairs
11230 .iter()
11231 .map(|&p| experts.macros_up[sel[p as usize] as usize])
11232 .collect();
11233 let wd: Vec<f32> = (0..n_pairs)
11234 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
11235 .collect();
11236 {
11240 let mut tabs = experts
11241 .prime_tables
11242 .lock()
11243 .map_err(|_| "grouped prime table cache is poisoned")?;
11244 if tabs.len() != world {
11245 tabs.clear();
11246 for rank in 0..world {
11247 let engine = &self.ranks[rank];
11248 let _main = engine.gpu.enter_main()?;
11249 let (gb, ub, db) =
11250 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
11251 let mut tab = vec![0u64; 3 * n_expert];
11252 {
11253 use cudarc::driver::DevicePtr;
11254 let stream = engine.stream();
11255 let (pg, _g0) = gb.bank.device_ptr(&stream);
11256 let (pu, _g1) = ub.bank.device_ptr(&stream);
11257 let (pd, _g2) = db.bank.device_ptr(&stream);
11258 for ex in 0..n_expert {
11259 tab[ex] = pg as u64 + (ex * gb.expert_bytes) as u64;
11260 tab[n_expert + ex] = pu as u64 + (ex * ub.expert_bytes) as u64;
11261 tab[2 * n_expert + ex] = pd as u64 + (ex * db.expert_bytes) as u64;
11262 }
11263 }
11264 tabs.push(engine.htod_u64(&tab)?);
11265 }
11266 }
11267 }
11268 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
11269 let g_t1 = std::time::Instant::now();
11270 {
11277 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
11278 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
11279 for rank in 0..world {
11280 let e_r = &self.ranks[rank];
11281 let _m = e_r.gpu.enter_main();
11282 eprintln!(
11283 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
11284 root_stream={:?}",
11285 e_r.ctx().ordinal(),
11286 std::sync::Arc::as_ptr(&e_r.ctx()),
11287 e_r.stream().cu_stream(),
11288 e.ctx().ordinal(),
11289 e.stream().cu_stream(),
11290 );
11291 }
11292 }
11293 }
11294
11295 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
11296 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
11297 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
11298 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
11299 for rank in 0..world {
11300 let engine = &self.ranks[rank];
11301 let _main = engine.gpu.enter_main()?;
11302 if gprof {
11303 let h = engine
11308 .ctx()
11309 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
11310 h.record(&engine.stream())?;
11311 ev_head.push(h);
11312 }
11313 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
11316 let gb = &experts.gate[rank];
11317 let ub = &experts.up[rank];
11318 let db = &experts.down[rank];
11319 if db.device_rank != rank {
11320 return Err("grouped prime: down shard placement drifted".into());
11321 }
11322 let local_ff = gb.local_out;
11323 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
11324 return Err("grouped prime: bank width mismatch".into());
11325 }
11326 let csr_tok_d = engine.htod_i32(&csr_tok)?;
11329 let exi_d = engine.htod_i32(&ex_ids)?;
11330 let exoff_d = engine.htod_i32(&ex_off)?;
11331 let mg_d = engine.htod(&mg)?;
11332 let mu_d = engine.htod(&mu)?;
11333 let tabs_guard = experts
11335 .prime_tables
11336 .lock()
11337 .map_err(|_| "grouped prime table cache is poisoned")?;
11338 let tab_d = &tabs_guard[rank];
11339 let mut z_r = engine.uninit(t * width)?;
11340 {
11341 let mut dst = z_r.slice_mut(0..t * width);
11342 engine
11343 .stream()
11344 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
11345 }
11346 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
11347 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
11348 if dstage {
11349 let zr = engine.dtoh(&z_r)?;
11353 let zsv = engine.dtoh(&zs)?;
11354 let z16v = engine.dtoh_u8(&z16)?;
11355 eprintln!(
11356 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
11357 Self::determ_stage_sum(&zr),
11358 Self::determ_stage_sum(&zsv),
11359 Self::determ_stage_bytes(&z16v)
11360 );
11361 }
11362 if dstage {
11363 engine.stream().synchronize()?;
11371 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
11372 let exi_v = engine.dtoh_i32(&exi_d)?;
11373 let exo_v = engine.dtoh_i32(&exoff_d)?;
11374 let mg_v = engine.dtoh(&mg_d)?;
11375 let mu_v = engine.dtoh(&mu_d)?;
11376 let tab_v = engine.dtoh_u64(tab_d)?;
11377 eprintln!(
11378 "[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={}",
11379 Self::determ_stage_i32(&csr_v),
11380 Self::determ_stage_i32(&exi_v),
11381 Self::determ_stage_i32(&exo_v),
11382 Self::determ_stage_i32(&ex_off),
11383 Self::determ_stage_sum(&mg_v),
11384 Self::determ_stage_sum(&mu_v),
11385 tab_v
11386 .iter()
11387 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
11388 gb.row_bytes
11389 );
11390 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
11393 let bank_v = engine.dtoh_u8(&gb.bank)?;
11394 eprintln!(
11395 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
11396 Self::determ_stage_bytes(&bank_v),
11397 bank_v.len()
11398 );
11399 }
11400 }
11401 let mut g = engine.moe_f16_grouped(
11402 tab_d,
11403 0,
11404 n_expert,
11405 &exi_d,
11406 &ex_off,
11407 &exoff_d,
11408 &z16,
11409 &zs,
11410 width,
11411 local_ff,
11412 n_active,
11413 n_pairs,
11414 bank_qt,
11415 gb.row_bytes,
11416 )?;
11417 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
11418 let mut u = engine.moe_f16_grouped(
11419 tab_d,
11420 1,
11421 n_expert,
11422 &exi_d,
11423 &ex_off,
11424 &exoff_d,
11425 &z16,
11426 &zs,
11427 width,
11428 local_ff,
11429 n_active,
11430 n_pairs,
11431 bank_qt,
11432 ub.row_bytes,
11433 )?;
11434 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
11435 let act = match activation_limit.filter(|l| *l > 1e-6) {
11439 Some(lim) => {
11440 let mut a = engine.uninit(n_pairs * local_ff)?;
11441 engine.swiglu_clamped_mul_scaled(
11442 &g,
11443 &u,
11444 1.0,
11445 1.0,
11446 lim,
11447 &mut a,
11448 n_pairs * local_ff,
11449 )?;
11450 a
11451 }
11452 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
11453 };
11454 if dstage {
11455 let gv = engine.dtoh(&g)?;
11456 let uv = engine.dtoh(&u)?;
11457 let av = engine.dtoh(&act)?;
11458 let key = (rank, t);
11463 let mut prev_map = DETERM_PREV
11464 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
11465 .lock()
11466 .map_err(|_| "determ prev map poisoned")?;
11467 let shape = match prev_map.get(&key) {
11468 Some(prev) if prev.len() == gv.len() => {
11469 let mut md = 0.0f32;
11470 let mut n_diff = 0usize;
11471 let mut n_big = 0usize;
11472 for (a, b) in prev.iter().zip(gv.iter()) {
11473 let d = (a - b).abs();
11474 if d > 0.0 {
11475 n_diff += 1;
11476 }
11477 if d > 1e-3 {
11478 n_big += 1;
11479 }
11480 if d > md {
11481 md = d;
11482 }
11483 }
11484 format!(
11485 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
11486 gv.len()
11487 )
11488 }
11489 _ => String::new(),
11490 };
11491 prev_map.insert(key, gv.clone());
11492 drop(prev_map);
11493 eprintln!(
11494 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
11495 Self::determ_stage_sum(&gv),
11496 Self::determ_stage_sum(&uv),
11497 Self::determ_stage_sum(&av)
11498 );
11499 }
11500 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
11501 let d_csr = engine.moe_f16_grouped(
11502 tab_d,
11503 2,
11504 n_expert,
11505 &exi_d,
11506 &ex_off,
11507 &exoff_d,
11508 &a16,
11509 &a_s,
11510 local_ff,
11511 width,
11512 n_active,
11513 n_pairs,
11514 bank_qt,
11515 db.row_bytes,
11516 )?;
11517
11518 if dstage {
11521 engine.stream().synchronize()?;
11522 let a16v = engine.dtoh_u8(&a16)?;
11523 let dv = engine.dtoh(&d_csr)?;
11524 eprintln!(
11525 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
11526 Self::determ_stage_bytes(&a16v),
11527 Self::determ_stage_sum(&dv)
11528 );
11529 }
11530 let ev = engine.ctx().new_event(None)?;
11531 ev.record(&engine.stream())?;
11532 if gprof {
11533 let tp = engine
11542 .ctx()
11543 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
11544 tp.record(&engine.stream())?;
11545 ev_tail_prof.push(tp);
11546 }
11547 ev_rank.push(ev);
11548 partials.push(d_csr);
11549 }
11550 let _main = e.gpu.enter_main()?;
11551 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
11552 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
11554 let g_t2 = std::time::Instant::now();
11555 for ev in &ev_rank {
11556 e.stream().wait(ev)?;
11557 }
11558 let mut y0 = e.uninit(n_pairs * width)?;
11561 {
11562 let mut dst = y0.slice_mut(0..n_pairs * width);
11563 e.stream()
11564 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
11565 }
11566 let mut y1 = e.uninit(n_pairs * width)?;
11567 {
11568 let mut dst = y1.slice_mut(0..n_pairs * width);
11569 e.stream()
11570 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
11571 }
11572 let inv_d = e.htod_i32(&inv)?;
11573 let wd_d = e.htod(&wd)?;
11574 let mut out = e.uninit(t * width)?;
11575 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
11576 if gprof {
11577 let _ = e.stream().synchronize();
11578 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
11579 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
11587 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
11588 let guard = self.ranks[rank].gpu.enter_main();
11589 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
11590 Ok(v) => span_ms.push(v),
11591 Err(err) => {
11592 static SAID: std::sync::atomic::AtomicBool =
11593 std::sync::atomic::AtomicBool::new(false);
11594 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
11595 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
11596 }
11597 span_ms.push(-1.0);
11598 }
11599 }
11600 }
11601 eprintln!(
11602 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
11603 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
11604 span_ms.iter().sum::<f32>(),
11605 span_ms.iter().cloned().fold(0.0f32, f32::max)
11606 );
11607 }
11608 Ok(out)
11609 }
11610
11611 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
11612 &self,
11613 experts: &ResidentNvfp4TensorParallel,
11614 e: &Engine,
11615 input_dev: &crate::CudaSlice<f32>,
11616 sel_d: &crate::CudaSlice<i32>,
11617 w_d: &crate::CudaSlice<f32>,
11618 experts_per_token: usize,
11619 activation_limit: Option<f32>,
11620 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11621 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11622 experts,
11623 e,
11624 input_dev,
11625 sel_d,
11626 w_d,
11627 experts_per_token,
11628 activation_limit,
11629 || Ok(()),
11630 )
11631 }
11632
11633 #[allow(clippy::too_many_arguments)]
11639 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11640 &self,
11641 experts: &ResidentNvfp4TensorParallel,
11642 e: &Engine,
11643 input_dev: &crate::CudaSlice<f32>,
11644 sel_d: &crate::CudaSlice<i32>,
11645 w_d: &crate::CudaSlice<f32>,
11646 experts_per_token: usize,
11647 activation_limit: Option<f32>,
11648 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11649 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11650 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11651 experts,
11652 e,
11653 input_dev,
11654 sel_d,
11655 w_d,
11656 experts_per_token,
11657 activation_limit,
11658 pre_join,
11659 None,
11660 )
11661 }
11662
11663 #[allow(clippy::too_many_arguments)]
11668 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11669 &self,
11670 experts: &ResidentNvfp4TensorParallel,
11671 e: &Engine,
11672 input_dev: &crate::CudaSlice<f32>,
11673 sel_d: &crate::CudaSlice<i32>,
11674 w_d: &crate::CudaSlice<f32>,
11675 experts_per_token: usize,
11676 activation_limit: Option<f32>,
11677 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11678 post_add: Option<(u64, u64)>,
11679 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11680 if input_dev.len() != experts.input_width {
11681 return Err(format!(
11682 "NVFP4 device-routed input {} != width {}",
11683 input_dev.len(),
11684 experts.input_width
11685 )
11686 .into());
11687 }
11688 let n_sel = experts_per_token;
11689 if sel_d.len() < n_sel || w_d.len() < n_sel {
11690 return Err(format!(
11691 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
11692 sel_d.len(),
11693 w_d.len()
11694 )
11695 .into());
11696 }
11697 let world = self.ranks.len();
11698 if world != NVFP4_CANONICAL_ROW_SHARDS {
11699 return Err(format!(
11700 "NVFP4 device routes require world == canonical shard grid \
11701 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
11702 )
11703 .into());
11704 }
11705 let local_out = if experts.ep2 {
11706 experts.expert_width
11707 } else {
11708 experts.expert_width / world
11709 };
11710
11711 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11712 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11713 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11714 let started = timing.then(std::time::Instant::now);
11715
11716 let mut workspace_guard = experts
11717 .device_workspace
11718 .lock()
11719 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11720 if workspace_guard.is_none() {
11721 drop(workspace_guard);
11722 let zero = vec![0.0f32; experts.input_width];
11723 let zero_sel = vec![0usize; n_sel];
11724 let zero_w = vec![0.0f32; n_sel];
11725 let _ = self.run_tensor_parallel_routes_nvfp4_device(
11726 experts,
11727 &zero,
11728 &zero_sel,
11729 &zero_w,
11730 n_sel,
11731 activation_limit,
11732 )?;
11733 workspace_guard = experts
11734 .device_workspace
11735 .lock()
11736 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11737 }
11738 let workspace = workspace_guard
11739 .as_mut()
11740 .expect("NVFP4 device routes workspace initialized above");
11741 if workspace.n_sel != n_sel {
11742 return Err(format!(
11743 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
11744 workspace.n_sel
11745 )
11746 .into());
11747 }
11748
11749 if step_tp_graph_enabled()? && step_tp_graph_headroom_ok(e) {
11760 if experts.ep2 {
11761 return Err(
11762 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
11763 co-gated; unset one"
11764 .into(),
11765 );
11766 }
11767 if workspace.dev_route_e.is_none() {
11768 let _main = e.gpu.enter_main()?;
11769 workspace.dev_route_e = Some((
11770 e.htod_i32(&vec![0i32; n_sel])?,
11771 e.htod(&vec![0.0f32; n_sel])?,
11772 ));
11773 }
11774 if workspace.in_stage_e.is_none() {
11775 let _main = e.gpu.enter_main()?;
11776 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11777 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11778 }
11779 if workspace.routes_graph.is_none() {
11780 let graph = self.nvfp4_routes_build_graph(
11781 experts,
11782 workspace,
11783 local_out,
11784 n_sel,
11785 activation_limit,
11786 )?;
11787 workspace.routes_graph = Some(graph);
11788 eprintln!(
11789 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
11790 children=3 updates=none performance_claim=false"
11791 );
11792 }
11793 let output = {
11794 let _main = e.gpu.enter_main()?;
11795 {
11796 let (sel_e, w_e) = workspace
11797 .dev_route_e
11798 .as_mut()
11799 .expect("device route staging set above");
11800 {
11801 let mut dst = sel_e.slice_mut(0..n_sel);
11802 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11803 }
11804 {
11805 let mut dst = w_e.slice_mut(0..n_sel);
11806 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11807 }
11808 }
11809 {
11810 let in_stage = workspace
11811 .in_stage_e
11812 .as_mut()
11813 .expect("graph staging set above");
11814 let mut dst = in_stage.slice_mut(0..experts.input_width);
11815 e.stream()
11816 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
11817 }
11818 unsafe {
11819 let r = cudarc::driver::sys::cuGraphLaunch(
11820 workspace
11821 .routes_graph
11822 .as_ref()
11823 .expect("routes graph built above")
11824 .exec,
11825 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11826 );
11827 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11828 return Err(format!("routes graph launch: {r:?}").into());
11829 }
11830 }
11831 let mut output = e.uninit(experts.input_width)?;
11832 {
11833 let out_stage = workspace
11834 .out_stage_e
11835 .as_ref()
11836 .expect("graph staging set above");
11837 e.stream().memcpy_dtod(
11838 &out_stage.slice(0..experts.input_width),
11839 &mut output.slice_mut(0..experts.input_width),
11840 )?;
11841 }
11842 output
11843 };
11844 if let Some(started) = started {
11845 use std::sync::atomic::Ordering;
11846 let ns = TIMING_NS
11847 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11848 + started.elapsed().as_nanos() as u64;
11849 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11850 if calls % 430 == 0 {
11851 eprintln!(
11852 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11853 ns as f64 / 1.0e6,
11854 ns as f64 / calls as f64 / 1.0e3,
11855 );
11856 }
11857 }
11858 return Ok(output);
11859 }
11860
11861 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11865 if *device != e.ctx().ordinal() {
11866 return Err("NVFP4 device-routed routes engine changed".into());
11867 }
11868 } else {
11869 let _main = e.gpu.enter_main()?;
11870 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11871 }
11872 if workspace.dev_route_e.is_none() {
11873 let _main = e.gpu.enter_main()?;
11874 workspace.dev_route_e = Some((
11875 e.htod_i32(&vec![0i32; n_sel])?,
11876 e.htod(&vec![0.0f32; n_sel])?,
11877 ));
11878 }
11879 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
11885 let e_device = e.ctx().ordinal();
11886 let rank1_routed_peek = workspace.rank1_routed;
11888 let stage_needed = !mirror
11889 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
11890 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
11891 });
11892 {
11893 let _main = e.gpu.enter_main()?;
11894 if stage_needed {
11895 let (sel_e, w_e) = workspace
11896 .dev_route_e
11897 .as_mut()
11898 .expect("device route staging set above");
11899 {
11900 let mut dst = sel_e.slice_mut(0..n_sel);
11901 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11902 }
11903 {
11904 let mut dst = w_e.slice_mut(0..n_sel);
11905 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11906 }
11907 }
11908 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11909 ev_entry.record(&e.stream())?;
11910 }
11911 let prestaged = std::mem::take(&mut workspace.prestaged);
11914 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
11915 for (rank_index, engine) in self.ranks.iter().enumerate() {
11916 let _main = engine.gpu.enter_main()?;
11917 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11918 engine.stream().wait(ev_entry)?;
11919 if !prestaged {
11920 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11921 engine
11922 .stream()
11923 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11924 }
11925 if !(rank1_routed && rank_index == 1) {
11926 let same_dev = engine.ctx().ordinal() == e_device;
11930 if mirror {
11931 let Nvfp4DeviceRoutesWorkspace {
11934 sel,
11935 route_w,
11936 dev_route_e,
11937 ..
11938 } = &mut *workspace;
11939 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
11940 if same_dev {
11941 (sel_d, w_d)
11942 } else {
11943 let (sel_e, w_e) = dev_route_e
11944 .as_ref()
11945 .expect("device route staging set above");
11946 (sel_e, w_e)
11947 };
11948 engine.moe_sel_w_mirror(
11949 src_sel,
11950 src_w,
11951 &mut sel[rank_index],
11952 &mut route_w[rank_index],
11953 n_sel,
11954 )?;
11955 } else {
11956 let (sel_e, w_e) = workspace
11957 .dev_route_e
11958 .as_ref()
11959 .expect("device route staging set above");
11960 {
11961 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
11962 engine
11963 .stream()
11964 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
11965 }
11966 {
11967 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
11968 engine
11969 .stream()
11970 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11971 }
11972 }
11973 }
11974 if !prestaged {
11975 let Nvfp4DeviceRoutesWorkspace {
11976 input, in_q, in_d, ..
11977 } = &mut *workspace;
11978 engine.quantize_q8_1_into(
11979 &input[rank_index],
11980 1,
11981 experts.input_width,
11982 &mut in_q[rank_index],
11983 &mut in_d[rank_index],
11984 )?;
11985 }
11986 }
11987 self.nvfp4_routes_batched_sweeps(
11988 experts,
11989 workspace,
11990 &[],
11991 &[],
11992 &[],
11993 local_out,
11994 n_sel,
11995 activation_limit,
11996 true,
11997 )?;
11998
11999 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
12002 let _main = engine.gpu.enter_main()?;
12003 workspace.ev_rank[rank_index].record(&engine.stream())?;
12004 }
12005 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
12008 let mut ticket = 0u32;
12009 if memops {
12010 use cudarc::driver::sys;
12011 if workspace.fence_flags_raw == 0 {
12012 let root = &self.ranks[0];
12013 let _main = root.gpu.enter_main()?;
12014 let mut ptr: sys::CUdeviceptr = 0;
12015 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
12016 if r != sys::CUresult::CUDA_SUCCESS {
12017 return Err(format!("fence flag alloc: {r:?}").into());
12018 }
12019 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
12020 if r != sys::CUresult::CUDA_SUCCESS {
12021 return Err(format!("fence flag memset: {r:?}").into());
12022 }
12023 workspace.fence_flags_raw = ptr as u64;
12024 }
12025 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
12026 ticket = workspace.fence_ticket;
12027 let base = workspace.fence_flags_raw;
12028 if fence_rank1_on() {
12034 let peer = &self.ranks[1];
12035 let _pmain = peer.gpu.enter_main()?;
12036 peer.ring_flag_raw(base, ticket)?;
12037 }
12038 {
12039 let root = &self.ranks[0];
12040 let _main = root.gpu.enter_main()?;
12041 let r = unsafe {
12042 sys::cuStreamWriteValue32_v2(
12043 root.stream().cu_stream() as sys::CUstream,
12044 (base + 4) as sys::CUdeviceptr,
12045 ticket,
12046 0,
12047 )
12048 };
12049 if r != sys::CUresult::CUDA_SUCCESS {
12050 return Err(format!("fence write root: {r:?}").into());
12051 }
12052 }
12053 }
12054 pre_join()?;
12057
12058 if moe_direct_on() && self.ranks.len() == 2 {
12059 let _main = e.gpu.enter_main()?;
12066 if memops {
12067 use cudarc::driver::sys;
12068 let base = workspace.fence_flags_raw;
12069 let r = unsafe {
12070 sys::cuStreamWaitValue32_v2(
12071 e.stream().cu_stream() as sys::CUstream,
12072 (base + 4) as sys::CUdeviceptr,
12073 ticket,
12074 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
12075 )
12076 };
12077 if r != sys::CUresult::CUDA_SUCCESS {
12078 return Err(format!("fence wait: {r:?}").into());
12079 }
12080 if fence_rank1_on() {
12081 let r = unsafe {
12083 sys::cuStreamWaitValue32_v2(
12084 e.stream().cu_stream() as sys::CUstream,
12085 base as sys::CUdeviceptr,
12086 ticket,
12087 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
12088 )
12089 };
12090 if r != sys::CUresult::CUDA_SUCCESS {
12091 return Err(format!("fence wait rank1: {r:?}").into());
12092 }
12093 } else {
12094 for ev in workspace.ev_rank.iter().skip(1) {
12095 e.stream().wait(ev)?;
12096 }
12097 }
12098 } else {
12099 {
12100 let root = &self.ranks[0];
12101 let _rmain = root.gpu.enter_main()?;
12102 workspace
12103 .ev_done
12104 .as_ref()
12105 .expect("device routes done event")
12106 .record(&root.stream())?;
12107 }
12108 e.stream().wait(
12109 workspace
12110 .ev_done
12111 .as_ref()
12112 .expect("device routes done event"),
12113 )?;
12114 for ev in workspace.ev_rank.iter().skip(1) {
12115 e.stream().wait(ev)?;
12116 }
12117 }
12118 let mut output = e.uninit(experts.input_width)?;
12119 if let Some((sh_raw, scale_raw)) = post_add {
12120 e.add3_raw(
12123 &workspace.accumulator[0],
12124 &workspace.accumulator[1],
12125 sh_raw,
12126 scale_raw,
12127 &mut output,
12128 experts.input_width,
12129 )?;
12130 } else {
12131 e.add(
12132 &workspace.accumulator[0],
12133 &workspace.accumulator[1],
12134 &mut output,
12135 experts.input_width,
12136 )?;
12137 }
12138 let output = output;
12139 if let Some(started) = started {
12140 use std::sync::atomic::Ordering;
12141 let ns = TIMING_NS
12142 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12143 + started.elapsed().as_nanos() as u64;
12144 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12145 if calls % 430 == 0 {
12146 eprintln!(
12147 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12148 ns as f64 / 1.0e6,
12149 ns as f64 / calls as f64 / 1.0e3,
12150 );
12151 }
12152 }
12153 return Ok(output);
12154 }
12155 {
12156 let root = &self.ranks[0];
12157 let _main = root.gpu.enter_main()?;
12158 for ev in workspace.ev_rank.iter().skip(1) {
12159 root.stream().wait(ev)?;
12160 }
12161 root.stream()
12162 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12163 {
12164 let Nvfp4DeviceRoutesWorkspace {
12165 accumulator,
12166 remote,
12167 combined,
12168 ..
12169 } = &mut *workspace;
12170 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12171 }
12172 workspace
12173 .ev_done
12174 .as_ref()
12175 .expect("device routes done event")
12176 .record(&root.stream())?;
12177 }
12178 let output = {
12179 let _main = e.gpu.enter_main()?;
12180 e.stream().wait(
12181 workspace
12182 .ev_done
12183 .as_ref()
12184 .expect("device routes done event"),
12185 )?;
12186 let mut output = e.uninit(experts.input_width)?;
12189 e.stream().memcpy_dtod(
12190 &workspace.combined.slice(0..experts.input_width),
12191 &mut output.slice_mut(0..experts.input_width),
12192 )?;
12193 output
12194 };
12195 if let Some(started) = started {
12196 use std::sync::atomic::Ordering;
12197 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12198 + started.elapsed().as_nanos() as u64;
12199 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12200 if calls % 430 == 0 {
12201 eprintln!(
12202 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12203 ns as f64 / 1.0e6,
12204 ns as f64 / calls as f64 / 1.0e3,
12205 );
12206 }
12207 }
12208 Ok(output)
12209 }
12210
12211 pub(crate) fn decode_v2_finish_root_fused(
12215 &self,
12216 ws: &mut StepTpDecodeV2Ws,
12217 ) -> Result<(), Box<dyn std::error::Error>> {
12218 let root = &self.ranks[0];
12219 let _main = root.gpu.enter_main()?;
12220 if ws.raw_peer_partial != 0 {
12221 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
12223 } else {
12224 root.stream()
12225 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
12226 }
12227 {
12228 let StepTpDecodeV2Ws {
12229 o_partials,
12230 peer_partial,
12231 reduce_a,
12232 o_out,
12233 ..
12234 } = &mut *ws;
12235 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
12236 }
12237 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
12238 if shadows {
12239 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
12242 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
12243 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
12244 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
12245 }
12246 if shadows && ws.raw_peer_partial != 0 {
12247 raw_copy_bytes(
12248 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
12249 ws.raw_k1,
12250 ws.local_kv_dim * 4,
12251 root,
12252 )?;
12253 raw_copy_bytes(
12254 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
12255 ws.raw_v1,
12256 ws.local_kv_dim * 4,
12257 root,
12258 )?;
12259 } else if shadows {
12260 let start = ws.local_kv_dim;
12261 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
12262 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
12263 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
12264 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
12265 }
12266 if ws.raw_mixed_stage_e != 0 {
12267 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
12270 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
12271 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
12272 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
12273 }
12274 Ok(())
12275 }
12276
12277 pub(crate) fn decode_v2_arm_token_mirrors(
12280 &self,
12281 ws: &mut StepTpDecodeV2Ws,
12282 mixed_stage_e: u64,
12283 shadow_stage_e: (u64, u64),
12284 ) -> Result<(), Box<dyn std::error::Error>> {
12285 use cudarc::driver::DevicePtr;
12286 let root = &self.ranks[0];
12287 let _main = root.gpu.enter_main()?;
12288 let stream = root.stream();
12289 let (a, _g) = ws.reduce_a.device_ptr(&stream);
12290 ws.raw_reduce_a = a as u64;
12291 ws.raw_mixed_stage_e = mixed_stage_e;
12292 ws.raw_shadow_stage_e = shadow_stage_e;
12293 Ok(())
12294 }
12295
12296 fn nvfp4_routes_build_graph(
12302 &self,
12303 experts: &ResidentNvfp4TensorParallel,
12304 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12305 local_out: usize,
12306 n_sel: usize,
12307 activation_limit: Option<f32>,
12308 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12309 use cudarc::driver::DevicePtr;
12310 use cudarc::driver::sys;
12311 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12312 if r == sys::CUresult::CUDA_SUCCESS {
12313 Ok(())
12314 } else {
12315 Err(format!("{what}: {r:?}").into())
12316 }
12317 }
12318 let world = self.ranks.len();
12319 if world != 2 {
12320 return Err("routes graph door is built for the TP2 pair".into());
12321 }
12322 let width = experts.input_width;
12323
12324 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
12326 let stream = engine.stream();
12327 let (ptr, _g) = buf.device_ptr(&stream);
12328 ptr as u64
12329 };
12330 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
12331 let stream = engine.stream();
12332 let (ptr, _g) = buf.device_ptr(&stream);
12333 ptr as u64
12334 };
12335 let (sel_e, w_e) = workspace
12336 .dev_route_e
12337 .as_ref()
12338 .expect("device route staging set before graph build");
12339 let root_engine = &self.ranks[0];
12340 let p_in_stage = ptr_f32(
12341 workspace.in_stage_e.as_ref().expect("graph staging"),
12342 root_engine,
12343 );
12344 let p_out_stage = ptr_f32(
12345 workspace.out_stage_e.as_ref().expect("graph staging"),
12346 root_engine,
12347 );
12348 let p_sel_e = ptr_i32(sel_e, root_engine);
12349 let p_w_e = ptr_f32(w_e, root_engine);
12350 let p_input: Vec<u64> = (0..world)
12351 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
12352 .collect();
12353 let p_sel: Vec<u64> = (0..world)
12354 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
12355 .collect();
12356 let p_route_w: Vec<u64> = (0..world)
12357 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
12358 .collect();
12359 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
12360 let p_remote = ptr_f32(&workspace.remote, root_engine);
12361 let p_combined = ptr_f32(&workspace.combined, root_engine);
12362
12363 let raw_copy = |dst: u64,
12364 src: u64,
12365 bytes: usize,
12366 engine: &Engine|
12367 -> Result<(), Box<dyn std::error::Error>> {
12368 unsafe {
12369 cu_try(
12370 sys::cuMemcpyAsync(
12371 dst as sys::CUdeviceptr,
12372 src as sys::CUdeviceptr,
12373 bytes,
12374 engine.stream().cu_stream() as sys::CUstream,
12375 ),
12376 "routes graph cuMemcpyAsync",
12377 )
12378 }
12379 };
12380
12381 let mut children = Vec::with_capacity(3);
12382 for rank in 0..world {
12383 let engine = &self.ranks[rank];
12384 let _main = engine.gpu.enter_main()?;
12385 let (child, _retained) = engine.capture_graph_retained(|_| {
12386 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
12387 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
12388 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
12389 {
12390 let Nvfp4DeviceRoutesWorkspace {
12391 input, in_q, in_d, ..
12392 } = &mut *workspace;
12393 engine.quantize_q8_1_into(
12394 &input[rank],
12395 1,
12396 width,
12397 &mut in_q[rank],
12398 &mut in_d[rank],
12399 )?;
12400 }
12401 self.nvfp4_routes_batched_sweeps_rank(
12402 experts,
12403 workspace,
12404 &[],
12405 &[],
12406 &[],
12407 local_out,
12408 n_sel,
12409 activation_limit,
12410 true,
12411 rank,
12412 )?;
12413 Ok(())
12414 })?;
12415 children.push(child);
12416 }
12417 {
12418 let root = &self.ranks[0];
12419 let _main = root.gpu.enter_main()?;
12420 let (child, _retained) = root.capture_graph_retained(|_| {
12421 raw_copy(p_remote, p_acc1, width * 4, root)?;
12422 {
12423 let Nvfp4DeviceRoutesWorkspace {
12424 accumulator,
12425 remote,
12426 combined,
12427 ..
12428 } = &mut *workspace;
12429 root.add(&accumulator[0], remote, combined, width)?;
12430 }
12431 raw_copy(p_out_stage, p_combined, width * 4, root)?;
12432 Ok(())
12433 })?;
12434 children.push(child);
12435 }
12436
12437 let mut parent: sys::CUgraph = std::ptr::null_mut();
12438 unsafe {
12439 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
12440 }
12441 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
12442 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
12443 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
12444 unsafe {
12445 cu_try(
12446 sys::cuGraphAddChildGraphNode(
12447 &mut n0,
12448 parent,
12449 std::ptr::null(),
12450 0,
12451 children[0].cu_graph(),
12452 ),
12453 "routes child r0",
12454 )?;
12455 cu_try(
12456 sys::cuGraphAddChildGraphNode(
12457 &mut n1,
12458 parent,
12459 std::ptr::null(),
12460 0,
12461 children[1].cu_graph(),
12462 ),
12463 "routes child r1",
12464 )?;
12465 let deps = [n0, n1];
12466 cu_try(
12467 sys::cuGraphAddChildGraphNode(
12468 &mut n2,
12469 parent,
12470 deps.as_ptr(),
12471 2,
12472 children[2].cu_graph(),
12473 ),
12474 "routes child root",
12475 )?;
12476 }
12477 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12478 unsafe {
12479 cu_try(
12480 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12481 "routes instantiate",
12482 )?;
12483 }
12484 Ok(RoutesGraph {
12485 exec,
12486 parent,
12487 _children: children,
12488 })
12489 }
12490
12491 #[allow(clippy::too_many_arguments)]
12495 pub(crate) fn routes_rank_section(
12496 &self,
12497 experts: &ResidentNvfp4TensorParallel,
12498 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12499 raw_input_src: u64,
12500 local_out: usize,
12501 n_sel: usize,
12502 activation_limit: Option<f32>,
12503 rank_index: usize,
12504 ) -> Result<(), Box<dyn std::error::Error>> {
12505 let engine = &self.ranks[rank_index];
12506 {
12507 let _main = engine.gpu.enter_main()?;
12508 let (sel_e_ptr, w_e_ptr) = workspace
12510 .raw_dev_route_e
12511 .ok_or("routes rank section requires armed staging pointers")?;
12512 raw_copy_bytes(
12513 workspace.raw_input[rank_index],
12514 raw_input_src,
12515 experts.input_width * 4,
12516 engine,
12517 )?;
12518 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
12519 raw_copy_bytes(
12520 workspace.raw_route_w[rank_index],
12521 w_e_ptr,
12522 n_sel * 4,
12523 engine,
12524 )?;
12525 {
12526 let Nvfp4DeviceRoutesWorkspace {
12527 input, in_q, in_d, ..
12528 } = &mut *workspace;
12529 engine.quantize_q8_1_into(
12530 &input[rank_index],
12531 1,
12532 experts.input_width,
12533 &mut in_q[rank_index],
12534 &mut in_d[rank_index],
12535 )?;
12536 }
12537 }
12538 self.nvfp4_routes_batched_sweeps_rank(
12539 experts,
12540 workspace,
12541 &[],
12542 &[],
12543 &[],
12544 local_out,
12545 n_sel,
12546 activation_limit,
12547 true,
12548 rank_index,
12549 )
12550 }
12551
12552 pub(crate) fn routes_root_section(
12555 &self,
12556 experts: &ResidentNvfp4TensorParallel,
12557 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12558 ) -> Result<(), Box<dyn std::error::Error>> {
12559 let root = &self.ranks[0];
12560 let _main = root.gpu.enter_main()?;
12561 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
12562 .raw_combine
12563 .ok_or("routes root section requires armed combine pointers")?;
12564 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
12565 {
12566 let Nvfp4DeviceRoutesWorkspace {
12567 accumulator,
12568 remote,
12569 combined,
12570 ..
12571 } = &mut *workspace;
12572 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12573 }
12574 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
12575 Ok(())
12576 }
12577
12578 pub(crate) fn routes_arm_raw(
12581 &self,
12582 experts: &ResidentNvfp4TensorParallel,
12583 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12584 ) -> Result<(), Box<dyn std::error::Error>> {
12585 use cudarc::driver::DevicePtr;
12586 if workspace.raw_dev_route_e.is_some() {
12587 return Ok(());
12588 }
12589 let _ = experts;
12590 let (sel_e, w_e) = workspace
12591 .dev_route_e
12592 .as_ref()
12593 .ok_or("routes staging not armed")?;
12594 let root = &self.ranks[0];
12595 {
12596 let _main = root.gpu.enter_main()?;
12597 let stream = root.stream();
12598 let (a, _g) = sel_e.device_ptr(&stream);
12599 let (b, _g) = w_e.device_ptr(&stream);
12600 workspace.raw_dev_route_e = Some((a as u64, b as u64));
12601 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
12602 let (d, _g) = workspace.remote.device_ptr(&stream);
12603 let (f, _g) = workspace.combined.device_ptr(&stream);
12604 let out_stage = workspace
12605 .out_stage_e
12606 .as_ref()
12607 .ok_or("routes out stage not armed")?;
12608 let (g_, _g) = out_stage.device_ptr(&stream);
12609 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
12610 }
12611 for rank in 0..self.ranks.len() {
12612 let engine = &self.ranks[rank];
12613 let _main = engine.gpu.enter_main()?;
12614 let stream = engine.stream();
12615 let (a, _g) = workspace.input[rank].device_ptr(&stream);
12616 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
12617 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
12618 workspace.raw_input.push(a as u64);
12619 workspace.raw_sel.push(b as u64);
12620 workspace.raw_route_w.push(c as u64);
12621 }
12622 Ok(())
12623 }
12624
12625 pub fn run_tensor_parallel_routes_nvfp4(
12629 &self,
12630 experts: &ResidentNvfp4TensorParallel,
12631 input: &[f32],
12632 tokens: usize,
12633 selected: &[usize],
12634 route_weights: &[f32],
12635 experts_per_token: usize,
12636 activation_limit: Option<f32>,
12637 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12638 validate_activations(input, tokens, experts.input_width)?;
12639 let pairs = tokens
12640 .checked_mul(experts_per_token)
12641 .ok_or("NVFP4 TP route count overflow")?;
12642 if selected.len() != pairs || route_weights.len() != pairs {
12643 return Err(format!(
12644 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
12645 {experts_per_token} ({pairs})",
12646 selected.len(),
12647 route_weights.len(),
12648 )
12649 .into());
12650 }
12651 if !route_weights.iter().all(|weight| weight.is_finite()) {
12652 return Err("NVFP4 TP route weights contain a non-finite value".into());
12653 }
12654
12655 let mut output = vec![0.0f32; tokens * experts.input_width];
12656 for token in 0..tokens {
12657 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
12658 for slot in 0..experts_per_token {
12659 let pair = token * experts_per_token + slot;
12660 let expert = selected[pair];
12661 if expert >= experts.expert_count {
12662 return Err(format!(
12663 "NVFP4 TP selected expert {expert} outside 0..{}",
12664 experts.expert_count
12665 )
12666 .into());
12667 }
12668 let gate = if experts.ep2 {
12674 self.run_full_bank_expert_nvfp4(
12675 &experts.gate,
12676 &experts.macros_gate,
12677 expert,
12678 input_row,
12679 )?
12680 } else {
12681 self.run_column_bank_expert_nvfp4(
12682 &experts.gate,
12683 &experts.macros_gate,
12684 expert,
12685 input_row,
12686 )?
12687 };
12688 let up = if experts.ep2 {
12689 self.run_full_bank_expert_nvfp4(
12690 &experts.up,
12691 &experts.macros_up,
12692 expert,
12693 input_row,
12694 )?
12695 } else {
12696 self.run_column_bank_expert_nvfp4(
12697 &experts.up,
12698 &experts.macros_up,
12699 expert,
12700 input_row,
12701 )?
12702 };
12703 let activated: Vec<f32> = gate
12704 .iter()
12705 .zip(&up)
12706 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
12707 .collect();
12708 debug_assert_eq!(activated.len(), experts.expert_width);
12709 let down = if experts.ep2 {
12710 self.run_full_down_expert_nvfp4(
12711 &experts.down,
12712 &experts.macros_down,
12713 expert,
12714 &activated,
12715 )?
12716 } else {
12717 self.run_row_bank_expert_nvfp4(
12718 &experts.down,
12719 &experts.macros_down,
12720 expert,
12721 &activated,
12722 )?
12723 };
12724 let weight = route_weights[pair];
12725 for (sum, value) in output
12726 [token * experts.input_width..(token + 1) * experts.input_width]
12727 .iter_mut()
12728 .zip(down)
12729 {
12730 *sum += weight * value;
12731 }
12732 }
12733 }
12734 Ok(output)
12735 }
12736}
12737
12738#[cfg(test)]
12739mod bank_v2_layout_tests {
12740 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
12741
12742 #[test]
12752 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
12753 let (out_f, in_f) = (2usize, 128usize);
12755 let row_bytes = nvfp4_row_bytes(in_f);
12756 assert_eq!(row_bytes, 72);
12757 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
12758 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
12759 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
12760 let n_slots = in_f / 32;
12761 for row in 0..out_f {
12762 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
12763 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
12764 for g in 0..n_slots {
12765 let (sblk, h) = (g / 2, g % 2);
12766 let sb = &src[sblk * 36..sblk * 36 + 36];
12767 assert_eq!(
12768 &dst[g * 16..g * 16 + 16],
12769 &sb[4 + 16 * h..4 + 16 * h + 16],
12770 "row {row} slot {g} codes"
12771 );
12772 assert_eq!(
12773 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
12774 &sb[2 * h..2 * h + 2],
12775 "row {row} slot {g} scales"
12776 );
12777 }
12778 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
12780 a.sort_unstable();
12781 b.sort_unstable();
12782 assert_eq!(a, b, "row {row} is not a byte permutation");
12783 }
12784 }
12785}
12786
12787#[cfg(test)]
12788mod tests {
12789
12790 #[test]
12797 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
12798 let (kp, bp) = (0xdead_0000u64, 0u64);
12799 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
12800 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
12801 assert_eq!(
12802 super::retired_rows_tab_key(kp, bp, 20, 2),
12803 super::retired_rows_tab_key(kp, bp, 20, 2),
12804 "same layer and t must hash the same, or the test proves nothing"
12805 );
12806 let a = super::rows_tab_host(&live, 0x9000, true, 1);
12807 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
12808 assert_ne!(a, b, "the two generations write DIFFERENT tables");
12809 assert_eq!(
12811 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
12812 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
12813 "the retired key collides across allocation generations"
12814 );
12815 }
12816
12817 #[test]
12820 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
12821 let parts = [
12822 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
12823 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
12824 ];
12825 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
12826 assert_eq!(
12827 same,
12828 vec![
12829 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
12830 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
12833 "same-session rows share one counter cell and step back t-1-r"
12834 );
12835 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
12836 assert_eq!(
12837 cross,
12838 vec![
12839 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
12840 0x00c1u64, 0x00d1u64, 0x7004, 0,
12841 ],
12842 "cross-session rows get their own counter cell and no step back"
12843 );
12844 }
12845 use super::*;
12846
12847 #[test]
12848 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
12849 let limit = Some(7.0);
12850 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
12851 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
12852 assert!(
12853 step_expert_activation_host(-20.0, 9.0, limit).abs()
12854 < step_expert_activation_host(-20.0, 9.0, None).abs()
12855 );
12856 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
12857 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
12858 assert!(validate_step_expert_activation_limit(limit).is_ok());
12859 }
12860
12861 #[test]
12862 fn moe_residual_host_preserves_official_add_order() {
12863 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
12864 assert_eq!(output, [0.0]);
12865 assert_eq!(
12866 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
12867 "MoE residual lengths residual=1 routed=2 shared=1"
12868 );
12869 }
12870
12871 #[test]
12872 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
12873 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
12874 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
12875 assert_eq!(owners.len(), 4);
12876 for (rank, owner) in owners.iter().enumerate() {
12877 assert_eq!(owner.rank, rank);
12878 assert_eq!(owner.selected, vec![0, 36]);
12879 assert_eq!(owner.token_rows, vec![0, 0]);
12880 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
12881 }
12882 }
12883
12884 #[test]
12885 fn expert_owner_routes_validate_geometry_and_selected_experts() {
12886 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
12887 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
12888 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
12889 assert!(error.contains("outside 0..288"));
12890 }
12891
12892 #[test]
12893 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
12894 let selected = [
12895 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
12896 ];
12897 assert_eq!(
12898 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
12899 16
12900 );
12901 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
12902 assert_eq!(
12903 owners
12904 .iter()
12905 .map(|owner| owner.selected.len())
12906 .collect::<Vec<_>>(),
12907 vec![2, 4, 6, 4]
12908 );
12909 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
12910 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
12911 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
12912 }
12913
12914 #[test]
12915 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
12916 let owner0 = [0usize, 3];
12917 let owner1 = [1usize, 2];
12918 let owners = [owner0.as_slice(), owner1.as_slice()];
12919 assert_eq!(
12920 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
12921 .unwrap(),
12922 WeightedRouteCombineShape {
12923 pairs: 4,
12924 max_pairs: 12,
12925 }
12926 );
12927 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
12928 assert!(
12929 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
12930 .is_err()
12931 );
12932 assert!(
12933 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
12934 .is_err()
12935 );
12936 assert!(
12937 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
12938 .is_err()
12939 );
12940 }
12941
12942 #[test]
12943 fn native_p2p_door_is_strict_and_default_off() {
12944 assert!(!parse_step_tp_native_p2p(None).unwrap());
12945 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
12946 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
12947 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
12948 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
12949 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
12950 }
12951
12952 #[test]
12953 fn bulk_p2p_door_is_strict_and_default_off() {
12954 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
12955 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
12956 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
12957 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
12958 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
12959 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
12960 }
12961
12962 #[test]
12963 fn ep_device_arithmetic_door_is_strict_and_default_off() {
12964 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
12965 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
12966 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
12967 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
12968 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
12969 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
12970 }
12971
12972 #[test]
12973 fn f32_mirror_door_is_strict_and_default_off() {
12974 assert!(!parse_step_tp_f32_mirror(None).unwrap());
12975 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
12976 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
12977 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
12978 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
12979 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
12980 }
12981
12982 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
12983 let codes = (0..out_features * in_features)
12984 .map(|index| (index % 251) as u8)
12985 .collect();
12986 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
12987 .map(|index| index as f32 + 1.0)
12988 .collect();
12989 (codes, scales)
12990 }
12991
12992 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
12993 (0..out_features * in_features)
12994 .flat_map(|value| (value as u16).to_le_bytes())
12995 .collect()
12996 }
12997
12998 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
12999 bytes
13000 .chunks_exact(2)
13001 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
13002 .collect()
13003 }
13004
13005 #[test]
13006 fn bf16_matrix_rejects_wrong_byte_count() {
13007 let bytes = vec![0u8; 4 * 4 * 2 - 1];
13008 let matrix = Bf16Matrix {
13009 bytes: &bytes,
13010 out_features: 4,
13011 in_features: 4,
13012 };
13013 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
13014 }
13015
13016 #[test]
13017 fn replicated_device_rows_require_exact_rank_local_shapes() {
13018 assert_eq!(
13019 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
13020 12_288
13021 );
13022 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
13023 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
13024 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
13025 assert!(
13026 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
13027 );
13028 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
13029 }
13030
13031 #[test]
13032 fn replicated_device_row_refresh_requires_exact_root_source() {
13033 assert_eq!(
13034 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
13035 12_288
13036 );
13037 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
13038 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
13039 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
13040 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
13041 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
13042 }
13043
13044 #[test]
13045 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
13046 for tp in [1, 2, 4, 8] {
13047 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
13048 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
13049 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
13050 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
13051 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
13052 }
13053 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
13054 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
13055 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
13056 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
13057 }
13058
13059 #[test]
13060 fn cache_rows_split_by_token_then_rank() {
13061 let rows = (0u8..24).collect::<Vec<_>>();
13062 assert_eq!(
13063 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
13064 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
13065 );
13066 assert_eq!(
13067 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
13068 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
13069 );
13070 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
13071 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
13072 }
13073
13074 #[test]
13075 fn bf16_column_shard_preserves_contiguous_output_rows() {
13076 let bytes = bf16_matrix_bytes(4, 4);
13077 let matrix = Bf16Matrix {
13078 bytes: &bytes,
13079 out_features: 4,
13080 in_features: 4,
13081 };
13082 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
13083 assert_eq!(shard.out_features, 2);
13084 assert_eq!(shard.in_features, 4);
13085 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
13086 }
13087
13088 #[test]
13089 fn bf16_row_shard_preserves_each_input_column_window() {
13090 let bytes = bf16_matrix_bytes(3, 4);
13091 let matrix = Bf16Matrix {
13092 bytes: &bytes,
13093 out_features: 3,
13094 in_features: 4,
13095 };
13096 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
13097 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
13098 }
13099
13100 #[test]
13101 fn bf16_row_block_preserves_global_column_order() {
13102 let bytes = bf16_matrix_bytes(3, 8);
13103 let matrix = Bf16Matrix {
13104 bytes: &bytes,
13105 out_features: 3,
13106 in_features: 8,
13107 };
13108 let block = bf16_row_block(matrix, 2, 3).unwrap();
13109 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
13110 }
13111
13112 #[test]
13113 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
13114 let (codes, scales) = matrix(1280, 4096);
13115 let matrix = E4m3BlockMatrix {
13116 codes: &codes,
13117 scales: &scales,
13118 out_features: 1280,
13119 in_features: 4096,
13120 };
13121 let shard = column_shard(matrix, 2, 1).unwrap();
13122 assert_eq!(shard.out_features, 640);
13123 assert_eq!(shard.codes, &codes[640 * 4096..]);
13124 assert_eq!(shard.scales, &scales[5 * 32..]);
13125 }
13126
13127 #[test]
13128 fn row_shard_preserves_each_weight_and_scale_column_window() {
13129 let (codes, scales) = matrix(4096, 1280);
13130 let matrix = E4m3BlockMatrix {
13131 codes: &codes,
13132 scales: &scales,
13133 out_features: 4096,
13134 in_features: 1280,
13135 };
13136 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
13137 assert_eq!(shard_codes.len(), 4096 * 640);
13138 assert_eq!(&shard_codes[..640], &codes[640..1280]);
13139 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
13140 assert_eq!(shard_scales.len(), 32 * 5);
13141 assert_eq!(&shard_scales[..5], &scales[5..10]);
13142 assert_eq!(&shard_scales[5..10], &scales[15..20]);
13143 }
13144
13145 #[test]
13146 fn activation_shards_keep_token_rows_separate() {
13147 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
13148 assert_eq!(
13149 activation_shard(&activations, 2, 8, 2, 1),
13150 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
13151 );
13152 }
13153
13154 #[test]
13155 fn expert_bank_selects_expert_major_code_and_scale_planes() {
13156 let expert_count = 2;
13157 let out_features = 128;
13158 let in_features = 128;
13159 let code_stride = out_features * in_features;
13160 let codes: Vec<u8> = (0..expert_count * code_stride)
13161 .map(|index| (index % 251) as u8)
13162 .collect();
13163 let scales = vec![1.0f32, 2.0];
13164 let bank = E4m3ExpertBank {
13165 codes: &codes,
13166 scales: &scales,
13167 expert_count,
13168 out_features,
13169 in_features,
13170 };
13171 bank.validate().unwrap();
13172 let expert = bank.expert(1).unwrap();
13173 assert_eq!(expert.codes, &codes[code_stride..]);
13174 assert_eq!(expert.scales, &[2.0]);
13175 }
13176
13177 #[test]
13178 fn expert_bank_rejects_non_positive_scale() {
13179 let codes = vec![0u8; 128 * 128];
13180 let scales = vec![0.0f32];
13181 let bank = E4m3ExpertBank {
13182 codes: &codes,
13183 scales: &scales,
13184 expert_count: 1,
13185 out_features: 128,
13186 in_features: 128,
13187 };
13188 assert!(bank.validate().unwrap_err().contains("non-positive"));
13189 }
13190
13191 #[test]
13192 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
13193 let expert_count = 2;
13194 let out_features = 256;
13195 let in_features = 128;
13196 let code_stride = out_features * in_features;
13197 let scale_stride = 2;
13198 let codes = (0..expert_count * code_stride)
13199 .map(|index| (index % 251) as u8)
13200 .collect::<Vec<_>>();
13201 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
13202 let bank = E4m3ExpertBank {
13203 codes: &codes,
13204 scales: &scales,
13205 expert_count,
13206 out_features,
13207 in_features,
13208 };
13209
13210 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
13211 assert_eq!(rank.out_features, 128);
13212 assert_eq!(rank.in_features, 128);
13213 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
13214 assert_eq!(rank.scales, vec![11.0, 21.0]);
13215 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
13216 assert_eq!(
13217 &rank.codes[128 * 128..],
13218 &codes[code_stride + 128 * 128..2 * code_stride]
13219 );
13220 assert_eq!(scale_stride, scales.len() / expert_count);
13221 }
13222
13223 #[test]
13224 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
13225 let expert_count = 2;
13226 let out_features = 128;
13227 let in_features = 256;
13228 let code_stride = out_features * in_features;
13229 let codes = (0..expert_count * code_stride)
13230 .map(|index| (index % 251) as u8)
13231 .collect::<Vec<_>>();
13232 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
13233 let bank = E4m3ExpertBank {
13234 codes: &codes,
13235 scales: &scales,
13236 expert_count,
13237 out_features,
13238 in_features,
13239 };
13240
13241 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
13242 assert_eq!(rank.out_features, 128);
13243 assert_eq!(rank.in_features, 128);
13244 assert_eq!(rank.k_blocks, Some(1));
13245 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
13246 assert_eq!(rank.scales, vec![11.0, 21.0]);
13247 assert_eq!(&rank.codes[..128], &codes[128..256]);
13248 assert_eq!(
13249 &rank.codes[128 * 128..128 * 128 + 128],
13250 &codes[code_stride + 128..code_stride + 256]
13251 );
13252 }
13253
13254 #[test]
13255 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
13256 let expert_count = 2;
13257 let out_features = 256;
13258 let in_features = 512;
13259 let code_stride = out_features * in_features;
13260 let mut codes = vec![0u8; expert_count * code_stride];
13261 for expert in 0..expert_count {
13262 for row in 0..out_features {
13263 for block in 0..4 {
13264 let value = (expert * 80 + block * 16 + row % 16) as u8;
13265 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
13266 codes[start..start + FP8_BLOCK].fill(value);
13267 }
13268 }
13269 }
13270 let scales = vec![
13271 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,
13272 112.0, 113.0, 114.0,
13273 ];
13274 let bank = E4m3ExpertBank {
13275 codes: &codes,
13276 scales: &scales,
13277 expert_count,
13278 out_features,
13279 in_features,
13280 };
13281
13282 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
13283 assert_eq!(rank.out_features, out_features);
13284 assert_eq!(rank.in_features, 256);
13285 assert_eq!(rank.k_blocks, Some(2));
13286 assert_eq!(rank.code_stride, out_features * 256);
13287 assert_eq!(rank.scale_stride, 4);
13288 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
13289 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
13290
13291 let block_stride = out_features * FP8_BLOCK;
13292 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
13293 assert!(
13294 rank.codes[block_stride..block_stride + FP8_BLOCK]
13295 .iter()
13296 .all(|&code| code == 48)
13297 );
13298 assert!(
13299 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
13300 .iter()
13301 .all(|&code| code == 112)
13302 );
13303 assert!(
13304 rank.codes
13305 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
13306 .iter()
13307 .all(|&code| code == 128)
13308 );
13309 }
13310
13311 #[test]
13312 fn step_ep_layer_specs_are_literal_and_fail_closed() {
13313 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
13314 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
13315 assert_eq!(
13316 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
13317 vec![StepEpLayerSpec {
13318 layer: 24,
13319 devices: vec![1, 2],
13320 }]
13321 );
13322 assert_eq!(
13323 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
13324 vec![
13325 StepEpLayerSpec {
13326 layer: 24,
13327 devices: vec![1, 2],
13328 },
13329 StepEpLayerSpec {
13330 layer: 25,
13331 devices: vec![1, 2],
13332 },
13333 StepEpLayerSpec {
13334 layer: 31,
13335 devices: vec![0, 2],
13336 },
13337 ]
13338 );
13339 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
13340 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
13341 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
13342 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
13343 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
13344 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13345 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
13346 }
13347
13348 #[test]
13349 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
13350 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
13351 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
13352 assert_eq!(
13353 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
13354 vec![
13355 StepTpLayerSpec {
13356 layer: 24,
13357 devices: vec![1, 2],
13358 },
13359 StepTpLayerSpec {
13360 layer: 25,
13361 devices: vec![1, 2],
13362 },
13363 ]
13364 );
13365 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
13366 assert!(error.contains("MEMRA_STEP_TP"));
13367 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
13368 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13369
13370 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
13371 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
13372 assert_eq!(all.first().unwrap().layer, 0);
13373 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
13374 let devices = (0..8).collect::<Vec<_>>();
13375 assert!(all.iter().all(|spec| spec.devices == devices));
13376 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
13377 }
13378}
13379
13380struct TokenGraphChild {
13392 graph: cudarc::driver::CudaGraph,
13393 node: cudarc::driver::sys::CUgraphNode,
13394 ctx: cudarc::driver::sys::CUcontext,
13395}
13396
13397struct TokenGraphFaSite {
13401 ctx: cudarc::driver::sys::CUcontext,
13402 memset_o: cudarc::driver::sys::CUgraphNode,
13403 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
13404 fa: cudarc::driver::sys::CUgraphNode,
13405 combine: cudarc::driver::sys::CUgraphNode,
13406 window: usize,
13407 n_head: usize,
13408 n_head_kv: usize,
13409 head_dim: usize,
13410}
13411
13412pub struct TokenGraphBuilder {
13413 parent: cudarc::driver::sys::CUgraph,
13414 children: Vec<TokenGraphChild>,
13415 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
13418 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
13421 group: Option<(
13424 u32,
13425 Vec<cudarc::driver::sys::CUgraphNode>,
13426 Vec<cudarc::driver::sys::CUgraphNode>,
13427 )>,
13428}
13429
13430unsafe impl Send for TokenGraphBuilder {}
13432
13433impl TokenGraphBuilder {
13434 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
13435 use cudarc::driver::sys;
13436 let mut parent: sys::CUgraph = std::ptr::null_mut();
13437 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
13438 if r != sys::CUresult::CUDA_SUCCESS {
13439 return Err(format!("token graph create: {r:?}").into());
13440 }
13441 Ok(Self {
13442 parent,
13443 children: Vec::new(),
13444 frontier: Vec::new(),
13445 pending_detached: Vec::new(),
13446 group: None,
13447 })
13448 }
13449
13450 fn push_child(
13451 &mut self,
13452 graph: cudarc::driver::CudaGraph,
13453 parallel_group: Option<u32>,
13454 detached: bool,
13455 absorb: bool,
13456 ctx: cudarc::driver::sys::CUcontext,
13457 ) -> Result<(), Box<dyn std::error::Error>> {
13458 use cudarc::driver::sys;
13459 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
13463 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
13464 (state, Some(group)) => {
13465 if let Some((_, _, members)) = state.take() {
13467 self.frontier = members;
13468 }
13469 let base = self.frontier.clone();
13470 *state = Some((group, base.clone(), Vec::new()));
13471 base
13472 }
13473 (state, None) if detached => match state.as_ref() {
13474 Some((_, base, _)) => base.clone(),
13475 None => self.frontier.clone(),
13476 },
13477 (state, None) => {
13478 if let Some((_, _, members)) = state.take() {
13479 self.frontier = members;
13480 }
13481 let mut deps = self.frontier.clone();
13482 if absorb {
13483 deps.append(&mut self.pending_detached);
13484 }
13485 deps
13486 }
13487 };
13488 let mut node: sys::CUgraphNode = std::ptr::null_mut();
13489 let r = unsafe {
13490 sys::cuGraphAddChildGraphNode(
13491 &mut node,
13492 self.parent,
13493 if deps.is_empty() {
13494 std::ptr::null()
13495 } else {
13496 deps.as_ptr()
13497 },
13498 deps.len(),
13499 graph.cu_graph(),
13500 )
13501 };
13502 if r != sys::CUresult::CUDA_SUCCESS {
13503 return Err(format!("token graph child: {r:?}").into());
13504 }
13505 match (&mut self.group, parallel_group, detached) {
13506 (_, None, true) => self.pending_detached.push(node),
13507 (Some((_, _, members)), Some(_), _) => members.push(node),
13508 _ => self.frontier = vec![node],
13509 }
13510 self.children.push(TokenGraphChild { graph, node, ctx });
13511 Ok(())
13512 }
13513
13514 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
13515 use cudarc::driver::sys;
13516 if let Some((_, _, members)) = self.group.take() {
13517 self.frontier = members;
13518 }
13519 let mut fa_sites = Vec::new();
13522 for child in &self.children {
13523 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
13524 fa_sites.push(site);
13525 }
13526 }
13527 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
13528 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
13529 if r != sys::CUresult::CUDA_SUCCESS {
13530 return Err(format!("token graph instantiate: {r:?}").into());
13531 }
13532 Ok(TokenGraph {
13533 exec,
13534 parent: self.parent,
13535 _children: self.children,
13536 fa_sites,
13537 })
13538 }
13539}
13540
13541fn discover_fa_site(
13544 child_node: cudarc::driver::sys::CUgraphNode,
13545 ctx: cudarc::driver::sys::CUcontext,
13546) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
13547 use cudarc::driver::sys;
13548 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13549 if r == sys::CUresult::CUDA_SUCCESS {
13550 Ok(())
13551 } else {
13552 Err(format!("{what}: {r:?}").into())
13553 }
13554 }
13555 let mut graph: sys::CUgraph = std::ptr::null_mut();
13556 unsafe {
13557 cu_try(
13558 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
13559 "fa-site child GetGraph",
13560 )?;
13561 }
13562 let mut count: usize = 0;
13563 unsafe {
13564 cu_try(
13565 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
13566 "fa-site GetNodes(count)",
13567 )?;
13568 }
13569 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
13570 unsafe {
13571 cu_try(
13572 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
13573 "fa-site GetNodes",
13574 )?;
13575 }
13576 nodes.truncate(count);
13577 let node_type =
13578 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
13579 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
13580 unsafe {
13581 cu_try(
13582 sys::cuGraphNodeGetType(node, &mut ty),
13583 "fa-site NodeGetType",
13584 )?;
13585 }
13586 Ok(ty)
13587 };
13588 let memsets: Vec<sys::CUgraphNode> = {
13589 let mut v = Vec::new();
13590 for &node in &nodes {
13591 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
13592 v.push(node);
13593 }
13594 }
13595 v
13596 };
13597 if memsets.len() != 3 {
13598 return Ok(None);
13599 }
13600 let dependents =
13602 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
13603 let mut n: usize = 0;
13604 unsafe {
13605 cu_try(
13606 sys::cuGraphNodeGetDependentNodes_v2(
13607 node,
13608 std::ptr::null_mut(),
13609 std::ptr::null_mut(),
13610 &mut n,
13611 ),
13612 "fa-site GetDependentNodes(count)",
13613 )?;
13614 }
13615 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
13616 unsafe {
13617 cu_try(
13618 sys::cuGraphNodeGetDependentNodes_v2(
13619 node,
13620 v.as_mut_ptr(),
13621 std::ptr::null_mut(),
13622 &mut n,
13623 ),
13624 "fa-site GetDependentNodes",
13625 )?;
13626 }
13627 v.truncate(n);
13628 Ok(v)
13629 };
13630 let mut fa: Option<sys::CUgraphNode> = None;
13633 let mut last_memset: Option<sys::CUgraphNode> = None;
13634 for &ms in &memsets {
13635 for dep in dependents(ms)? {
13636 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13637 fa = Some(dep);
13638 last_memset = Some(ms);
13639 }
13640 }
13641 }
13642 let (Some(fa), Some(_last)) = (fa, last_memset) else {
13643 return Ok(None);
13644 };
13645 let mut combine: Option<sys::CUgraphNode> = None;
13646 for dep in dependents(fa)? {
13647 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13648 combine = Some(dep);
13649 }
13650 }
13651 let Some(combine) = combine else {
13652 return Ok(None);
13653 };
13654 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13657 unsafe {
13658 cu_try(
13659 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
13660 "fa-site KernelNodeGetParams",
13661 )?;
13662 }
13663 let arg_i32 =
13664 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
13665 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
13666 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
13668 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13669 unsafe {
13670 cu_try(
13671 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13672 "fa-site MemsetNodeGetParams",
13673 )?;
13674 }
13675 Ok(mp.width)
13676 };
13677 let mut widest = memsets[0];
13678 for &ms in &memsets[1..] {
13679 if width_of(ms)? > width_of(widest)? {
13680 widest = ms;
13681 }
13682 }
13683 let memset_m: Vec<sys::CUgraphNode> =
13684 memsets.iter().copied().filter(|&m| m != widest).collect();
13685 Ok(Some(TokenGraphFaSite {
13686 ctx,
13687 memset_o: widest,
13688 memset_m: [memset_m[0], memset_m[1]],
13689 fa,
13690 combine,
13691 window: win as usize,
13692 n_head: nh as usize,
13693 n_head_kv: nhkv as usize,
13694 head_dim: hd as usize,
13695 }))
13696}
13697
13698pub struct TokenGraph {
13699 exec: cudarc::driver::sys::CUgraphExec,
13700 parent: cudarc::driver::sys::CUgraph,
13701 _children: Vec<TokenGraphChild>,
13702 fa_sites: Vec<TokenGraphFaSite>,
13703}
13704
13705unsafe impl Send for TokenGraph {}
13706
13707impl TokenGraph {
13708 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
13713 use cudarc::driver::sys;
13714 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13715 if r == sys::CUresult::CUDA_SUCCESS {
13716 Ok(())
13717 } else {
13718 Err(format!("{what}: {r:?}").into())
13719 }
13720 }
13721 for site in &self.fa_sites {
13722 let layer_bucket = if site.window > 0 {
13723 bucket.min(site.window)
13724 } else {
13725 bucket
13726 };
13727 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
13728 let nsp = layer_bucket.div_ceil(sp).max(1);
13729 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13731 unsafe {
13732 cu_try(
13733 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
13734 "retarget fa GetParams",
13735 )?;
13736 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
13737 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
13738 params.gridDimY = nsp as u32;
13739 cu_try(
13740 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
13741 "retarget fa SetParams",
13742 )?;
13743 }
13744 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13746 unsafe {
13747 cu_try(
13748 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
13749 "retarget combine GetParams",
13750 )?;
13751 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
13752 cu_try(
13753 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
13754 "retarget combine SetParams",
13755 )?;
13756 }
13757 let set_width =
13759 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
13760 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13761 unsafe {
13762 cu_try(
13763 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13764 "retarget memset GetParams",
13765 )?;
13766 }
13767 mp.width = width;
13768 unsafe {
13769 cu_try(
13770 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
13771 "retarget memset SetParams",
13772 )?;
13773 }
13774 Ok(())
13775 };
13776 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
13777 set_width(site.memset_m[0], site.n_head * nsp)?;
13778 set_width(site.memset_m[1], site.n_head * nsp)?;
13779 }
13780 Ok(())
13781 }
13782
13783 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
13784 use cudarc::driver::sys;
13785 let _main = e.gpu.enter_main()?;
13786 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
13787 if r != sys::CUresult::CUDA_SUCCESS {
13788 return Err(format!("token graph launch: {r:?}").into());
13789 }
13790 Ok(())
13791 }
13792}
13793
13794impl Drop for TokenGraph {
13795 fn drop(&mut self) {
13796 unsafe {
13797 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
13798 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
13799 }
13800 }
13801}
13802
13803std::thread_local! {
13804 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
13805 const { std::cell::RefCell::new(None) };
13806}
13807
13808pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
13810 let builder = TokenGraphBuilder::new()?;
13811 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
13812 Ok(())
13813}
13814
13815pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
13817 let builder = TOKEN_GRAPH_BUILDER
13818 .with(|cell| cell.borrow_mut().take())
13819 .ok_or("token graph build was not begun")?;
13820 builder.finish()
13821}
13822
13823pub fn token_graph_building() -> bool {
13825 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
13826}
13827
13828pub fn graph_section<F>(
13833 engine: &Engine,
13834 parallel_group: Option<u32>,
13835 f: F,
13836) -> Result<(), Box<dyn std::error::Error>>
13837where
13838 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13839{
13840 graph_section_opts(engine, parallel_group, false, false, f)
13841}
13842
13843pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13845where
13846 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13847{
13848 graph_section_opts(engine, None, false, true, f)
13849}
13850
13851pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13854where
13855 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13856{
13857 graph_section_opts(engine, None, true, false, f)
13858}
13859
13860pub fn graph_section_opts<F>(
13861 engine: &Engine,
13862 parallel_group: Option<u32>,
13863 detached: bool,
13864 absorb: bool,
13865 f: F,
13866) -> Result<(), Box<dyn std::error::Error>>
13867where
13868 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13869{
13870 let building = token_graph_building();
13871 if !building {
13872 let mut f = f;
13873 return f();
13874 }
13875 let (child, ctx) = {
13876 let _main = engine.gpu.enter_main()?;
13877 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
13878 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
13879 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13880 return Err(format!("graph section ctx query: {r:?}").into());
13881 }
13882 let mut f = f;
13883 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
13886 (child, ctx)
13887 };
13888 TOKEN_GRAPH_BUILDER.with(|cell| {
13889 cell.borrow_mut()
13890 .as_mut()
13891 .expect("builder checked above")
13892 .push_child(child, parallel_group, detached, absorb, ctx)
13893 })
13894}