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
710pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
714 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
715}
716
717#[derive(Debug, Clone, PartialEq, Eq)]
718pub struct StepEpLayerSpec {
719 pub layer: usize,
720 pub devices: Vec<usize>,
721}
722
723pub type StepTpLayerSpec = StepEpLayerSpec;
724
725fn parse_step_layer_specs(
726 flag: &str,
727 value: Option<&str>,
728 allow_full_model: bool,
729) -> Result<Vec<StepEpLayerSpec>, String> {
730 let Some(value) = value else {
731 return Ok(Vec::new());
732 };
733 if value.is_empty() || value == "0" {
734 return Ok(Vec::new());
735 }
736
737 let mut specs = Vec::new();
738 for item in value.split(';') {
739 let (layers, devices) = item.split_once('@').ok_or_else(|| {
740 let layers = if allow_full_model {
741 "LAYER[-LAYER] or all"
742 } else {
743 "LAYER[-LAYER]"
744 };
745 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
746 })?;
747 let (first, last) = if layers == "all" {
748 if !allow_full_model {
749 return Err(format!(
750 "{flag} does not support the full-model shorthand; assign routed layers \
751 explicitly"
752 ));
753 }
754 (0, STEP37_TRUNK_LAYERS - 1)
755 } else {
756 match layers.split_once('-') {
757 Some((first, last)) => {
758 let first = first
759 .parse::<usize>()
760 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
761 let last = last
762 .parse::<usize>()
763 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
764 if first > last {
765 return Err(format!("{flag} layer range {first}-{last} is reversed"));
766 }
767 if last - first + 1 > 128 {
768 return Err(format!(
769 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
770 ));
771 }
772 (first, last)
773 }
774 None => {
775 let layer = layers
776 .parse::<usize>()
777 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
778 (layer, layer)
779 }
780 }
781 };
782 let devices = devices
783 .split(',')
784 .map(|device| {
785 device
786 .parse::<usize>()
787 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
788 })
789 .collect::<Result<Vec<_>, _>>()?;
790 if !(2..=8).contains(&devices.len()) {
791 return Err(format!(
792 "{flag} requires 2..=8 devices, got {}",
793 devices.len()
794 ));
795 }
796 let mut unique = devices.clone();
797 unique.sort_unstable();
798 unique.dedup();
799 if unique.len() != devices.len() {
800 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
801 }
802 for layer in first..=last {
803 if specs
804 .iter()
805 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
806 {
807 return Err(format!("{flag} assigns layer {layer} more than once"));
808 }
809 specs.push(StepEpLayerSpec {
810 layer,
811 devices: devices.clone(),
812 });
813 }
814 }
815 Ok(specs)
816}
817
818pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
819 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
820}
821
822pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
823 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
824}
825
826pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
827 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
828}
829
830pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
831 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
832}
833
834#[derive(Clone, Copy)]
835pub struct E4m3BlockMatrix<'a> {
836 pub codes: &'a [u8],
837 pub scales: &'a [f32],
838 pub out_features: usize,
839 pub in_features: usize,
840}
841
842impl E4m3BlockMatrix<'_> {
843 fn validate(&self) -> Result<(), String> {
844 let code_count = self
845 .out_features
846 .checked_mul(self.in_features)
847 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
848 if self.codes.len() != code_count {
849 return Err(format!(
850 "E4M3 code count {} != {}x{} ({code_count})",
851 self.codes.len(),
852 self.out_features,
853 self.in_features,
854 ));
855 }
856 let scale_count =
857 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
858 if self.scales.len() != scale_count {
859 return Err(format!(
860 "E4M3 scale count {} != {scale_count} for {}x{}",
861 self.scales.len(),
862 self.out_features,
863 self.in_features,
864 ));
865 }
866 if !self
867 .scales
868 .iter()
869 .all(|scale| scale.is_finite() && *scale > 0.0)
870 {
871 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
872 }
873 Ok(())
874 }
875}
876
877#[derive(Clone, Copy)]
878pub struct E4m3ExpertBank<'a> {
879 pub codes: &'a [u8],
880 pub scales: &'a [f32],
881 pub expert_count: usize,
882 pub out_features: usize,
883 pub in_features: usize,
884}
885
886impl E4m3ExpertBank<'_> {
887 fn validate(&self) -> Result<(), String> {
888 if self.expert_count == 0 {
889 return Err("E4M3 expert bank is empty".to_string());
890 }
891 let code_stride = self
892 .out_features
893 .checked_mul(self.in_features)
894 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
895 let code_count = self
896 .expert_count
897 .checked_mul(code_stride)
898 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
899 if self.codes.len() != code_count {
900 return Err(format!(
901 "E4M3 expert code count {} != {}x{} ({code_count})",
902 self.codes.len(),
903 self.expert_count,
904 code_stride,
905 ));
906 }
907 let scale_stride =
908 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
909 let scale_count = self
910 .expert_count
911 .checked_mul(scale_stride)
912 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
913 if self.scales.len() != scale_count {
914 return Err(format!(
915 "E4M3 expert scale count {} != {}x{} ({scale_count})",
916 self.scales.len(),
917 self.expert_count,
918 scale_stride,
919 ));
920 }
921 if !self
922 .scales
923 .iter()
924 .all(|scale| scale.is_finite() && *scale > 0.0)
925 {
926 return Err(
927 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
928 );
929 }
930 Ok(())
931 }
932
933 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
934 if expert >= self.expert_count {
935 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
936 }
937 let code_stride = self.out_features * self.in_features;
938 let scale_stride =
939 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
940 Ok(E4m3BlockMatrix {
941 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
942 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
943 out_features: self.out_features,
944 in_features: self.in_features,
945 })
946 }
947}
948
949pub struct ColumnParallelResult {
950 pub gathered: Vec<f32>,
951 pub rank_outputs: Vec<Vec<f32>>,
952}
953
954pub struct RowParallelResult {
955 pub reduced: Vec<f32>,
956 pub rank_partials: Vec<Vec<f32>>,
957}
958
959#[derive(Clone, Copy)]
960pub struct Bf16Matrix<'a> {
961 pub bytes: &'a [u8],
962 pub out_features: usize,
963 pub in_features: usize,
964}
965
966impl Bf16Matrix<'_> {
967 pub fn validate(&self) -> Result<(), String> {
968 if self.out_features == 0 || self.in_features == 0 {
969 return Err("BF16 matrix dimensions must be nonzero".into());
970 }
971 let expected = self
972 .out_features
973 .checked_mul(self.in_features)
974 .and_then(|values| values.checked_mul(2))
975 .ok_or("BF16 matrix byte count overflow")?;
976 if self.bytes.len() != expected {
977 return Err(format!(
978 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
979 self.bytes.len(),
980 self.out_features,
981 self.in_features,
982 ));
983 }
984 Ok(())
985 }
986}
987
988struct ResidentE4m3Rank {
989 codes: CudaSlice<u8>,
990 scales: CudaSlice<f32>,
991 out_features: usize,
992 in_features: usize,
993}
994
995enum ResidentBf16Weight {
996 Bf16(CudaSlice<u8>),
997 F32(CudaSlice<f32>),
998}
999
1000impl ResidentBf16Weight {
1001 fn ordinal(&self) -> usize {
1002 match self {
1003 Self::Bf16(bytes) => bytes.ordinal(),
1004 Self::F32(values) => values.ordinal(),
1005 }
1006 }
1007}
1008
1009struct ResidentBf16Rank {
1010 weight: ResidentBf16Weight,
1011 out_features: usize,
1012 in_features: usize,
1013 q8: Option<CudaSlice<u8>>,
1016}
1017
1018pub struct ResidentColumnParallel {
1019 ranks: Vec<ResidentE4m3Rank>,
1020 out_features: usize,
1021 in_features: usize,
1022}
1023
1024pub struct ResidentRowParallel {
1025 ranks: Vec<ResidentE4m3Rank>,
1026 out_features: usize,
1027 in_features: usize,
1028}
1029
1030pub struct ResidentBf16ColumnParallel {
1031 ranks: Vec<ResidentBf16Rank>,
1032 out_features: usize,
1033 in_features: usize,
1034 canonical_chunk_rows: Option<usize>,
1035}
1036
1037pub struct ResidentBf16RowParallel {
1038 ranks: Vec<ResidentBf16Rank>,
1039 out_features: usize,
1040 in_features: usize,
1041}
1042
1043pub struct ResidentStepBf16RowParallel {
1044 ranks: Vec<Vec<ResidentBf16Rank>>,
1045 out_features: usize,
1046 in_features: usize,
1047 canonical_chunk_cols: usize,
1048}
1049
1050pub struct ResidentSigmoidTopKRouter {
1052 weight: CudaSlice<f32>,
1053 correction_bias: CudaSlice<f32>,
1054 active: CudaSlice<u8>,
1055 root_device: usize,
1056 input_width: usize,
1057 expert_count: usize,
1058 experts_per_token: usize,
1059 active_count: usize,
1060 scaling_factor: f32,
1061 route_norm: bool,
1062}
1063
1064pub struct SigmoidTopKHostOutput {
1065 pub logits: Vec<f32>,
1066 pub selected: Vec<u32>,
1067 pub weights: Vec<f32>,
1068}
1069
1070pub struct ResidentReplicatedBf16SwiGlu {
1072 gate: Vec<ResidentBf16Rank>,
1073 up: Vec<ResidentBf16Rank>,
1074 down: Vec<ResidentBf16Rank>,
1075 input_width: usize,
1076 intermediate_width: usize,
1077}
1078
1079pub struct ResidentReplicatedDeviceRows {
1084 ranks: Vec<CudaSlice<f32>>,
1085 tokens: usize,
1086 width: usize,
1087}
1088
1089impl ResidentReplicatedDeviceRows {
1090 pub fn tokens(&self) -> usize {
1091 self.tokens
1092 }
1093
1094 pub fn width(&self) -> usize {
1095 self.width
1096 }
1097
1098 pub fn ranks(&self) -> usize {
1099 self.ranks.len()
1100 }
1101}
1102
1103pub fn moe_residual_host(
1105 residual: &[f32],
1106 routed: &[f32],
1107 shared: &[f32],
1108) -> Result<Vec<f32>, String> {
1109 if residual.len() != routed.len() || residual.len() != shared.len() {
1110 return Err(format!(
1111 "MoE residual lengths residual={} routed={} shared={}",
1112 residual.len(),
1113 routed.len(),
1114 shared.len()
1115 ));
1116 }
1117 let ffn = routed
1118 .iter()
1119 .zip(shared)
1120 .map(|(&routed, &shared)| routed + shared)
1121 .collect::<Vec<_>>();
1122 Ok(residual
1123 .iter()
1124 .zip(ffn)
1125 .map(|(&residual, ffn)| residual + ffn)
1126 .collect())
1127}
1128
1129pub use memra_kv::{
1130 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1131};
1132
1133pub struct ResidentTpExpert {
1139 gate: ResidentColumnParallel,
1140 up: ResidentColumnParallel,
1141 down: ResidentRowParallel,
1142 input_width: usize,
1143 expert_width: usize,
1144}
1145
1146struct ResidentE4m3ExpertBankRank {
1147 codes: CudaSlice<u8>,
1148 scales: CudaSlice<f32>,
1149 expert_range: Range<usize>,
1150 out_features: usize,
1151 in_features: usize,
1152 code_stride: usize,
1153 scale_stride: usize,
1154 k_blocks: Option<usize>,
1157}
1158
1159struct PackedE4m3ExpertBankRank {
1160 codes: Vec<u8>,
1161 scales: Vec<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>,
1168}
1169
1170struct ResidentEpRank {
1171 gate: ResidentE4m3ExpertBankRank,
1172 up: ResidentE4m3ExpertBankRank,
1173 down: ResidentE4m3ExpertBankRank,
1174}
1175
1176pub struct ResidentExpertParallel {
1183 ranks: Vec<ResidentEpRank>,
1184 expert_count: usize,
1185 input_width: usize,
1186 expert_width: usize,
1187}
1188
1189pub struct StepGroupedFp8ProjectionOutput {
1194 pub gate: Vec<f32>,
1195 pub up: Vec<f32>,
1196 pub down: Vec<f32>,
1197}
1198
1199pub struct PreparedStepGroupedFp8Gate {
1204 device: usize,
1205 gate: ResidentE4m3ExpertBankRank,
1206 up: ResidentE4m3ExpertBankRank,
1207 down: ResidentE4m3ExpertBankRank,
1208 input: CudaSlice<f32>,
1209 route_csr: DeviceExpertCsr,
1210 down_csr: DeviceExpertCsr,
1211 gate_workspace: Fp8GroupedWorkspace,
1212 up_workspace: Fp8GroupedWorkspace,
1213 down_workspace: Fp8GroupedWorkspace,
1214 activation: CudaSlice<f32>,
1215 activation_limit: Option<f32>,
1216 tokens: usize,
1217 pairs: usize,
1218}
1219
1220impl PreparedStepGroupedFp8Gate {
1221 pub fn tokens(&self) -> usize {
1222 self.tokens
1223 }
1224
1225 pub fn pairs(&self) -> usize {
1226 self.pairs
1227 }
1228}
1229
1230struct PreparedStepGroupedExpertOwner {
1231 rank: usize,
1232 global_pairs: Vec<usize>,
1233 route_csr: DeviceExpertCsr,
1234 down_csr: DeviceExpertCsr,
1235 gate_workspace: Fp8GroupedWorkspace,
1236 up_workspace: Fp8GroupedWorkspace,
1237 down_workspace: Fp8GroupedWorkspace,
1238 activation: CudaSlice<f32>,
1239}
1240
1241struct StepGroupedExpertOwnerSchedule {
1242 global_pairs: Vec<usize>,
1243 route_csr: ExpertCsr,
1244 down_csr: ExpertCsr,
1245}
1246
1247pub struct PreparedStepGroupedExpertParallelGate {
1253 rank_inputs: Vec<CudaSlice<f32>>,
1254 owners: Vec<PreparedStepGroupedExpertOwner>,
1255 activation_limit: Option<f32>,
1256 tokens: usize,
1257 pairs: usize,
1258 max_tokens: usize,
1259 max_pairs: usize,
1260 input_width: usize,
1261 expert_width: usize,
1262 generation: u64,
1263 executed_generation: Option<u64>,
1264 ready: bool,
1265}
1266
1267impl PreparedStepGroupedExpertParallelGate {
1268 pub fn tokens(&self) -> usize {
1269 self.tokens
1270 }
1271
1272 pub fn pairs(&self) -> usize {
1273 self.pairs
1274 }
1275
1276 pub fn max_tokens(&self) -> usize {
1277 self.max_tokens
1278 }
1279
1280 pub fn input_width(&self) -> usize {
1281 self.input_width
1282 }
1283
1284 pub fn expert_width(&self) -> usize {
1285 self.expert_width
1286 }
1287
1288 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1289 validate_step_expert_activation_limit(limit)?;
1290 self.activation_limit = limit;
1291 self.executed_generation = None;
1292 Ok(())
1293 }
1294
1295 pub fn active_owners(&self) -> usize {
1296 self.owners
1297 .iter()
1298 .filter(|owner| !owner.global_pairs.is_empty())
1299 .count()
1300 }
1301
1302 pub fn owner_pair_counts(&self) -> Vec<usize> {
1303 self.owners
1304 .iter()
1305 .map(|owner| owner.global_pairs.len())
1306 .collect()
1307 }
1308
1309 pub fn generation(&self) -> u64 {
1310 self.generation
1311 }
1312}
1313
1314struct PreparedPeerWeightedRouteOwner {
1315 token_rows: CudaSlice<i32>,
1316 slots: CudaSlice<i32>,
1317 weights: CudaSlice<f32>,
1318 active_pairs: usize,
1319}
1320
1321pub struct PreparedPeerWeightedRouteCombine {
1327 root_device: usize,
1328 owners: Vec<PreparedPeerWeightedRouteOwner>,
1329 peer_staging: CudaSlice<f32>,
1330 slots: CudaSlice<f32>,
1331 weights: CudaSlice<f32>,
1332 output: CudaSlice<f32>,
1333 peer_devices: Vec<usize>,
1334 peer_outputs: Vec<CudaSlice<f32>>,
1335 width: usize,
1336 experts_per_token: usize,
1337 max_tokens: usize,
1338 max_pairs: usize,
1339 tokens: usize,
1340 pairs: usize,
1341 projection_generation: u64,
1342 output_generation: Option<u64>,
1343 broadcast_generation: Option<u64>,
1344 ready: bool,
1345}
1346
1347impl PreparedPeerWeightedRouteCombine {
1348 pub fn tokens(&self) -> usize {
1349 self.tokens
1350 }
1351
1352 pub fn pairs(&self) -> usize {
1353 self.pairs
1354 }
1355
1356 pub fn owner_pair_counts(&self) -> Vec<usize> {
1357 self.owners.iter().map(|owner| owner.active_pairs).collect()
1358 }
1359
1360 pub fn distributed_ranks(&self) -> usize {
1361 1 + self.peer_outputs.len()
1362 }
1363}
1364
1365struct ResidentTpExpertBank {
1366 gate: Vec<ResidentE4m3ExpertBankRank>,
1367 up: Vec<ResidentE4m3ExpertBankRank>,
1368 down: Vec<ResidentE4m3ExpertBankRank>,
1369 expert_count: usize,
1370 input_width: usize,
1371 expert_width: usize,
1372}
1373
1374pub struct ResidentTensorParallel {
1380 bank: ResidentTpExpertBank,
1381}
1382
1383pub struct TpE4m3HostBounce {
1389 devices: Vec<usize>,
1390 ranks: Vec<Engine>,
1391 native_p2p: bool,
1392 ep_device_arithmetic: bool,
1393 bulk_p2p: bool,
1394 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1397}
1398
1399pub enum StepTpGateShards<'a> {
1408 F32(&'a [crate::CudaSlice<f32>]),
1409 Bf16(&'a [crate::CudaSlice<u8>]),
1410}
1411
1412pub struct StepTpDecodeV2Ws {
1413 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1417 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1418 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1419 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1420 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1421 pub(crate) tcol_cap: usize,
1422 w8_aq: Vec<CudaSlice<i8>>,
1426 w8_ad: Vec<CudaSlice<f32>>,
1427 w8_in: usize,
1428 w8o_aq: Vec<CudaSlice<i8>>,
1431 w8o_ad: Vec<CudaSlice<f32>>,
1432 w8o_in: usize,
1433 w8t_aq: Vec<CudaSlice<i8>>,
1437 w8t_ad: Vec<CudaSlice<f32>>,
1438 w8t_in: usize,
1439 w8t_oaq: Vec<CudaSlice<i8>>,
1440 w8t_oad: Vec<CudaSlice<f32>>,
1441 w8t_oin: usize,
1442 w8t_cap: usize,
1443 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1450 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1451 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1452 pub(crate) fa2_cap: usize,
1453 rope_k_t: Vec<CudaSlice<f32>>,
1457 rope_ctr_t: Vec<CudaSlice<u32>>,
1458 rope_pos_t: Vec<CudaSlice<i32>>,
1459 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1463 rows_tab_t: Vec<CudaSlice<u64>>,
1473 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1477 tcol_gated: Vec<CudaSlice<f32>>,
1478 tcol_opart: Vec<CudaSlice<f32>>,
1479 tcol_opeer: Option<CudaSlice<f32>>,
1480 tcol_omix: Option<CudaSlice<f32>>,
1481 tcol_ocap: usize,
1482 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1485 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1486 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1487 pub(crate) q: Vec<CudaSlice<f32>>,
1488 pub(crate) k: Vec<CudaSlice<f32>>,
1489 pub(crate) pos: Vec<CudaSlice<i32>>,
1490 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1492 pub(crate) gate: Vec<CudaSlice<f32>>,
1493 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1494 pub(crate) gated: Vec<CudaSlice<f32>>,
1495 o_partials: Vec<Vec<CudaSlice<f32>>>,
1497 ev_rank: Vec<CudaEvent>,
1499 peer_partial: CudaSlice<f32>,
1501 reduce_a: CudaSlice<f32>,
1502 reduce_b: CudaSlice<f32>,
1503 zeros: CudaSlice<f32>,
1505 pub(crate) k_shadow: CudaSlice<f32>,
1506 pub(crate) v_shadow: CudaSlice<f32>,
1507 ev_refresh: CudaEvent,
1508 ev_oproj: CudaEvent,
1509 gate_e: CudaSlice<f32>,
1511 pub(crate) h_stage: Option<CudaSlice<f32>>,
1514 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1515 attn_in: Vec<CudaSlice<f32>>,
1519 raw_h_stage: u64,
1521 raw_pos_stage: u64,
1522 raw_attn_in: Vec<u64>,
1523 raw_pos: Vec<u64>,
1524 raw_o_partial1: u64,
1525 raw_peer_partial: u64,
1526 raw_k1: u64,
1527 raw_v1: u64,
1528 raw_k_shadow: u64,
1529 raw_v_shadow: u64,
1530 raw_mixed_stage_e: u64,
1534 raw_reduce_a: u64,
1535 raw_shadow_stage_e: (u64, u64),
1536 ev_entry: CudaEvent,
1537 e_device: usize,
1538 local_q_dim: usize,
1540 local_kv_dim: usize,
1541 heads: usize,
1542 pub(crate) o_out: usize,
1543 o_block_cols: usize,
1544 blocks_per_rank: usize,
1545}
1546
1547impl TpE4m3HostBounce {
1548 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1549 Self::new_inner(devices, false, false, false, false)
1550 }
1551
1552 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1553 Self::new_inner(devices, false, true, false, false)
1554 }
1555
1556 pub fn new_native_p2p_device_arithmetic(
1557 devices: &[usize],
1558 ) -> Result<Self, Box<dyn std::error::Error>> {
1559 Self::new_inner(devices, false, true, true, false)
1560 }
1561
1562 pub(crate) fn new_configured(
1563 devices: &[usize],
1564 native_p2p: bool,
1565 ep_device_arithmetic: bool,
1566 bulk_p2p: bool,
1567 ) -> Result<Self, Box<dyn std::error::Error>> {
1568 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1569 }
1570
1571 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1576 Self::new_inner(&[device], true, false, false, false)
1577 }
1578
1579 fn new_inner(
1580 devices: &[usize],
1581 allow_single_rank: bool,
1582 native_p2p: bool,
1583 ep_device_arithmetic: bool,
1584 bulk_p2p: bool,
1585 ) -> Result<Self, Box<dyn std::error::Error>> {
1586 if ep_device_arithmetic && !native_p2p {
1587 return Err("device-resident EP arithmetic requires native P2P".into());
1588 }
1589 if bulk_p2p && !native_p2p {
1590 return Err("bulk TP transport requires native P2P".into());
1591 }
1592 let minimum = if allow_single_rank { 1 } else { 2 };
1593 if !(minimum..=8).contains(&devices.len()) {
1594 return Err(format!(
1595 "TP reference requires {minimum}..=8 devices, got {}",
1596 devices.len()
1597 )
1598 .into());
1599 }
1600 let mut unique = devices.to_vec();
1601 unique.sort_unstable();
1602 unique.dedup();
1603 if unique.len() != devices.len() {
1604 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1605 }
1606 let ranks = devices
1607 .iter()
1608 .map(|&device| Engine::new(device))
1609 .collect::<Result<Vec<_>, _>>()?;
1610 if native_p2p {
1611 configure_native_p2p(&ranks, devices)?;
1612 }
1613 if allow_single_rank {
1614 eprintln!(
1615 "[tp] canonical oracle transport=local device={} performance_claim=false",
1616 devices[0]
1617 );
1618 } else if native_p2p {
1619 if ep_device_arithmetic {
1620 eprintln!(
1621 "[tp] correctness transport=native-p2p devices={devices:?} \
1622 native_p2p=true activation=device-host-exact \
1623 accumulation=device-host-exact output=root-readback \
1624 bulk_p2p={bulk_p2p} performance_claim=false"
1625 );
1626 } else {
1627 eprintln!(
1628 "[tp] correctness transport=native-p2p devices={devices:?} \
1629 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1630 performance_claim=false"
1631 );
1632 }
1633 } else {
1634 eprintln!(
1635 "[tp] correctness transport=host-bounce devices={devices:?} \
1636 native_p2p=false performance_claim=false"
1637 );
1638 }
1639 Ok(Self {
1640 devices: devices.to_vec(),
1641 ranks,
1642 native_p2p,
1643 ep_device_arithmetic,
1644 bulk_p2p,
1645 decode_v2: std::sync::Mutex::new(Vec::new()),
1646 })
1647 }
1648
1649 pub fn devices(&self) -> &[usize] {
1650 &self.devices
1651 }
1652
1653 pub fn native_p2p(&self) -> bool {
1654 self.native_p2p
1655 }
1656
1657 pub fn bulk_p2p(&self) -> bool {
1658 self.bulk_p2p
1659 }
1660
1661 pub fn expert_activation_label(&self) -> &'static str {
1662 if self.ep_device_arithmetic {
1663 "device-host-exact"
1664 } else {
1665 "host-canonical"
1666 }
1667 }
1668
1669 pub fn expert_accumulation_label(&self) -> &'static str {
1670 self.expert_activation_label()
1671 }
1672
1673 pub fn expert_output_label(&self) -> &'static str {
1674 if self.ep_device_arithmetic {
1675 "root-readback"
1676 } else {
1677 "host-accumulated"
1678 }
1679 }
1680
1681 pub fn transport_label(&self) -> &'static str {
1682 if self.devices.len() == 1 {
1683 "local"
1684 } else if self.native_p2p {
1685 "native-p2p"
1686 } else {
1687 "host-bounce"
1688 }
1689 }
1690
1691 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1692 self.ranks
1693 .iter()
1694 .map(|rank| rank.ctx().name().map_err(Into::into))
1695 .collect()
1696 }
1697
1698 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1704 self.ranks.get(rank)
1705 }
1706
1707 pub fn allocate_tp_kv_cache(
1708 &self,
1709 kv_dim_k: usize,
1710 kv_dim_v: usize,
1711 capacity: usize,
1712 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1713 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1714 }
1715
1716 pub fn allocate_tp_swa_kv_cache(
1717 &self,
1718 kv_dim_k: usize,
1719 kv_dim_v: usize,
1720 capacity: usize,
1721 window: usize,
1722 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1723 if window == 0 {
1724 return Err("TP SWA KV window must be nonzero".into());
1725 }
1726 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1727 }
1728
1729 fn allocate_tp_kv_cache_inner(
1730 &self,
1731 kv_dim_k: usize,
1732 kv_dim_v: usize,
1733 capacity: usize,
1734 window: Option<usize>,
1735 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1736 if capacity == 0 || capacity > i32::MAX as usize {
1737 return Err(
1738 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1739 );
1740 }
1741 let tp = self.ranks.len();
1742 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1743 let physical_rows = window
1744 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1745 .unwrap_or(capacity);
1746 let k_plane_bytes = physical_rows
1747 .checked_mul(shape.k_token_bytes)
1748 .and_then(|bytes| bytes.checked_add(8))
1749 .ok_or("TP KV K plane-byte overflow")?;
1750 let v_plane_bytes = physical_rows
1751 .checked_mul(shape.v_token_bytes)
1752 .and_then(|bytes| bytes.checked_add(8))
1753 .ok_or("TP KV V plane-byte overflow")?;
1754 let mut ranks = Vec::with_capacity(tp);
1755 for engine in &self.ranks {
1756 let _main = engine.gpu.enter_main()?;
1757 ranks.push(ResidentTpKvCacheRank::new(
1758 engine.alloc_u8(k_plane_bytes)?,
1759 engine.alloc_u8(v_plane_bytes)?,
1760 engine.htod_i32(&[0])?,
1761 ));
1762 }
1763 Ok(match window {
1764 Some(window) => ResidentTpKvCache::new_swa(
1765 ranks,
1766 shape.kv_dim_k,
1767 shape.kv_dim_v,
1768 shape.k_token_bytes,
1769 shape.v_token_bytes,
1770 capacity,
1771 window,
1772 ),
1773 None => ResidentTpKvCache::new(
1774 ranks,
1775 shape.kv_dim_k,
1776 shape.kv_dim_v,
1777 shape.k_token_bytes,
1778 shape.v_token_bytes,
1779 capacity,
1780 ),
1781 })
1782 }
1783
1784 pub fn grow_tp_kv_cache(
1785 &self,
1786 source: &ResidentTpKvCache,
1787 target_capacity: usize,
1788 rows: usize,
1789 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1790 self.validate_tp_kv_cache(source)?;
1791 let plan = source.prepare_grow(target_capacity, rows)?;
1792 let ranks = self.ranks.len();
1793 let global_k = source
1794 .kv_dim_k()
1795 .checked_mul(ranks)
1796 .ok_or("TP KV grow global K dimension overflow")?;
1797 let global_v = source
1798 .kv_dim_v()
1799 .checked_mul(ranks)
1800 .ok_or("TP KV grow global V dimension overflow")?;
1801 let mut target = match source.ring_window() {
1802 Some(window) => {
1803 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1804 }
1805 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1806 };
1807 self.validate_tp_kv_cache(&target)?;
1808
1809 for (rank, engine) in self.ranks.iter().enumerate() {
1810 let _main = engine.gpu.enter_main()?;
1811 let src = source
1812 .rank(rank)
1813 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1814 let dst = target
1815 .rank_mut(rank)
1816 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1817 if plan.k_bytes() > 0 {
1818 engine.copy_u8_range_into(
1819 dst.k_mut(),
1820 0,
1821 src.k(),
1822 plan.source_row() * source.k_tok_bytes(),
1823 plan.k_bytes(),
1824 )?;
1825 }
1826 if plan.v_bytes() > 0 {
1827 engine.copy_u8_range_into(
1828 dst.v_mut(),
1829 0,
1830 src.v(),
1831 plan.source_row() * source.v_tok_bytes(),
1832 plan.v_bytes(),
1833 )?;
1834 }
1835 }
1836 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1837
1838 for engine in &self.ranks {
1841 let _main = engine.gpu.enter_main()?;
1842 engine.stream().synchronize()?;
1843 }
1844 let physical_copy_rows = plan.copy_rows();
1845 target.publish_grow(plan)?;
1846 eprintln!(
1847 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1848 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1849 rank_streams_synchronized=true generation_preserved=true",
1850 rows,
1851 source.capacity(),
1852 target_capacity,
1853 ranks,
1854 physical_copy_rows,
1855 source.ring_window(),
1856 );
1857 Ok(target)
1858 }
1859
1860 pub fn hydrate_tp_kv_cache(
1861 &self,
1862 cache: &mut ResidentTpKvCache,
1863 rows: usize,
1864 k_rows: &[u8],
1865 v_rows: &[u8],
1866 ) -> Result<(), Box<dyn std::error::Error>> {
1867 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1868 }
1869
1870 pub fn hydrate_tp_kv_cache_from(
1871 &self,
1872 cache: &mut ResidentTpKvCache,
1873 logical_len: usize,
1874 resident_start: usize,
1875 k_rows: &[u8],
1876 v_rows: &[u8],
1877 ) -> Result<(), Box<dyn std::error::Error>> {
1878 self.validate_tp_kv_cache(cache)?;
1879 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1880 return Err(format!(
1881 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1882 cache.committed_len(),
1883 cache.staged_len()
1884 )
1885 .into());
1886 }
1887 if resident_start > logical_len || logical_len > cache.capacity() {
1888 return Err(format!(
1889 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1890 cache.capacity(),
1891 )
1892 .into());
1893 }
1894 let rows = logical_len - resident_start;
1895 if rows > cache.physical_capacity() {
1896 return Err(format!(
1897 "TP KV hydration rows {rows} exceed physical capacity {}",
1898 cache.physical_capacity()
1899 )
1900 .into());
1901 }
1902 for rank in 0..self.ranks.len() {
1903 let k_rank =
1904 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1905 let v_rank =
1906 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1907 let engine = &self.ranks[rank];
1908 let _main = engine.gpu.enter_main()?;
1909 let rank_cache = cache
1910 .rank_mut(rank)
1911 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1912 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1913 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1914 }
1915 cache.publish_hydration(logical_len, resident_start)?;
1916 Ok(())
1917 }
1918
1919 pub fn append_tp_kv_transaction(
1920 &self,
1921 cache: &mut ResidentTpKvCache,
1922 transaction: TpKvTransaction,
1923 k_shards: &[CudaSlice<f32>],
1924 v_shards: &[CudaSlice<f32>],
1925 rows: usize,
1926 ) -> Result<(), Box<dyn std::error::Error>> {
1927 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1928 }
1929
1930 #[allow(clippy::too_many_arguments)]
1935 pub fn append_tp_kv_transaction_inner(
1936 &self,
1937 cache: &mut ResidentTpKvCache,
1938 transaction: TpKvTransaction,
1939 k_shards: &[CudaSlice<f32>],
1940 v_shards: &[CudaSlice<f32>],
1941 rows: usize,
1942 external_rank_appends: bool,
1943 ) -> Result<(), Box<dyn std::error::Error>> {
1944 self.validate_tp_kv_cache(cache)?;
1945 let plan = cache.prepare_append(transaction, rows)?;
1946 let target = plan.target();
1947 let expected_k = rows
1948 .checked_mul(cache.kv_dim_k())
1949 .ok_or("TP KV K append size overflow")?;
1950 let expected_v = rows
1951 .checked_mul(cache.kv_dim_v())
1952 .ok_or("TP KV V append size overflow")?;
1953 if !external_rank_appends
1956 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1957 {
1958 return Err(format!(
1959 "TP KV append shard counts k={} v={} != ranks {}",
1960 k_shards.len(),
1961 v_shards.len(),
1962 self.ranks.len()
1963 )
1964 .into());
1965 }
1966 let kv_dim_k = cache.kv_dim_k();
1967 let kv_dim_v = cache.kv_dim_v();
1968 let k_tok_bytes = cache.k_tok_bytes();
1969 let v_tok_bytes = cache.v_tok_bytes();
1970 if let Some(KvRingAppend::Rebase {
1971 src_row,
1972 keep_rows,
1973 new_base,
1974 ..
1975 }) = plan.ring_append()
1976 {
1977 for rank in 0..self.ranks.len() {
1978 let engine = &self.ranks[rank];
1979 let _main = engine.gpu.enter_main()?;
1980 let rank_cache = cache
1981 .rank_mut(rank)
1982 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1983 if keep_rows > 0 {
1984 let k_len = keep_rows
1985 .checked_mul(k_tok_bytes)
1986 .ok_or("TP KV K rebase-byte overflow")?;
1987 let v_len = keep_rows
1988 .checked_mul(v_tok_bytes)
1989 .ok_or("TP KV V rebase-byte overflow")?;
1990 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1991 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
1992 engine.copy_u8_range_into(
1993 &mut k_tmp,
1994 0,
1995 rank_cache.k(),
1996 src_row * k_tok_bytes,
1997 k_len,
1998 )?;
1999 engine.copy_u8_range_into(
2000 &mut v_tmp,
2001 0,
2002 rank_cache.v(),
2003 src_row * v_tok_bytes,
2004 v_len,
2005 )?;
2006 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2007 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2008 }
2009 if rank_cache.base_d().is_some() {
2013 let value = new_base as i32;
2014 let rank_cache = cache
2015 .rank_mut(rank)
2016 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2017 if let Some(base_d) = rank_cache.base_d_mut() {
2018 engine.set_i32_one(base_d, value)?;
2019 }
2020 }
2021 }
2022 }
2023 cache.publish_append_rebase(plan)?;
2024 let write_row = plan.write_row();
2025 for rank in 0..self.ranks.len() {
2026 if external_rank_appends {
2027 break;
2028 }
2029 let engine = &self.ranks[rank];
2030 let _main = engine.gpu.enter_main()?;
2031 if k_shards[rank].len() != expected_k
2032 || v_shards[rank].len() != expected_v
2033 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2034 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2035 {
2036 return Err(format!(
2037 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2038 != expected {expected_k}/{expected_v} on device {}",
2039 k_shards[rank].len(),
2040 k_shards[rank].ordinal(),
2041 v_shards[rank].len(),
2042 v_shards[rank].ordinal(),
2043 engine.ctx().ordinal(),
2044 )
2045 .into());
2046 }
2047 let rank_cache = cache
2048 .rank_mut(rank)
2049 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2050 let (rank_k, rank_v) = rank_cache.planes_mut();
2051 engine.append_kv_quantized_rows(
2052 &k_shards[rank],
2053 &v_shards[rank],
2054 rank_k,
2055 rank_v,
2056 write_row,
2057 rows,
2058 kv_dim_k,
2059 kv_dim_v,
2060 k_tok_bytes,
2061 v_tok_bytes,
2062 Engine::kv_fp8_on(),
2063 )?;
2064 }
2065 if !external_rank_appends {
2066 self.set_tp_kv_len_mirrors(cache, target)?;
2069 }
2070 cache.publish_append_plan(plan)?;
2071 Ok(())
2072 }
2073
2074 pub fn commit_tp_kv_transaction(
2075 &self,
2076 cache: &mut ResidentTpKvCache,
2077 transaction: TpKvTransaction,
2078 accepted_rows: usize,
2079 ) -> Result<(), Box<dyn std::error::Error>> {
2080 self.validate_tp_kv_cache(cache)?;
2081 let target = cache.commit_target(transaction, accepted_rows)?;
2082 self.set_tp_kv_len_mirrors(cache, target)?;
2083 cache.publish_finalize(transaction, target)?;
2084 Ok(())
2085 }
2086
2087 pub fn commit_tp_kv_transaction_external(
2093 &self,
2094 cache: &mut ResidentTpKvCache,
2095 transaction: TpKvTransaction,
2096 accepted_rows: usize,
2097 ) -> Result<(), Box<dyn std::error::Error>> {
2098 self.validate_tp_kv_cache(cache)?;
2099 let target = cache.commit_target(transaction, accepted_rows)?;
2100 cache.publish_finalize(transaction, target)?;
2101 Ok(())
2102 }
2103
2104 pub fn rollback_tp_kv_transaction(
2105 &self,
2106 cache: &mut ResidentTpKvCache,
2107 transaction: TpKvTransaction,
2108 ) -> Result<(), Box<dyn std::error::Error>> {
2109 self.validate_tp_kv_cache(cache)?;
2110 cache.validate_transaction(transaction)?;
2111 let target = transaction.base_len();
2112 self.set_tp_kv_len_mirrors(cache, target)?;
2113 cache.publish_finalize(transaction, target)?;
2114 Ok(())
2115 }
2116
2117 pub fn tp_kv_device_lengths(
2118 &self,
2119 cache: &ResidentTpKvCache,
2120 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2121 self.validate_tp_kv_cache(cache)?;
2122 let mut lengths = Vec::with_capacity(self.ranks.len());
2123 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2124 let _main = engine.gpu.enter_main()?;
2125 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2126 }
2127 Ok(lengths)
2128 }
2129
2130 fn set_tp_kv_len_mirrors(
2131 &self,
2132 cache: &mut ResidentTpKvCache,
2133 len: usize,
2134 ) -> Result<(), Box<dyn std::error::Error>> {
2135 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2136 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2137 let _main = engine.gpu.enter_main()?;
2138 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2139 }
2140 Ok(())
2141 }
2142
2143 fn validate_tp_kv_cache(
2144 &self,
2145 cache: &ResidentTpKvCache,
2146 ) -> Result<(), Box<dyn std::error::Error>> {
2147 if cache.ranks_len() != self.ranks.len() {
2148 return Err(format!(
2149 "TP KV cache ranks {} != runtime ranks {}",
2150 cache.ranks_len(),
2151 self.ranks.len()
2152 )
2153 .into());
2154 }
2155 let expected_k = cache
2156 .physical_capacity()
2157 .checked_mul(cache.k_tok_bytes())
2158 .and_then(|bytes| bytes.checked_add(8))
2159 .ok_or("TP KV K plane validation overflow")?;
2160 let expected_v = cache
2161 .physical_capacity()
2162 .checked_mul(cache.v_tok_bytes())
2163 .and_then(|bytes| bytes.checked_add(8))
2164 .ok_or("TP KV V plane validation overflow")?;
2165 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2166 let device = engine.ctx().ordinal();
2167 if rank_cache.k().len() != expected_k
2168 || rank_cache.v().len() != expected_v
2169 || rank_cache.len_d().len() != 1
2170 || rank_cache.k().ordinal() != device
2171 || rank_cache.v().ordinal() != device
2172 || rank_cache.len_d().ordinal() != device
2173 {
2174 return Err(format!(
2175 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2176 )
2177 .into());
2178 }
2179 }
2180 Ok(())
2181 }
2182
2183 pub fn full(
2184 &self,
2185 matrix: E4m3BlockMatrix<'_>,
2186 activations: &[f32],
2187 tokens: usize,
2188 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2189 matrix.validate()?;
2190 validate_activations(activations, tokens, matrix.in_features)?;
2191 run_rank(&self.ranks[0], matrix, activations, tokens)
2192 }
2193
2194 pub fn column_parallel(
2198 &self,
2199 matrix: E4m3BlockMatrix<'_>,
2200 activations: &[f32],
2201 tokens: usize,
2202 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2203 matrix.validate()?;
2204 validate_activations(activations, tokens, matrix.in_features)?;
2205 let tp = self.ranks.len();
2206 if matrix.out_features % tp != 0 {
2207 return Err(format!(
2208 "column-parallel out_features {} is not divisible by TP={tp}",
2209 matrix.out_features
2210 )
2211 .into());
2212 }
2213 let local_out = matrix.out_features / tp;
2214 if local_out % FP8_BLOCK != 0 {
2215 return Err(format!(
2216 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2217 E4M3 scale block"
2218 )
2219 .into());
2220 }
2221
2222 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2223 let mut rank_outputs = Vec::with_capacity(tp);
2224 for (rank_index, rank) in self.ranks.iter().enumerate() {
2225 let shard = column_shard(matrix, tp, rank_index)?;
2226 let output = run_rank(rank, shard, activations, tokens)?;
2227 let row_start = rank_index * local_out;
2228 for token in 0..tokens {
2229 gathered[token * matrix.out_features + row_start
2230 ..token * matrix.out_features + row_start + local_out]
2231 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2232 }
2233 rank_outputs.push(output);
2234 }
2235 Ok(ColumnParallelResult {
2236 gathered,
2237 rank_outputs,
2238 })
2239 }
2240
2241 pub fn upload_column_parallel(
2242 &self,
2243 matrix: E4m3BlockMatrix<'_>,
2244 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2245 matrix.validate()?;
2246 let tp = self.ranks.len();
2247 validate_column_shape(matrix, tp)?;
2248 let mut ranks = Vec::with_capacity(tp);
2249 for (rank_index, engine) in self.ranks.iter().enumerate() {
2250 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2251 }
2252 Ok(ResidentColumnParallel {
2253 ranks,
2254 out_features: matrix.out_features,
2255 in_features: matrix.in_features,
2256 })
2257 }
2258
2259 pub fn column_parallel_resident(
2260 &self,
2261 matrix: &ResidentColumnParallel,
2262 activations: &[f32],
2263 tokens: usize,
2264 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2265 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2266 validate_activations(activations, tokens, matrix.in_features)?;
2267 let local_out = matrix.out_features / self.ranks.len();
2268 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2269 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2270 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2271 let output = run_resident_rank(engine, shard, activations, tokens)?;
2272 let row_start = rank_index * local_out;
2273 for token in 0..tokens {
2274 gathered[token * matrix.out_features + row_start
2275 ..token * matrix.out_features + row_start + local_out]
2276 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2277 }
2278 rank_outputs.push(output);
2279 }
2280 Ok(ColumnParallelResult {
2281 gathered,
2282 rank_outputs,
2283 })
2284 }
2285
2286 pub fn row_parallel(
2290 &self,
2291 matrix: E4m3BlockMatrix<'_>,
2292 activations: &[f32],
2293 tokens: usize,
2294 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2295 matrix.validate()?;
2296 validate_activations(activations, tokens, matrix.in_features)?;
2297 let tp = self.ranks.len();
2298 if matrix.in_features % tp != 0 {
2299 return Err(format!(
2300 "row-parallel in_features {} is not divisible by TP={tp}",
2301 matrix.in_features
2302 )
2303 .into());
2304 }
2305 let local_in = matrix.in_features / tp;
2306 if local_in % FP8_BLOCK != 0 {
2307 return Err(format!(
2308 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2309 E4M3 scale block"
2310 )
2311 .into());
2312 }
2313
2314 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2315 let mut rank_partials = Vec::with_capacity(tp);
2316 for (rank_index, rank) in self.ranks.iter().enumerate() {
2317 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2318 let local_activations =
2319 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2320 let shard = E4m3BlockMatrix {
2321 codes: &codes,
2322 scales: &scales,
2323 out_features: matrix.out_features,
2324 in_features: local_in,
2325 };
2326 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2327 for (sum, value) in reduced.iter_mut().zip(&partial) {
2328 *sum += *value;
2329 }
2330 rank_partials.push(partial);
2331 }
2332 Ok(RowParallelResult {
2333 reduced,
2334 rank_partials,
2335 })
2336 }
2337
2338 pub fn upload_row_parallel(
2339 &self,
2340 matrix: E4m3BlockMatrix<'_>,
2341 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2342 matrix.validate()?;
2343 let tp = self.ranks.len();
2344 validate_row_shape(matrix, tp)?;
2345 let local_in = matrix.in_features / tp;
2346 let mut ranks = Vec::with_capacity(tp);
2347 for (rank_index, engine) in self.ranks.iter().enumerate() {
2348 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2349 ranks.push(upload_rank(
2350 engine,
2351 E4m3BlockMatrix {
2352 codes: &codes,
2353 scales: &scales,
2354 out_features: matrix.out_features,
2355 in_features: local_in,
2356 },
2357 )?);
2358 }
2359 Ok(ResidentRowParallel {
2360 ranks,
2361 out_features: matrix.out_features,
2362 in_features: matrix.in_features,
2363 })
2364 }
2365
2366 pub fn row_parallel_resident(
2367 &self,
2368 matrix: &ResidentRowParallel,
2369 activations: &[f32],
2370 tokens: usize,
2371 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2372 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2373 validate_activations(activations, tokens, matrix.in_features)?;
2374 let tp = self.ranks.len();
2375 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2376 let mut rank_partials = Vec::with_capacity(tp);
2377 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2378 let local_activations =
2379 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2380 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2381 for (sum, value) in reduced.iter_mut().zip(&partial) {
2382 *sum += *value;
2383 }
2384 rank_partials.push(partial);
2385 }
2386 Ok(RowParallelResult {
2387 reduced,
2388 rank_partials,
2389 })
2390 }
2391
2392 pub fn upload_bf16_column_parallel(
2393 &self,
2394 matrix: Bf16Matrix<'_>,
2395 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2396 self.upload_bf16_column_parallel_inner(matrix, None, false)
2397 }
2398
2399 pub fn upload_step_bf16_column_parallel(
2401 &self,
2402 matrix: Bf16Matrix<'_>,
2403 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2404 self.upload_step_bf16_column_parallel_inner(matrix, false)
2405 }
2406
2407 pub fn upload_step_bf16_column_parallel_f32_mirror(
2412 &self,
2413 matrix: Bf16Matrix<'_>,
2414 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2415 self.upload_step_bf16_column_parallel_inner(matrix, true)
2416 }
2417
2418 fn upload_step_bf16_column_parallel_inner(
2419 &self,
2420 matrix: Bf16Matrix<'_>,
2421 f32_mirror: bool,
2422 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2423 let canonical_chunk_rows =
2424 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2425 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2426 }
2427
2428 fn upload_bf16_column_parallel_inner(
2429 &self,
2430 matrix: Bf16Matrix<'_>,
2431 canonical_chunk_rows: Option<usize>,
2432 f32_mirror: bool,
2433 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2434 matrix.validate()?;
2435 let tp = self.ranks.len();
2436 if matrix.out_features % tp != 0 {
2437 return Err(format!(
2438 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2439 matrix.out_features
2440 )
2441 .into());
2442 }
2443 let mut ranks = Vec::with_capacity(tp);
2444 for (rank, engine) in self.ranks.iter().enumerate() {
2445 ranks.push(upload_bf16_rank(
2446 engine,
2447 bf16_column_shard(matrix, tp, rank)?,
2448 f32_mirror,
2449 )?);
2450 }
2451 Ok(ResidentBf16ColumnParallel {
2452 ranks,
2453 out_features: matrix.out_features,
2454 in_features: matrix.in_features,
2455 canonical_chunk_rows,
2456 })
2457 }
2458
2459 pub fn bf16_column_parallel_resident(
2460 &self,
2461 matrix: &ResidentBf16ColumnParallel,
2462 activations: &[f32],
2463 tokens: usize,
2464 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2465 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2466 validate_activations(activations, tokens, matrix.in_features)?;
2467 let local_out = matrix.out_features / self.ranks.len();
2468 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2469 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2470 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2471 let output = run_resident_bf16_rank(
2472 engine,
2473 shard,
2474 activations,
2475 tokens,
2476 matrix.canonical_chunk_rows,
2477 )?;
2478 for token in 0..tokens {
2479 let src = &output[token * local_out..(token + 1) * local_out];
2480 let dst_start = token * matrix.out_features + rank * local_out;
2481 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2482 }
2483 rank_outputs.push(output);
2484 }
2485 Ok(ColumnParallelResult {
2486 gathered,
2487 rank_outputs,
2488 })
2489 }
2490
2491 pub fn bf16_column_parallel_resident_native(
2498 &self,
2499 matrix: &ResidentBf16ColumnParallel,
2500 activations: &[f32],
2501 tokens: usize,
2502 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2503 let rank_outputs =
2504 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2505 let local_out = matrix.out_features / self.ranks.len();
2506 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2507 }
2508
2509 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2514 self.ranks
2515 .first()
2516 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2517 }
2518
2519 pub fn bf16_column_parallel_resident_native_device(
2532 &self,
2533 matrix: &ResidentBf16ColumnParallel,
2534 root_activation: &CudaSlice<f32>,
2535 tokens: usize,
2536 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2537 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2538 matrix,
2539 root_activation,
2540 tokens,
2541 )?;
2542 let local_out = matrix.out_features / self.ranks.len();
2543 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2544 let root = &self.ranks[0];
2545 let _main = root.gpu.enter_main()?;
2546 root.stream().synchronize()?;
2547 Ok(gathered)
2548 }
2549
2550 pub fn bf16_column_parallel_resident_device_shards_from_root(
2555 &self,
2556 matrix: &ResidentBf16ColumnParallel,
2557 root_activation: &CudaSlice<f32>,
2558 tokens: usize,
2559 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2560 if self.ranks.len() > 1 && !self.native_p2p {
2561 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2562 }
2563 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2564 let values = tokens
2565 .checked_mul(matrix.in_features)
2566 .ok_or("device BF16 column activation size overflow")?;
2567 let root = &self.ranks[0];
2568 if tokens == 0
2569 || root_activation.len() < values
2570 || root_activation.ordinal() != root.ctx().ordinal()
2571 {
2572 return Err("device BF16 column root activation geometry mismatch".into());
2573 }
2574
2575 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2576 let root_input = {
2577 let _main = root.gpu.enter_main()?;
2578 let mut root_input = root.uninit(values)?;
2579 root.stream()
2580 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2581 root_input
2582 };
2583 {
2587 let _main = root.gpu.enter_main()?;
2588 root.stream().synchronize()?;
2589 }
2590 rank_inputs.push(root_input);
2591 for engine in &self.ranks[1..] {
2592 let peer_input = {
2593 let _main = engine.gpu.enter_main()?;
2594 let mut peer_input = engine.uninit(values)?;
2595 engine
2596 .stream()
2597 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2598 peer_input
2599 };
2600 rank_inputs.push(peer_input);
2601 }
2602
2603 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2604 for rank in 0..self.ranks.len() {
2605 rank_outputs.push(run_resident_bf16_rank_device(
2606 &self.ranks[rank],
2607 &matrix.ranks[rank],
2608 &rank_inputs[rank],
2609 tokens,
2610 matrix.canonical_chunk_rows,
2611 self.bulk_p2p,
2612 )?);
2613 }
2614 Ok(rank_outputs)
2615 }
2616
2617 pub fn bf16_column_parallel_resident_device_shards(
2624 &self,
2625 matrix: &ResidentBf16ColumnParallel,
2626 activations: &[f32],
2627 tokens: usize,
2628 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2629 if self.ranks.len() > 1 && !self.native_p2p {
2630 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2631 }
2632 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2633 validate_activations(activations, tokens, matrix.in_features)?;
2634
2635 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2636 let root_input = {
2637 let root = &self.ranks[0];
2638 let _main = root.gpu.enter_main()?;
2639 root.htod(activations)?
2640 };
2641 {
2647 let root = &self.ranks[0];
2648 let _main = root.gpu.enter_main()?;
2649 root.stream().synchronize()?;
2650 }
2651 rank_inputs.push(root_input);
2652 for engine in &self.ranks[1..] {
2653 let peer_input = {
2654 let _main = engine.gpu.enter_main()?;
2655 let mut peer_input = engine.uninit(activations.len())?;
2656 engine
2657 .stream()
2658 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2659 peer_input
2660 };
2661 rank_inputs.push(peer_input);
2662 }
2663
2664 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2665 for rank in 0..self.ranks.len() {
2666 rank_outputs.push(run_resident_bf16_rank_device(
2667 &self.ranks[rank],
2668 &matrix.ranks[rank],
2669 &rank_inputs[rank],
2670 tokens,
2671 matrix.canonical_chunk_rows,
2672 self.bulk_p2p,
2673 )?);
2674 }
2675 Ok(rank_outputs)
2676 }
2677
2678 pub fn allocate_replicated_device_rows(
2682 &self,
2683 tokens: usize,
2684 width: usize,
2685 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2686 if self.ranks.len() > 1 && !self.native_p2p {
2687 return Err("replicated device rows require native P2P ranks".into());
2688 }
2689 let values = tokens
2690 .checked_mul(width)
2691 .ok_or("replicated device row size overflow")?;
2692 let rank_lengths = vec![values; self.ranks.len()];
2693 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2694 let mut ranks = Vec::with_capacity(self.ranks.len());
2695 for engine in &self.ranks {
2696 let _main = engine.gpu.enter_main()?;
2697 ranks.push(engine.uninit(values)?);
2698 }
2699 Ok(ResidentReplicatedDeviceRows {
2700 ranks,
2701 tokens,
2702 width,
2703 })
2704 }
2705
2706 pub fn refresh_replicated_device_rows_from_root(
2708 &self,
2709 rows: &mut ResidentReplicatedDeviceRows,
2710 source: &CudaSlice<f32>,
2711 ) -> Result<(), Box<dyn std::error::Error>> {
2712 if self.ranks.len() > 1 && !self.native_p2p {
2713 return Err("replicated device rows require native P2P ranks".into());
2714 }
2715 validate_replicated_device_rows(&self.ranks, rows)?;
2716 let root = self
2717 .ranks
2718 .first()
2719 .ok_or("replicated rows have no root rank")?;
2720 let values = replicated_device_row_source_values(
2721 rows.tokens,
2722 rows.width,
2723 source.len(),
2724 source.ordinal(),
2725 root.ctx().ordinal(),
2726 )?;
2727 let (root_rows, peer_rows) = rows
2728 .ranks
2729 .split_first_mut()
2730 .ok_or("replicated rows have no root allocation")?;
2731 {
2732 let _main = root.gpu.enter_main()?;
2733 let mut destination = root_rows.slice_mut(0..values);
2734 root.stream()
2735 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2736 root.stream().synchronize()?;
2737 }
2738 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2739 let _main = engine.gpu.enter_main()?;
2740 let mut destination = peer_rows.slice_mut(0..values);
2741 engine
2742 .stream()
2743 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2744 }
2745 Ok(())
2746 }
2747
2748 pub fn upload_replicated_device_rows(
2750 &self,
2751 rows: &[f32],
2752 tokens: usize,
2753 width: usize,
2754 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2755 if self.ranks.len() > 1 && !self.native_p2p {
2756 return Err("replicated device rows require native P2P ranks".into());
2757 }
2758 validate_activations(rows, tokens, width)?;
2759 let root = self
2760 .ranks
2761 .first()
2762 .ok_or("replicated rows have no root rank")?;
2763 let root_rows = {
2764 let _main = root.gpu.enter_main()?;
2765 root.htod(rows)?
2766 };
2767 {
2768 let _main = root.gpu.enter_main()?;
2769 root.stream().synchronize()?;
2770 }
2771 let mut ranks = Vec::with_capacity(self.ranks.len());
2772 ranks.push(root_rows);
2773 for engine in self.ranks.iter().skip(1) {
2774 let _main = engine.gpu.enter_main()?;
2775 let mut peer_rows = engine.uninit(rows.len())?;
2776 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2777 ranks.push(peer_rows);
2778 }
2779 Ok(ResidentReplicatedDeviceRows {
2780 ranks,
2781 tokens,
2782 width,
2783 })
2784 }
2785
2786 pub fn bf16_column_parallel_resident_replicated_device_shards(
2788 &self,
2789 matrix: &ResidentBf16ColumnParallel,
2790 activations: &ResidentReplicatedDeviceRows,
2791 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2792 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2793 validate_replicated_device_rows(&self.ranks, activations)?;
2794 if activations.width != matrix.in_features {
2795 return Err(format!(
2796 "replicated BF16 column input width {} != matrix width {}",
2797 activations.width, matrix.in_features
2798 )
2799 .into());
2800 }
2801 let mut outputs = Vec::with_capacity(self.ranks.len());
2802 for rank in 0..self.ranks.len() {
2803 outputs.push(run_resident_bf16_rank_device(
2804 &self.ranks[rank],
2805 &matrix.ranks[rank],
2806 &activations.ranks[rank],
2807 activations.tokens,
2808 matrix.canonical_chunk_rows,
2809 self.bulk_p2p,
2810 )?);
2811 }
2812 Ok(outputs)
2813 }
2814
2815 #[allow(clippy::too_many_arguments)]
2817 pub fn upload_sigmoid_topk_router(
2818 &self,
2819 weight: Bf16Matrix<'_>,
2820 correction_bias: &[f32],
2821 active: Option<&[bool]>,
2822 experts_per_token: usize,
2823 scaling_factor: f32,
2824 route_norm: bool,
2825 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2826 weight.validate()?;
2827 if correction_bias.len() != weight.out_features
2828 || experts_per_token == 0
2829 || experts_per_token > weight.out_features
2830 || !correction_bias.iter().all(|value| value.is_finite())
2831 || !scaling_factor.is_finite()
2832 || scaling_factor <= 0.0
2833 {
2834 return Err(format!(
2835 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2836 weight.out_features,
2837 weight.in_features,
2838 correction_bias.len(),
2839 experts_per_token,
2840 )
2841 .into());
2842 }
2843 let active_row = active
2844 .map(|mask| {
2845 if mask.len() != weight.out_features {
2846 return Err(format!(
2847 "sigmoid router active mask {} != experts {}",
2848 mask.len(),
2849 weight.out_features
2850 ));
2851 }
2852 Ok(mask
2853 .iter()
2854 .map(|&enabled| u8::from(enabled))
2855 .collect::<Vec<_>>())
2856 })
2857 .transpose()?
2858 .unwrap_or_else(|| vec![1; weight.out_features]);
2859 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2860 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2861
2862 let root = self
2863 .ranks
2864 .first()
2865 .ok_or("sigmoid router runtime has no root rank")?;
2866 let _main = root.gpu.enter_main()?;
2867 let bf16 = root.htod_bytes(weight.bytes)?;
2868 let weight_f32 = root.bf16_to_f32(
2869 &bf16.slice(0..bf16.len()),
2870 weight.out_features * weight.in_features,
2871 )?;
2872 Ok(ResidentSigmoidTopKRouter {
2873 weight: weight_f32,
2874 correction_bias: root.htod(correction_bias)?,
2875 active: root.htod_bytes(&active_row)?,
2876 root_device: root.ctx().ordinal(),
2877 input_width: weight.in_features,
2878 expert_count: weight.out_features,
2879 experts_per_token,
2880 active_count,
2881 scaling_factor,
2882 route_norm,
2883 })
2884 }
2885
2886 pub fn sigmoid_topk_replicated_device_rows_host(
2891 &self,
2892 router: &ResidentSigmoidTopKRouter,
2893 input: &ResidentReplicatedDeviceRows,
2894 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2895 validate_replicated_device_rows(&self.ranks, input)?;
2896 if input.width != router.input_width {
2897 return Err(format!(
2898 "sigmoid router input width {} != resident width {}",
2899 input.width, router.input_width
2900 )
2901 .into());
2902 }
2903 let root = self
2904 .ranks
2905 .first()
2906 .ok_or("sigmoid router runtime has no root rank")?;
2907 let _main = root.gpu.enter_main()?;
2908 if root.ctx().ordinal() != router.root_device
2909 || router.weight.ordinal() != router.root_device
2910 || router.correction_bias.ordinal() != router.root_device
2911 || router.active.ordinal() != router.root_device
2912 {
2913 return Err("sigmoid router root residency changed".into());
2914 }
2915 let logits = root.router_gemv(
2916 &router.weight,
2917 &input.ranks[0],
2918 router.input_width,
2919 router.expert_count,
2920 input.tokens,
2921 )?;
2922 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2923 &logits,
2924 input.tokens,
2925 router.expert_count,
2926 router.experts_per_token,
2927 router.active_count,
2928 &router.correction_bias,
2929 &router.active,
2930 router.scaling_factor,
2931 router.route_norm,
2932 )?;
2933 Ok(SigmoidTopKHostOutput {
2934 logits: root.dtoh(&logits)?,
2935 selected,
2936 weights,
2937 })
2938 }
2939
2940 pub fn upload_replicated_bf16_swiglu(
2942 &self,
2943 gate: Bf16Matrix<'_>,
2944 up: Bf16Matrix<'_>,
2945 down: Bf16Matrix<'_>,
2946 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2947 gate.validate()?;
2948 up.validate()?;
2949 down.validate()?;
2950 if gate.in_features != up.in_features
2951 || gate.out_features != up.out_features
2952 || down.in_features != gate.out_features
2953 || down.out_features != gate.in_features
2954 {
2955 return Err(format!(
2956 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2957 gate.out_features,
2958 gate.in_features,
2959 up.out_features,
2960 up.in_features,
2961 down.out_features,
2962 down.in_features,
2963 )
2964 .into());
2965 }
2966 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2967 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2968 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2969 for engine in &self.ranks {
2970 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2971 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2972 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2973 }
2974 Ok(ResidentReplicatedBf16SwiGlu {
2975 gate: gate_ranks,
2976 up: up_ranks,
2977 down: down_ranks,
2978 input_width: gate.in_features,
2979 intermediate_width: gate.out_features,
2980 })
2981 }
2982
2983 pub fn replicated_bf16_swiglu_resident_device(
2985 &self,
2986 mlp: &ResidentReplicatedBf16SwiGlu,
2987 input: &ResidentReplicatedDeviceRows,
2988 activation_limit: Option<f32>,
2989 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2990 validate_step_expert_activation_limit(activation_limit)?;
2991 validate_replicated_device_rows(&self.ranks, input)?;
2992 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2993 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2994 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2995 if input.width != mlp.input_width
2996 || mlp.gate.len() != self.ranks.len()
2997 || mlp.up.len() != self.ranks.len()
2998 || mlp.down.len() != self.ranks.len()
2999 {
3000 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3001 }
3002
3003 let mut outputs = Vec::with_capacity(self.ranks.len());
3004 for rank in 0..self.ranks.len() {
3005 let engine = &self.ranks[rank];
3006 let gate = run_resident_bf16_rank_device(
3007 engine,
3008 &mlp.gate[rank],
3009 &input.ranks[rank],
3010 input.tokens,
3011 None,
3012 self.bulk_p2p,
3013 )?;
3014 let up = run_resident_bf16_rank_device(
3015 engine,
3016 &mlp.up[rank],
3017 &input.ranks[rank],
3018 input.tokens,
3019 None,
3020 self.bulk_p2p,
3021 )?;
3022 let _main = engine.gpu.enter_main()?;
3023 let values = input
3024 .tokens
3025 .checked_mul(mlp.intermediate_width)
3026 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3027 let mut activation = engine.uninit(values)?;
3028 if let Some(limit) = activation_limit {
3029 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3030 } else {
3031 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3032 }
3033 outputs.push(run_resident_bf16_rank_device(
3034 engine,
3035 &mlp.down[rank],
3036 &activation,
3037 input.tokens,
3038 None,
3039 self.bulk_p2p,
3040 )?);
3041 }
3042 Ok(ResidentReplicatedDeviceRows {
3043 ranks: outputs,
3044 tokens: input.tokens,
3045 width: mlp.input_width,
3046 })
3047 }
3048
3049 pub fn rms_norm_replicated_device_rows(
3051 &self,
3052 input: &ResidentReplicatedDeviceRows,
3053 weight: &[f32],
3054 eps: f32,
3055 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3056 validate_replicated_device_rows(&self.ranks, input)?;
3057 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3058 return Err(format!(
3059 "replicated RMS norm weight/eps {}/{} != width {}",
3060 weight.len(),
3061 eps,
3062 input.width
3063 )
3064 .into());
3065 }
3066 let mut ranks = Vec::with_capacity(self.ranks.len());
3067 for (rank, engine) in self.ranks.iter().enumerate() {
3068 let _main = engine.gpu.enter_main()?;
3069 let weight = engine.htod(weight)?;
3070 let mut output = engine.uninit(input.tokens * input.width)?;
3071 engine.rms_norm(
3072 &input.ranks[rank],
3073 &weight,
3074 &mut output,
3075 input.width,
3076 input.tokens,
3077 eps,
3078 )?;
3079 ranks.push(output);
3080 }
3081 Ok(ResidentReplicatedDeviceRows {
3082 ranks,
3083 tokens: input.tokens,
3084 width: input.width,
3085 })
3086 }
3087
3088 pub fn add_rms_norm_replicated_device_rows(
3090 &self,
3091 input: &ResidentReplicatedDeviceRows,
3092 update: &ResidentReplicatedDeviceRows,
3093 weight: &[f32],
3094 eps: f32,
3095 ) -> Result<
3096 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3097 Box<dyn std::error::Error>,
3098 > {
3099 validate_replicated_device_rows(&self.ranks, input)?;
3100 validate_replicated_device_rows(&self.ranks, update)?;
3101 if input.tokens != update.tokens
3102 || input.width != update.width
3103 || weight.len() != input.width
3104 || !eps.is_finite()
3105 || eps <= 0.0
3106 {
3107 return Err(format!(
3108 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3109 input.tokens,
3110 input.width,
3111 update.tokens,
3112 update.width,
3113 weight.len(),
3114 )
3115 .into());
3116 }
3117 let values = input.tokens * input.width;
3118 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3119 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3120 for (rank, engine) in self.ranks.iter().enumerate() {
3121 let _main = engine.gpu.enter_main()?;
3122 let weight = engine.htod(weight)?;
3123 let mut residual = engine.uninit(values)?;
3124 let mut normalized = engine.uninit(values)?;
3125 engine.add_rms_norm(
3126 &input.ranks[rank],
3127 &update.ranks[rank],
3128 &weight,
3129 &mut residual,
3130 &mut normalized,
3131 input.width,
3132 input.tokens,
3133 eps,
3134 )?;
3135 residual_ranks.push(residual);
3136 normalized_ranks.push(normalized);
3137 }
3138 Ok((
3139 ResidentReplicatedDeviceRows {
3140 ranks: residual_ranks,
3141 tokens: input.tokens,
3142 width: input.width,
3143 },
3144 ResidentReplicatedDeviceRows {
3145 ranks: normalized_ranks,
3146 tokens: input.tokens,
3147 width: input.width,
3148 },
3149 ))
3150 }
3151
3152 pub fn collect_replicated_device_rows(
3153 &self,
3154 rows: &ResidentReplicatedDeviceRows,
3155 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3156 validate_replicated_device_rows(&self.ranks, rows)?;
3157 let mut outputs = Vec::with_capacity(self.ranks.len());
3158 for (rank, engine) in self.ranks.iter().enumerate() {
3159 let _main = engine.gpu.enter_main()?;
3160 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3161 }
3162 Ok(outputs)
3163 }
3164
3165 pub fn upload_bf16_row_parallel(
3166 &self,
3167 matrix: Bf16Matrix<'_>,
3168 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3169 matrix.validate()?;
3170 let tp = self.ranks.len();
3171 if matrix.in_features % tp != 0 {
3172 return Err(format!(
3173 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3174 matrix.in_features
3175 )
3176 .into());
3177 }
3178 let mut ranks = Vec::with_capacity(tp);
3179 for (rank, engine) in self.ranks.iter().enumerate() {
3180 let shard = bf16_row_shard(matrix, tp, rank)?;
3181 ranks.push(upload_bf16_rank(
3182 engine,
3183 Bf16Matrix {
3184 bytes: &shard,
3185 out_features: matrix.out_features,
3186 in_features: matrix.in_features / tp,
3187 },
3188 false,
3189 )?);
3190 }
3191 Ok(ResidentBf16RowParallel {
3192 ranks,
3193 out_features: matrix.out_features,
3194 in_features: matrix.in_features,
3195 })
3196 }
3197
3198 pub fn bf16_row_parallel_resident(
3199 &self,
3200 matrix: &ResidentBf16RowParallel,
3201 activations: &[f32],
3202 tokens: usize,
3203 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3204 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3205 validate_activations(activations, tokens, matrix.in_features)?;
3206 let tp = self.ranks.len();
3207 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3208 let mut rank_partials = Vec::with_capacity(tp);
3209 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3210 let local_activations =
3211 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3212 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3213 for (sum, value) in reduced.iter_mut().zip(&partial) {
3214 *sum += value;
3215 }
3216 rank_partials.push(partial);
3217 }
3218 Ok(RowParallelResult {
3219 reduced,
3220 rank_partials,
3221 })
3222 }
3223
3224 pub fn upload_step_bf16_row_parallel(
3226 &self,
3227 matrix: Bf16Matrix<'_>,
3228 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3229 self.upload_step_bf16_row_parallel_inner(matrix, false)
3230 }
3231
3232 pub fn upload_step_bf16_row_parallel_f32_mirror(
3233 &self,
3234 matrix: Bf16Matrix<'_>,
3235 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3236 self.upload_step_bf16_row_parallel_inner(matrix, true)
3237 }
3238
3239 fn upload_step_bf16_row_parallel_inner(
3240 &self,
3241 matrix: Bf16Matrix<'_>,
3242 f32_mirror: bool,
3243 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3244 matrix.validate()?;
3245 let tp = self.ranks.len();
3246 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3247 let local_in = matrix.in_features / tp;
3248 let blocks_per_rank = local_in / canonical_chunk_cols;
3249 let mut ranks = Vec::with_capacity(tp);
3250 for (rank, engine) in self.ranks.iter().enumerate() {
3251 let mut blocks = Vec::with_capacity(blocks_per_rank);
3252 for block in 0..blocks_per_rank {
3253 let global_block = rank * blocks_per_rank + block;
3254 let col_start = global_block * canonical_chunk_cols;
3255 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3256 blocks.push(upload_bf16_rank(
3257 engine,
3258 Bf16Matrix {
3259 bytes: &bytes,
3260 out_features: matrix.out_features,
3261 in_features: canonical_chunk_cols,
3262 },
3263 f32_mirror,
3264 )?);
3265 }
3266 ranks.push(blocks);
3267 }
3268 Ok(ResidentStepBf16RowParallel {
3269 ranks,
3270 out_features: matrix.out_features,
3271 in_features: matrix.in_features,
3272 canonical_chunk_cols,
3273 })
3274 }
3275
3276 pub fn step_bf16_row_parallel_resident(
3281 &self,
3282 matrix: &ResidentStepBf16RowParallel,
3283 activations: &[f32],
3284 tokens: usize,
3285 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3286 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3287 validate_activations(activations, tokens, matrix.in_features)?;
3288 let root = &self.ranks[0];
3289 let output_len = tokens
3290 .checked_mul(matrix.out_features)
3291 .ok_or("Step BF16 row output size overflow")?;
3292 let mut reduced = {
3293 let _main = root.gpu.enter_main()?;
3294 root.htod(&vec![0.0f32; output_len])?
3295 };
3296 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3297 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3298 for (block, resident) in blocks.iter().enumerate() {
3299 let global_block = rank * blocks_per_rank + block;
3300 let input = activation_shard(
3301 activations,
3302 tokens,
3303 matrix.in_features,
3304 PRODUCT_MAX_CARDS,
3305 global_block,
3306 );
3307 let partial =
3308 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3309 let next = {
3310 let _main = root.gpu.enter_main()?;
3311 let partial = root.htod(&partial)?;
3312 let mut next = root.uninit(output_len)?;
3313 root.add(&reduced, &partial, &mut next, output_len)?;
3314 next
3315 };
3316 reduced = next;
3317 }
3318 }
3319 let _main = root.gpu.enter_main()?;
3320 root.dtoh(&reduced)
3321 }
3322
3323 pub fn step_bf16_row_parallel_resident_native(
3329 &self,
3330 matrix: &ResidentStepBf16RowParallel,
3331 activations: &[f32],
3332 tokens: usize,
3333 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3334 if self.ranks.len() > 1 && !self.native_p2p {
3335 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3336 }
3337 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3338 validate_activations(activations, tokens, matrix.in_features)?;
3339 let root = &self.ranks[0];
3340 let root_input = {
3341 let _main = root.gpu.enter_main()?;
3342 root.htod(activations)?
3343 };
3344 {
3347 let _main = root.gpu.enter_main()?;
3348 root.stream().synchronize()?;
3349 }
3350 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3351 let _main = root.gpu.enter_main()?;
3352 root.dtoh(&reduced)
3353 }
3354
3355 pub fn step_bf16_row_parallel_resident_native_device(
3363 &self,
3364 matrix: &ResidentStepBf16RowParallel,
3365 root_activation: &CudaSlice<f32>,
3366 tokens: usize,
3367 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3368 if self.ranks.len() > 1 && !self.native_p2p {
3369 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3370 }
3371 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3372 let values = tokens
3373 .checked_mul(matrix.in_features)
3374 .ok_or("device Step BF16 row activation size overflow")?;
3375 let root = &self.ranks[0];
3376 if tokens == 0
3377 || root_activation.len() < values
3378 || root_activation.ordinal() != root.ctx().ordinal()
3379 {
3380 return Err("device Step BF16 row root activation geometry mismatch".into());
3381 }
3382 let root_input = {
3383 let _main = root.gpu.enter_main()?;
3384 let mut root_input = root.uninit(values)?;
3385 root.stream()
3386 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3387 root.stream().synchronize()?; root_input
3389 };
3390 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3391 let _main = root.gpu.enter_main()?;
3392 root.stream().synchronize()?;
3393 Ok(reduced)
3394 }
3395
3396 fn step_bf16_row_native_reduce_from_root(
3401 &self,
3402 matrix: &ResidentStepBf16RowParallel,
3403 root_input: &CudaSlice<f32>,
3404 tokens: usize,
3405 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3406 let root = &self.ranks[0];
3407 let output_len = tokens
3408 .checked_mul(matrix.out_features)
3409 .ok_or("native Step BF16 row output size overflow")?;
3410 let mut reduced = {
3411 let _main = root.gpu.enter_main()?;
3412 root.htod(&vec![0.0f32; output_len])?
3413 };
3414 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3415 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3416 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3417 let mut remote_partial_keepalive = Vec::new();
3418 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3419 for (block, resident) in blocks.iter().enumerate() {
3420 let global_block = rank * blocks_per_rank + block;
3421 let col_start = global_block * matrix.canonical_chunk_cols;
3422 let block_len = tokens
3423 .checked_mul(matrix.canonical_chunk_cols)
3424 .ok_or("native Step BF16 row block size overflow")?;
3425 let block_input = if self.bulk_p2p {
3426 let root_packed = {
3427 let _main = root.gpu.enter_main()?;
3428 let mut root_packed = root.uninit(block_len)?;
3429 root.copy_rows_strided(
3430 &root_input,
3431 &mut root_packed,
3432 matrix.canonical_chunk_cols,
3433 tokens,
3434 matrix.in_features,
3435 col_start,
3436 )?;
3437 root_packed
3438 };
3439 if rank == 0 {
3440 root_packed
3441 } else {
3442 {
3445 let _main = root.gpu.enter_main()?;
3446 root.stream().synchronize()?;
3447 }
3448 let engine = &self.ranks[rank];
3449 let _main = engine.gpu.enter_main()?;
3450 let mut block_input = engine.uninit(block_len)?;
3451 engine
3452 .stream()
3453 .memcpy_dtod(&root_packed, &mut block_input)?;
3454 root_packed_keepalive.push(root_packed);
3455 block_input
3456 }
3457 } else {
3458 let engine = &self.ranks[rank];
3459 let _main = engine.gpu.enter_main()?;
3460 let mut block_input = engine.uninit(block_len)?;
3461 for token in 0..tokens {
3462 let source_start = token * matrix.in_features + col_start;
3463 let source = root_input
3464 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3465 let destination_start = token * matrix.canonical_chunk_cols;
3466 let mut destination = block_input.slice_mut(
3467 destination_start..destination_start + matrix.canonical_chunk_cols,
3468 );
3469 engine.stream().memcpy_dtod(&source, &mut destination)?;
3470 }
3471 block_input
3472 };
3473 let partial = run_resident_bf16_rank_device(
3474 &self.ranks[rank],
3475 resident,
3476 &block_input,
3477 tokens,
3478 None,
3479 self.bulk_p2p,
3480 )?;
3481 block_input_keepalive.push(block_input);
3482 let root_partial = if rank == 0 {
3483 partial
3484 } else {
3485 {
3488 let engine = &self.ranks[rank];
3489 let _main = engine.gpu.enter_main()?;
3490 engine.stream().synchronize()?;
3491 }
3492 let _main = root.gpu.enter_main()?;
3493 let mut peer_partial = root.uninit(output_len)?;
3494 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3495 remote_partial_keepalive.push(partial);
3496 peer_partial
3497 };
3498 let next = {
3499 let _main = root.gpu.enter_main()?;
3500 let mut next = root.uninit(output_len)?;
3501 root.add(&reduced, &root_partial, &mut next, output_len)?;
3502 next
3503 };
3504 reduced = next;
3505 }
3506 }
3507 {
3508 let _main = root.gpu.enter_main()?;
3509 root.stream().synchronize()?;
3510 }
3511 drop(remote_partial_keepalive);
3512 drop(root_packed_keepalive);
3513 drop(block_input_keepalive);
3514 Ok(reduced)
3515 }
3516
3517 pub fn step_bf16_row_parallel_resident_root_device(
3520 &self,
3521 matrix: &ResidentStepBf16RowParallel,
3522 rank_activations: &[CudaSlice<f32>],
3523 tokens: usize,
3524 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3525 if self.ranks.len() > 1 && !self.native_p2p {
3526 return Err(
3527 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3528 );
3529 }
3530 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3531 let local_width = matrix.in_features / self.ranks.len();
3532 let shard_len = tokens
3533 .checked_mul(local_width)
3534 .ok_or("device Step BF16 row shard size overflow")?;
3535 if tokens == 0
3536 || rank_activations.len() != self.ranks.len()
3537 || rank_activations
3538 .iter()
3539 .zip(&self.ranks)
3540 .any(|(rows, engine)| {
3541 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3542 })
3543 {
3544 return Err("device Step BF16 row activation shard geometry changed".into());
3545 }
3546
3547 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3548 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3549 let mut partials = Vec::with_capacity(self.ranks.len());
3550 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3551 if blocks.len() != blocks_per_rank {
3552 return Err(format!(
3553 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3554 blocks.len()
3555 )
3556 .into());
3557 }
3558 let engine = &self.ranks[rank];
3559 let _main = engine.gpu.enter_main()?;
3560 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3561 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3562 for (block, resident) in blocks.iter().enumerate() {
3563 let block_len = tokens
3564 .checked_mul(matrix.canonical_chunk_cols)
3565 .ok_or("device Step BF16 row block size overflow")?;
3566 let mut block_input = engine.uninit(block_len)?;
3567 let local_col_start = block * matrix.canonical_chunk_cols;
3568 if self.bulk_p2p {
3569 engine.copy_rows_strided(
3570 &rank_activations[rank],
3571 &mut block_input,
3572 matrix.canonical_chunk_cols,
3573 tokens,
3574 local_width,
3575 local_col_start,
3576 )?;
3577 } else {
3578 for token in 0..tokens {
3579 let source_start = token * local_width + local_col_start;
3580 let source = rank_activations[rank]
3581 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3582 let destination_start = token * matrix.canonical_chunk_cols;
3583 let mut destination = block_input.slice_mut(
3584 destination_start..destination_start + matrix.canonical_chunk_cols,
3585 );
3586 engine.stream().memcpy_dtod(&source, &mut destination)?;
3587 }
3588 }
3589 let partial = run_resident_bf16_rank_device(
3590 engine,
3591 resident,
3592 &block_input,
3593 tokens,
3594 None,
3595 self.bulk_p2p,
3596 )?;
3597 rank_inputs.push(block_input);
3598 rank_partials.push(partial);
3599 }
3600 block_inputs.push(rank_inputs);
3601 partials.push(rank_partials);
3602 }
3603 for engine in self.ranks.iter().skip(1) {
3604 let _main = engine.gpu.enter_main()?;
3605 engine.stream().synchronize()?;
3606 }
3607
3608 let output_len = tokens
3609 .checked_mul(matrix.out_features)
3610 .ok_or("device Step BF16 row output size overflow")?;
3611 let root = &self.ranks[0];
3612 let _main = root.gpu.enter_main()?;
3613 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3614 let mut remote_partials = Vec::new();
3615 for (rank, rank_partials) in partials.into_iter().enumerate() {
3616 for partial in rank_partials {
3617 let root_partial = if rank == 0 {
3618 partial
3619 } else {
3620 let mut peer_partial = root.uninit(output_len)?;
3621 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3622 remote_partials.push(partial);
3623 peer_partial
3624 };
3625 let mut next = root.uninit(output_len)?;
3626 root.add(&reduced, &root_partial, &mut next, output_len)?;
3627 reduced = next;
3628 }
3629 }
3630 root.stream().synchronize()?;
3631 drop(remote_partials);
3632 drop(block_inputs);
3633 Ok(reduced)
3634 }
3635
3636 pub fn step_bf16_row_parallel_resident_replicated_device(
3638 &self,
3639 matrix: &ResidentStepBf16RowParallel,
3640 rank_activations: &[CudaSlice<f32>],
3641 tokens: usize,
3642 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3643 let reduced =
3644 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3645 let output_len = tokens
3646 .checked_mul(matrix.out_features)
3647 .ok_or("device Step BF16 row output size overflow")?;
3648 let mut ranks = Vec::with_capacity(self.ranks.len());
3649 ranks.push(reduced);
3650 for engine in self.ranks.iter().skip(1) {
3651 let _main = engine.gpu.enter_main()?;
3652 let mut peer_output = engine.uninit(output_len)?;
3653 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3654 ranks.push(peer_output);
3655 }
3656 Ok(ResidentReplicatedDeviceRows {
3657 ranks,
3658 tokens,
3659 width: matrix.out_features,
3660 })
3661 }
3662
3663 pub fn upload_expert(
3664 &self,
3665 gate: E4m3BlockMatrix<'_>,
3666 up: E4m3BlockMatrix<'_>,
3667 down: E4m3BlockMatrix<'_>,
3668 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3669 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3670 return Err("TP expert gate/up dimensions differ".into());
3671 }
3672 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3673 return Err(format!(
3674 "TP expert down {}x{} does not invert gate/up {}x{}",
3675 down.out_features, down.in_features, gate.out_features, gate.in_features
3676 )
3677 .into());
3678 }
3679 Ok(ResidentTpExpert {
3680 gate: self.upload_column_parallel(gate)?,
3681 up: self.upload_column_parallel(up)?,
3682 down: self.upload_row_parallel(down)?,
3683 input_width: gate.in_features,
3684 expert_width: gate.out_features,
3685 })
3686 }
3687
3688 pub fn run_expert(
3689 &self,
3690 expert: &ResidentTpExpert,
3691 input: &[f32],
3692 tokens: usize,
3693 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3694 validate_activations(input, tokens, expert.input_width)?;
3695 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3696 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3697 let activated: Vec<f32> = gate
3698 .gathered
3699 .iter()
3700 .zip(&up.gathered)
3701 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3702 .collect();
3703 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3704 Ok(self
3705 .row_parallel_resident(&expert.down, &activated, tokens)?
3706 .reduced)
3707 }
3708
3709 pub fn upload_expert_parallel(
3710 &self,
3711 gate: E4m3ExpertBank<'_>,
3712 up: E4m3ExpertBank<'_>,
3713 down: E4m3ExpertBank<'_>,
3714 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3715 gate.validate()?;
3716 up.validate()?;
3717 down.validate()?;
3718 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3719 return Err("EP gate/up/down expert counts differ".into());
3720 }
3721 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3722 return Err("EP gate/up dimensions differ".into());
3723 }
3724 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3725 return Err(format!(
3726 "EP down {}x{} does not invert gate/up {}x{}",
3727 down.out_features, down.in_features, gate.out_features, gate.in_features
3728 )
3729 .into());
3730 }
3731 if gate.expert_count % self.ranks.len() != 0 {
3732 return Err(format!(
3733 "EP expert count {} is not divisible by {} ranks",
3734 gate.expert_count,
3735 self.ranks.len()
3736 )
3737 .into());
3738 }
3739
3740 let per_rank = gate.expert_count / self.ranks.len();
3741 let mut ranks = Vec::with_capacity(self.ranks.len());
3742 for (rank, engine) in self.ranks.iter().enumerate() {
3743 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3744 ranks.push(ResidentEpRank {
3745 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3746 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3747 down: upload_expert_bank_rank(engine, down, expert_range)?,
3748 });
3749 }
3750 Ok(ResidentExpertParallel {
3751 ranks,
3752 expert_count: gate.expert_count,
3753 input_width: gate.in_features,
3754 expert_width: gate.out_features,
3755 })
3756 }
3757
3758 #[allow(clippy::too_many_arguments)]
3764 pub fn prepare_step_grouped_fp8_gate(
3765 &self,
3766 gate: E4m3ExpertBank<'_>,
3767 up: E4m3ExpertBank<'_>,
3768 down: E4m3ExpertBank<'_>,
3769 input: &[f32],
3770 tokens: usize,
3771 selected: &[usize],
3772 activation_limit: Option<f32>,
3773 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3774 gate.validate()?;
3775 up.validate()?;
3776 down.validate()?;
3777 validate_step_expert_activation_limit(activation_limit)?;
3778 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3779 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3780 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3781 {
3782 return Err(format!(
3783 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3784 got gate/up/down={}/{}/{}",
3785 gate.expert_count, up.expert_count, down.expert_count,
3786 )
3787 .into());
3788 }
3789 if gate.in_features != up.in_features
3790 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3791 || up.out_features != STEP_GROUPED_FP8_WIDTH
3792 || down.in_features != STEP_GROUPED_FP8_WIDTH
3793 || down.out_features != gate.in_features
3794 {
3795 return Err(format!(
3796 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3797 gate.out_features,
3798 gate.in_features,
3799 up.out_features,
3800 up.in_features,
3801 down.out_features,
3802 down.in_features,
3803 )
3804 .into());
3805 }
3806 validate_activations(input, tokens, gate.in_features)?;
3807 let pairs = tokens
3808 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3809 .ok_or("official Step grouped FP8 route count overflow")?;
3810 if selected.len() != pairs {
3811 return Err(format!(
3812 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3813 ({pairs})",
3814 selected.len()
3815 )
3816 .into());
3817 }
3818 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3819 let mut unique = routes.to_vec();
3820 unique.sort_unstable();
3821 unique.dedup();
3822 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3823 return Err(format!(
3824 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3825 {routes:?}"
3826 )
3827 .into());
3828 }
3829 }
3830
3831 let engine = self
3832 .ranks
3833 .first()
3834 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3835 let _main = engine.gpu.enter_main()?;
3836 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3837 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3838 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3839 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3840 let input = engine.htod(input)?;
3841 let route_csr = ExpertCsr::from_token_routes(
3842 STEP_GROUPED_FP8_EXPERTS,
3843 tokens,
3844 STEP_GROUPED_FP8_TOP_K,
3845 selected,
3846 )?
3847 .upload(engine)?;
3848 let pair_rows = (0..pairs).collect::<Vec<_>>();
3849 let down_csr =
3850 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3851 .upload(engine)?;
3852 let gate_workspace =
3853 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3854 let up_workspace =
3855 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3856 let down_workspace =
3857 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3858 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3859 Ok(PreparedStepGroupedFp8Gate {
3860 device: engine.ctx().ordinal(),
3861 gate,
3862 up,
3863 down,
3864 input,
3865 route_csr,
3866 down_csr,
3867 gate_workspace,
3868 up_workspace,
3869 down_workspace,
3870 activation,
3871 activation_limit,
3872 tokens,
3873 pairs,
3874 })
3875 }
3876
3877 pub fn run_step_grouped_fp8_gate(
3879 &self,
3880 plan: &mut PreparedStepGroupedFp8Gate,
3881 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3882 let engine = self
3883 .ranks
3884 .first()
3885 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3886 if engine.ctx().ordinal() != plan.device {
3887 return Err(format!(
3888 "official Step grouped FP8 plan device {} != rank-zero device {}",
3889 plan.device,
3890 engine.ctx().ordinal()
3891 )
3892 .into());
3893 }
3894 let _main = engine.gpu.enter_main()?;
3895
3896 plan.gate_workspace.quantize(engine, &plan.input)?;
3897 plan.gate_workspace.project(
3898 engine,
3899 &plan.gate.codes,
3900 &plan.gate.scales,
3901 &plan.route_csr,
3902 plan.gate.code_stride,
3903 plan.gate.scale_stride,
3904 1.0,
3905 )?;
3906 plan.up_workspace.quantize(engine, &plan.input)?;
3907 plan.up_workspace.project(
3908 engine,
3909 &plan.up.codes,
3910 &plan.up.scales,
3911 &plan.route_csr,
3912 plan.up.code_stride,
3913 plan.up.scale_stride,
3914 1.0,
3915 )?;
3916 if let Some(limit) = plan.activation_limit {
3917 engine.silu_clamped_mul_host_expf(
3918 plan.gate_workspace.output(),
3919 plan.up_workspace.output(),
3920 limit,
3921 &mut plan.activation,
3922 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3923 )?;
3924 } else {
3925 engine.silu_mul_host_expf(
3926 plan.gate_workspace.output(),
3927 plan.up_workspace.output(),
3928 &mut plan.activation,
3929 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3930 )?;
3931 }
3932 plan.down_workspace.quantize(engine, &plan.activation)?;
3933 plan.down_workspace.project(
3934 engine,
3935 &plan.down.codes,
3936 &plan.down.scales,
3937 &plan.down_csr,
3938 plan.down.code_stride,
3939 plan.down.scale_stride,
3940 1.0,
3941 )?;
3942
3943 Ok(StepGroupedFp8ProjectionOutput {
3944 gate: engine.dtoh(plan.gate_workspace.output())?,
3945 up: engine.dtoh(plan.up_workspace.output())?,
3946 down: engine.dtoh(plan.down_workspace.output())?,
3947 })
3948 }
3949
3950 pub fn prepare_step_grouped_expert_parallel_gate(
3951 &self,
3952 experts: &ResidentExpertParallel,
3953 input: &[f32],
3954 tokens: usize,
3955 selected: &[usize],
3956 activation_limit: Option<f32>,
3957 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3958 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3959 experts,
3960 input,
3961 tokens,
3962 selected,
3963 activation_limit,
3964 tokens,
3965 )
3966 }
3967
3968 #[allow(clippy::too_many_arguments)]
3969 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3970 &self,
3971 experts: &ResidentExpertParallel,
3972 input: &[f32],
3973 tokens: usize,
3974 selected: &[usize],
3975 activation_limit: Option<f32>,
3976 max_tokens: usize,
3977 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3978 if !self.native_p2p || !self.ep_device_arithmetic {
3979 return Err(
3980 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3981 );
3982 }
3983 validate_step_expert_activation_limit(activation_limit)?;
3984 validate_ep_residency(&self.ranks, experts)?;
3985 validate_activations(input, tokens, experts.input_width)?;
3986 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3987 return Err(format!(
3988 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3989 )
3990 .into());
3991 }
3992 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3993 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3994 {
3995 return Err(format!(
3996 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3997 STEP_GROUPED_FP8_EXPERTS,
3998 STEP_GROUPED_FP8_WIDTH,
3999 experts.expert_count,
4000 experts.expert_width,
4001 )
4002 .into());
4003 }
4004 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4005 let max_pairs = max_tokens
4006 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4007 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4008 let input_capacity = max_tokens
4009 .checked_mul(experts.input_width)
4010 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4011
4012 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4013 for engine in &self.ranks {
4014 let _main = engine.gpu.enter_main()?;
4015 rank_inputs.push(engine.uninit(input_capacity)?);
4016 }
4017
4018 let mut owners = Vec::with_capacity(self.ranks.len());
4019 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4020 if rank.gate.expert_range != rank.up.expert_range
4021 || rank.gate.expert_range != rank.down.expert_range
4022 {
4023 return Err(format!(
4024 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4025 owner_rank
4026 )
4027 .into());
4028 }
4029 let local_experts = rank.gate.expert_range.len();
4030 let engine = &self.ranks[owner_rank];
4031 let _main = engine.gpu.enter_main()?;
4032 let route_csr =
4033 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4034 let down_csr =
4035 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4036 let gate_workspace = Fp8GroupedWorkspace::new(
4037 engine,
4038 experts.input_width,
4039 experts.expert_width,
4040 max_tokens,
4041 max_pairs,
4042 )?;
4043 let up_workspace = Fp8GroupedWorkspace::new(
4044 engine,
4045 experts.input_width,
4046 experts.expert_width,
4047 max_tokens,
4048 max_pairs,
4049 )?;
4050 let down_workspace = Fp8GroupedWorkspace::new(
4051 engine,
4052 experts.expert_width,
4053 experts.input_width,
4054 max_pairs,
4055 max_pairs,
4056 )?;
4057 let activation = engine.uninit(
4058 max_pairs
4059 .checked_mul(experts.expert_width)
4060 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4061 )?;
4062 owners.push(PreparedStepGroupedExpertOwner {
4063 rank: owner_rank,
4064 global_pairs: Vec::new(),
4065 route_csr,
4066 down_csr,
4067 gate_workspace,
4068 up_workspace,
4069 down_workspace,
4070 activation,
4071 });
4072 }
4073
4074 let mut plan = PreparedStepGroupedExpertParallelGate {
4075 rank_inputs,
4076 owners,
4077 activation_limit,
4078 tokens: 0,
4079 pairs: 0,
4080 max_tokens,
4081 max_pairs,
4082 input_width: experts.input_width,
4083 expert_width: experts.expert_width,
4084 generation: 0,
4085 executed_generation: None,
4086 ready: false,
4087 };
4088 self.refresh_step_grouped_expert_parallel_gate(
4089 experts, &mut plan, input, tokens, selected,
4090 )?;
4091 Ok(plan)
4092 }
4093
4094 fn prepare_step_grouped_expert_parallel_refresh(
4095 &self,
4096 experts: &ResidentExpertParallel,
4097 plan: &PreparedStepGroupedExpertParallelGate,
4098 tokens: usize,
4099 selected: &[usize],
4100 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4101 {
4102 validate_ep_residency(&self.ranks, experts)?;
4103 if plan.rank_inputs.len() != self.ranks.len()
4104 || plan.owners.len() != self.ranks.len()
4105 || plan.input_width != experts.input_width
4106 || plan.expert_width != experts.expert_width
4107 || tokens > plan.max_tokens
4108 {
4109 return Err(format!(
4110 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4111 input={}/{} expert={}/{} tokens={}/{}",
4112 plan.rank_inputs.len(),
4113 self.ranks.len(),
4114 plan.owners.len(),
4115 self.ranks.len(),
4116 plan.input_width,
4117 experts.input_width,
4118 plan.expert_width,
4119 experts.expert_width,
4120 tokens,
4121 plan.max_tokens,
4122 )
4123 .into());
4124 }
4125 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4126 if pairs > plan.max_pairs {
4127 return Err(format!(
4128 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4129 plan.max_pairs
4130 )
4131 .into());
4132 }
4133 let next_generation = plan
4134 .generation
4135 .checked_add(1)
4136 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4137 let owner_routes = partition_expert_owner_routes(
4138 experts.expert_count,
4139 self.ranks.len(),
4140 tokens,
4141 STEP_GROUPED_FP8_TOP_K,
4142 selected,
4143 )?;
4144 let mut schedules = Vec::with_capacity(self.ranks.len());
4145 for routes in owner_routes {
4146 if routes.selected.is_empty() {
4147 schedules.push(None);
4148 continue;
4149 }
4150 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4151 let local_pairs = routes.selected.len();
4152 let route_csr = ExpertCsr::from_pair_rows(
4153 local_experts,
4154 tokens,
4155 &routes.selected,
4156 &routes.token_rows,
4157 )?;
4158 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4159 let down_csr = ExpertCsr::from_pair_rows(
4160 local_experts,
4161 local_pairs,
4162 &routes.selected,
4163 &down_rows,
4164 )?;
4165 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4166 global_pairs: routes.global_pairs,
4167 route_csr,
4168 down_csr,
4169 }));
4170 }
4171 Ok((pairs, next_generation, schedules))
4172 }
4173
4174 fn commit_step_grouped_expert_parallel_refresh(
4175 &self,
4176 plan: &mut PreparedStepGroupedExpertParallelGate,
4177 tokens: usize,
4178 pairs: usize,
4179 next_generation: u64,
4180 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4181 ) -> Result<(), Box<dyn std::error::Error>> {
4182 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4183 let engine = &self.ranks[owner.rank];
4184 let _main = engine.gpu.enter_main()?;
4185 if let Some(schedule) = schedule {
4186 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4187 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4188 owner.global_pairs = schedule.global_pairs;
4189 } else {
4190 owner.route_csr.clear();
4191 owner.down_csr.clear();
4192 owner.global_pairs.clear();
4193 }
4194 }
4195 plan.tokens = tokens;
4196 plan.pairs = pairs;
4197 plan.generation = next_generation;
4198 plan.ready = true;
4199 Ok(())
4200 }
4201
4202 pub fn refresh_step_grouped_expert_parallel_gate(
4203 &self,
4204 experts: &ResidentExpertParallel,
4205 plan: &mut PreparedStepGroupedExpertParallelGate,
4206 input: &[f32],
4207 tokens: usize,
4208 selected: &[usize],
4209 ) -> Result<(), Box<dyn std::error::Error>> {
4210 validate_activations(input, tokens, experts.input_width)?;
4211 let (pairs, next_generation, schedules) =
4212 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4213
4214 plan.ready = false;
4215 plan.executed_generation = None;
4216 {
4217 let root = &self.ranks[0];
4218 let _main = root.gpu.enter_main()?;
4219 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4220 root.stream().memcpy_htod(input, &mut destination)?;
4221 root.stream().synchronize()?;
4222 }
4223 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4224 let root_input = &root_inputs[0];
4225 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4226 let engine = &self.ranks[rank + 1];
4227 let _main = engine.gpu.enter_main()?;
4228 let mut destination = peer_input.slice_mut(0..input.len());
4229 engine
4230 .stream()
4231 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4232 }
4233 self.commit_step_grouped_expert_parallel_refresh(
4234 plan,
4235 tokens,
4236 pairs,
4237 next_generation,
4238 schedules,
4239 )
4240 }
4241
4242 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4247 &self,
4248 experts: &ResidentExpertParallel,
4249 plan: &mut PreparedStepGroupedExpertParallelGate,
4250 input: &CudaSlice<f32>,
4251 tokens: usize,
4252 selected: &[usize],
4253 ) -> Result<(), Box<dyn std::error::Error>> {
4254 let input_values = tokens
4255 .checked_mul(experts.input_width)
4256 .ok_or("Step owner-grouped FP8 input size overflow")?;
4257 let root = self
4258 .ranks
4259 .first()
4260 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4261 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4262 return Err(format!(
4263 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4264 device {}",
4265 input.len(),
4266 input.ordinal(),
4267 input_values,
4268 root.ctx().ordinal(),
4269 )
4270 .into());
4271 }
4272 let (pairs, next_generation, schedules) =
4273 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4274
4275 plan.ready = false;
4276 plan.executed_generation = None;
4277 {
4278 let _main = root.gpu.enter_main()?;
4279 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4280 root.stream()
4281 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4282 root.stream().synchronize()?;
4283 }
4284 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4285 let root_input = &root_inputs[0];
4286 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4287 let engine = &self.ranks[rank + 1];
4288 let _main = engine.gpu.enter_main()?;
4289 let mut destination = peer_input.slice_mut(0..input_values);
4290 engine
4291 .stream()
4292 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4293 }
4294 self.commit_step_grouped_expert_parallel_refresh(
4295 plan,
4296 tokens,
4297 pairs,
4298 next_generation,
4299 schedules,
4300 )
4301 }
4302
4303 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4308 &self,
4309 experts: &ResidentExpertParallel,
4310 plan: &mut PreparedStepGroupedExpertParallelGate,
4311 input: &ResidentReplicatedDeviceRows,
4312 ) -> Result<(), Box<dyn std::error::Error>> {
4313 validate_ep_residency(&self.ranks, experts)?;
4314 validate_replicated_device_rows(&self.ranks, input)?;
4315 if !plan.ready
4316 || input.tokens != plan.tokens
4317 || input.width != plan.input_width
4318 || input.tokens > plan.max_tokens
4319 || plan.rank_inputs.len() != self.ranks.len()
4320 || plan.owners.len() != self.ranks.len()
4321 || plan.input_width != experts.input_width
4322 || plan.expert_width != experts.expert_width
4323 {
4324 return Err("Step owner-grouped replicated input geometry changed".into());
4325 }
4326 let values = input
4327 .tokens
4328 .checked_mul(input.width)
4329 .ok_or("Step owner-grouped replicated input size overflow")?;
4330 let next_generation = plan
4331 .generation
4332 .checked_add(1)
4333 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4334 plan.ready = false;
4335 plan.executed_generation = None;
4336 for (rank, engine) in self.ranks.iter().enumerate() {
4337 let _main = engine.gpu.enter_main()?;
4338 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4339 engine
4340 .stream()
4341 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4342 }
4343 plan.generation = next_generation;
4344 plan.ready = true;
4345 Ok(())
4346 }
4347
4348 pub fn execute_step_grouped_expert_parallel_gate(
4349 &self,
4350 experts: &ResidentExpertParallel,
4351 plan: &mut PreparedStepGroupedExpertParallelGate,
4352 ) -> Result<(), Box<dyn std::error::Error>> {
4353 validate_ep_residency(&self.ranks, experts)?;
4354 if !plan.ready
4355 || plan.rank_inputs.len() != self.ranks.len()
4356 || plan.owners.len() != self.ranks.len()
4357 || plan.input_width != experts.input_width
4358 || plan.expert_width != experts.expert_width
4359 {
4360 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4361 }
4362 plan.executed_generation = None;
4363
4364 for owner in &mut plan.owners {
4365 if owner.global_pairs.is_empty() {
4366 continue;
4367 }
4368 let engine = &self.ranks[owner.rank];
4369 let bank = &experts.ranks[owner.rank];
4370 let _main = engine.gpu.enter_main()?;
4371 let local_pairs = owner.global_pairs.len();
4372 owner.gate_workspace.quantize_for_shape(
4373 engine,
4374 &plan.rank_inputs[owner.rank],
4375 plan.tokens,
4376 local_pairs,
4377 )?;
4378 owner.gate_workspace.project(
4379 engine,
4380 &bank.gate.codes,
4381 &bank.gate.scales,
4382 &owner.route_csr,
4383 bank.gate.code_stride,
4384 bank.gate.scale_stride,
4385 1.0,
4386 )?;
4387 owner.up_workspace.quantize_for_shape(
4388 engine,
4389 &plan.rank_inputs[owner.rank],
4390 plan.tokens,
4391 local_pairs,
4392 )?;
4393 owner.up_workspace.project(
4394 engine,
4395 &bank.up.codes,
4396 &bank.up.scales,
4397 &owner.route_csr,
4398 bank.up.code_stride,
4399 bank.up.scale_stride,
4400 1.0,
4401 )?;
4402 }
4403 for owner in &mut plan.owners {
4404 if owner.global_pairs.is_empty() {
4405 continue;
4406 }
4407 let engine = &self.ranks[owner.rank];
4408 let _main = engine.gpu.enter_main()?;
4409 let values = owner.global_pairs.len() * plan.expert_width;
4410 if let Some(limit) = plan.activation_limit {
4411 engine.silu_clamped_mul_host_expf(
4412 owner.gate_workspace.output(),
4413 owner.up_workspace.output(),
4414 limit,
4415 &mut owner.activation,
4416 values,
4417 )?;
4418 } else {
4419 engine.silu_mul_host_expf(
4420 owner.gate_workspace.output(),
4421 owner.up_workspace.output(),
4422 &mut owner.activation,
4423 values,
4424 )?;
4425 }
4426 }
4427 for owner in &mut plan.owners {
4428 if owner.global_pairs.is_empty() {
4429 continue;
4430 }
4431 let engine = &self.ranks[owner.rank];
4432 let bank = &experts.ranks[owner.rank];
4433 let _main = engine.gpu.enter_main()?;
4434 let local_pairs = owner.global_pairs.len();
4435 owner.down_workspace.quantize_for_shape(
4436 engine,
4437 &owner.activation,
4438 local_pairs,
4439 local_pairs,
4440 )?;
4441 owner.down_workspace.project(
4442 engine,
4443 &bank.down.codes,
4444 &bank.down.scales,
4445 &owner.down_csr,
4446 bank.down.code_stride,
4447 bank.down.scale_stride,
4448 1.0,
4449 )?;
4450 }
4451 plan.executed_generation = Some(plan.generation);
4452 Ok(())
4453 }
4454
4455 pub fn collect_step_grouped_expert_parallel_gate(
4456 &self,
4457 plan: &PreparedStepGroupedExpertParallelGate,
4458 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4459 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4460 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4461 }
4462 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4463 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4464 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4465 for owner in &plan.owners {
4466 if owner.global_pairs.is_empty() {
4467 continue;
4468 }
4469 let engine = &self.ranks[owner.rank];
4470 let _main = engine.gpu.enter_main()?;
4471 let owner_gate = engine.dtoh_view(
4472 &owner
4473 .gate_workspace
4474 .output()
4475 .slice(0..owner.gate_workspace.output_len()),
4476 )?;
4477 let owner_up = engine.dtoh_view(
4478 &owner
4479 .up_workspace
4480 .output()
4481 .slice(0..owner.up_workspace.output_len()),
4482 )?;
4483 let owner_down = engine.dtoh_view(
4484 &owner
4485 .down_workspace
4486 .output()
4487 .slice(0..owner.down_workspace.output_len()),
4488 )?;
4489 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4490 let local_expert = local_pair * plan.expert_width;
4491 let global_expert = global_pair * plan.expert_width;
4492 gate[global_expert..global_expert + plan.expert_width]
4493 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4494 up[global_expert..global_expert + plan.expert_width]
4495 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4496
4497 let local_hidden = local_pair * plan.input_width;
4498 let global_hidden = global_pair * plan.input_width;
4499 down[global_hidden..global_hidden + plan.input_width]
4500 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4501 }
4502 }
4503 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4504 }
4505
4506 pub fn run_step_grouped_expert_parallel_gate(
4507 &self,
4508 experts: &ResidentExpertParallel,
4509 plan: &mut PreparedStepGroupedExpertParallelGate,
4510 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4511 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4512 self.collect_step_grouped_expert_parallel_gate(plan)
4513 }
4514
4515 pub fn prepare_step_grouped_expert_parallel_combine(
4516 &self,
4517 plan: &PreparedStepGroupedExpertParallelGate,
4518 route_weights: &[f32],
4519 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4520 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4521 return Err(
4522 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4523 );
4524 }
4525 let owner_pairs = plan
4526 .owners
4527 .iter()
4528 .map(|owner| owner.global_pairs.as_slice())
4529 .collect::<Vec<_>>();
4530 let shape = validate_weighted_route_combine(
4531 plan.input_width,
4532 STEP_GROUPED_FP8_TOP_K,
4533 plan.max_tokens,
4534 plan.tokens,
4535 &owner_pairs,
4536 route_weights,
4537 )?;
4538 if shape.max_pairs != plan.max_pairs {
4539 return Err(format!(
4540 "Step owner-grouped combine capacity {} != projection capacity {}",
4541 shape.max_pairs, plan.max_pairs
4542 )
4543 .into());
4544 }
4545 let root = self
4546 .ranks
4547 .first()
4548 .ok_or("Step owner-grouped combine has no root rank")?;
4549 let slot_values = shape
4550 .max_pairs
4551 .checked_mul(plan.input_width)
4552 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4553 let output_values = plan
4554 .max_tokens
4555 .checked_mul(plan.input_width)
4556 .ok_or("Step owner-grouped combine output capacity overflow")?;
4557 let (root_device, owners, peer_staging, slots, weights, output) = {
4558 let _main = root.gpu.enter_main()?;
4559 let mut owners = Vec::with_capacity(plan.owners.len());
4560 for _ in &plan.owners {
4561 owners.push(PreparedPeerWeightedRouteOwner {
4562 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4563 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4564 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4565 active_pairs: 0,
4566 });
4567 }
4568 (
4569 root.ctx().ordinal(),
4570 owners,
4571 root.uninit(slot_values)?,
4572 root.uninit(slot_values)?,
4573 root.uninit(shape.max_pairs)?,
4574 root.uninit(output_values)?,
4575 )
4576 };
4577 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4578 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4579 for engine in self.ranks.iter().skip(1) {
4580 let _main = engine.gpu.enter_main()?;
4581 peer_devices.push(engine.ctx().ordinal());
4582 peer_outputs.push(engine.uninit(output_values)?);
4583 }
4584 let mut combine = PreparedPeerWeightedRouteCombine {
4585 root_device,
4586 owners,
4587 peer_staging,
4588 slots,
4589 weights,
4590 output,
4591 peer_devices,
4592 peer_outputs,
4593 width: plan.input_width,
4594 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4595 max_tokens: plan.max_tokens,
4596 max_pairs: shape.max_pairs,
4597 tokens: 0,
4598 pairs: 0,
4599 projection_generation: 0,
4600 output_generation: None,
4601 broadcast_generation: None,
4602 ready: false,
4603 };
4604 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4605 Ok(combine)
4606 }
4607
4608 pub fn refresh_step_grouped_expert_parallel_combine(
4609 &self,
4610 plan: &PreparedStepGroupedExpertParallelGate,
4611 combine: &mut PreparedPeerWeightedRouteCombine,
4612 route_weights: &[f32],
4613 ) -> Result<(), Box<dyn std::error::Error>> {
4614 let output_capacity = combine
4615 .max_tokens
4616 .checked_mul(combine.width)
4617 .ok_or("Step owner-grouped combine output capacity overflow")?;
4618 if !plan.ready
4619 || combine.owners.len() != plan.owners.len()
4620 || combine.peer_devices.len() + 1 != self.ranks.len()
4621 || combine.peer_outputs.len() + 1 != self.ranks.len()
4622 || combine.width != plan.input_width
4623 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4624 || combine.max_tokens != plan.max_tokens
4625 || combine.max_pairs != plan.max_pairs
4626 || combine.output.len() < output_capacity
4627 || combine
4628 .peer_outputs
4629 .iter()
4630 .any(|output| output.len() < output_capacity)
4631 {
4632 return Err("Step owner-grouped combine/projection geometry changed".into());
4633 }
4634 if self
4635 .ranks
4636 .iter()
4637 .skip(1)
4638 .zip(&combine.peer_devices)
4639 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4640 {
4641 return Err("Step owner-grouped combine peer devices changed".into());
4642 }
4643 let owner_pairs = plan
4644 .owners
4645 .iter()
4646 .map(|owner| owner.global_pairs.as_slice())
4647 .collect::<Vec<_>>();
4648 let shape = validate_weighted_route_combine(
4649 combine.width,
4650 combine.experts_per_token,
4651 combine.max_tokens,
4652 plan.tokens,
4653 &owner_pairs,
4654 route_weights,
4655 )?;
4656 if shape.max_pairs != combine.max_pairs {
4657 return Err("Step owner-grouped combine capacity changed during refresh".into());
4658 }
4659 let metadata = owner_pairs
4660 .iter()
4661 .map(|pairs| {
4662 let token_rows = pairs
4663 .iter()
4664 .map(|&pair| (pair / combine.experts_per_token) as i32)
4665 .collect::<Vec<_>>();
4666 let slots = pairs
4667 .iter()
4668 .map(|&pair| (pair % combine.experts_per_token) as i32)
4669 .collect::<Vec<_>>();
4670 let weights = pairs
4671 .iter()
4672 .map(|&pair| route_weights[pair])
4673 .collect::<Vec<_>>();
4674 (token_rows, slots, weights)
4675 })
4676 .collect::<Vec<_>>();
4677
4678 combine.ready = false;
4679 combine.output_generation = None;
4680 combine.broadcast_generation = None;
4681 let root = self
4682 .ranks
4683 .first()
4684 .ok_or("Step owner-grouped combine has no root rank")?;
4685 let _main = root.gpu.enter_main()?;
4686 if root.ctx().ordinal() != combine.root_device {
4687 return Err(format!(
4688 "Step owner-grouped combine root device changed {} != {}",
4689 root.ctx().ordinal(),
4690 combine.root_device
4691 )
4692 .into());
4693 }
4694 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4695 if token_rows.is_empty() {
4696 owner.active_pairs = 0;
4697 continue;
4698 }
4699 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4700 root.htod_i32_into(&mut owner.slots, &slots)?;
4701 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4702 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4703 owner.active_pairs = token_rows.len();
4704 }
4705 combine.tokens = plan.tokens;
4706 combine.pairs = shape.pairs;
4707 combine.projection_generation = plan.generation;
4708 combine.ready = true;
4709 Ok(())
4710 }
4711
4712 pub fn execute_step_grouped_expert_parallel_combine(
4713 &self,
4714 plan: &PreparedStepGroupedExpertParallelGate,
4715 combine: &mut PreparedPeerWeightedRouteCombine,
4716 ) -> Result<(), Box<dyn std::error::Error>> {
4717 if !plan.ready
4718 || plan.executed_generation != Some(plan.generation)
4719 || !combine.ready
4720 || combine.tokens != plan.tokens
4721 || combine.pairs != plan.pairs
4722 || combine.width != plan.input_width
4723 || combine.owners.len() != plan.owners.len()
4724 || combine.projection_generation != plan.generation
4725 {
4726 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4727 }
4728 combine.output_generation = None;
4729 combine.broadcast_generation = None;
4730 for owner in &plan.owners {
4731 if owner.rank == 0 || owner.global_pairs.is_empty() {
4732 continue;
4733 }
4734 let engine = &self.ranks[owner.rank];
4735 let _main = engine.gpu.enter_main()?;
4736 engine.stream().synchronize()?;
4737 }
4738 let root = self
4739 .ranks
4740 .first()
4741 .ok_or("Step owner-grouped combine has no root rank")?;
4742 let _main = root.gpu.enter_main()?;
4743 if root.ctx().ordinal() != combine.root_device {
4744 return Err("Step owner-grouped combine is not resident on the root device".into());
4745 }
4746 for (index, owner) in plan.owners.iter().enumerate() {
4747 let metadata = &combine.owners[index];
4748 if owner.global_pairs.len() != metadata.active_pairs {
4749 return Err(format!(
4750 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4751 owner.global_pairs.len(),
4752 metadata.active_pairs
4753 )
4754 .into());
4755 }
4756 if metadata.active_pairs == 0 {
4757 continue;
4758 }
4759 let values = metadata
4760 .active_pairs
4761 .checked_mul(combine.width)
4762 .ok_or("Step owner-grouped combine peer value count overflow")?;
4763 if owner.rank == 0 {
4764 root.scatter_slot(
4765 owner.down_workspace.output(),
4766 &metadata.token_rows,
4767 &metadata.slots,
4768 &metadata.weights,
4769 &mut combine.slots,
4770 &mut combine.weights,
4771 combine.width,
4772 combine.experts_per_token,
4773 metadata.active_pairs,
4774 )?;
4775 } else {
4776 let source = owner.down_workspace.output().slice(0..values);
4777 let mut destination = combine.peer_staging.slice_mut(0..values);
4778 root.stream().memcpy_dtod(&source, &mut destination)?;
4779 root.scatter_slot(
4780 &combine.peer_staging,
4781 &metadata.token_rows,
4782 &metadata.slots,
4783 &metadata.weights,
4784 &mut combine.slots,
4785 &mut combine.weights,
4786 combine.width,
4787 combine.experts_per_token,
4788 metadata.active_pairs,
4789 )?;
4790 }
4791 }
4792 root.reduce_slots_host(
4793 &combine.slots,
4794 &combine.weights,
4795 &mut combine.output,
4796 combine.width,
4797 combine.experts_per_token,
4798 combine.tokens,
4799 )?;
4800 combine.output_generation = Some(plan.generation);
4801 Ok(())
4802 }
4803
4804 pub fn collect_step_grouped_expert_parallel_combine(
4805 &self,
4806 plan: &PreparedStepGroupedExpertParallelGate,
4807 combine: &PreparedPeerWeightedRouteCombine,
4808 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4809 if !plan.ready
4810 || combine.output_generation != Some(plan.generation)
4811 || combine.projection_generation != plan.generation
4812 {
4813 return Err("Step owner-grouped combine output is stale or has not executed".into());
4814 }
4815 let root = self
4816 .ranks
4817 .first()
4818 .ok_or("Step owner-grouped combine has no root rank")?;
4819 let _main = root.gpu.enter_main()?;
4820 if root.ctx().ordinal() != combine.root_device {
4821 return Err("Step owner-grouped combine is not resident on the root device".into());
4822 }
4823 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4824 }
4825
4826 pub fn copy_step_grouped_expert_parallel_combine_root(
4831 &self,
4832 plan: &PreparedStepGroupedExpertParallelGate,
4833 combine: &PreparedPeerWeightedRouteCombine,
4834 destination: &Engine,
4835 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4836 if !plan.ready
4837 || combine.output_generation != Some(plan.generation)
4838 || combine.projection_generation != plan.generation
4839 {
4840 return Err("Step owner-grouped combine output is stale or has not executed".into());
4841 }
4842 let root = self
4843 .ranks
4844 .first()
4845 .ok_or("Step owner-grouped combine has no root rank")?;
4846 if root.ctx().ordinal() != combine.root_device
4847 || destination.ctx().ordinal() != combine.root_device
4848 {
4849 return Err(format!(
4850 "Step owner-grouped combine root/destination devices {}/{} != {}",
4851 root.ctx().ordinal(),
4852 destination.ctx().ordinal(),
4853 combine.root_device,
4854 )
4855 .into());
4856 }
4857 let values = combine
4858 .tokens
4859 .checked_mul(combine.width)
4860 .ok_or("Step owner-grouped combine copy size overflow")?;
4861 {
4862 let _main = root.gpu.enter_main()?;
4863 root.stream().synchronize()?;
4864 }
4865 let _main = destination.gpu.enter_main()?;
4866 let mut output = destination.uninit(values)?;
4867 destination
4868 .stream()
4869 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4870 Ok(output)
4871 }
4872
4873 pub fn broadcast_step_grouped_expert_parallel_combine(
4874 &self,
4875 plan: &PreparedStepGroupedExpertParallelGate,
4876 combine: &mut PreparedPeerWeightedRouteCombine,
4877 ) -> Result<(), Box<dyn std::error::Error>> {
4878 if !plan.ready
4879 || combine.output_generation != Some(plan.generation)
4880 || combine.projection_generation != plan.generation
4881 || combine.peer_devices.len() + 1 != self.ranks.len()
4882 || combine.peer_outputs.len() + 1 != self.ranks.len()
4883 {
4884 return Err("Step owner-grouped combine output cannot be broadcast".into());
4885 }
4886 combine.broadcast_generation = None;
4887 let values = combine
4888 .tokens
4889 .checked_mul(combine.width)
4890 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4891 {
4892 let root = self
4893 .ranks
4894 .first()
4895 .ok_or("Step owner-grouped combine has no root rank")?;
4896 let _main = root.gpu.enter_main()?;
4897 if root.ctx().ordinal() != combine.root_device {
4898 return Err("Step owner-grouped combine root device changed".into());
4899 }
4900 root.stream().synchronize()?;
4901 }
4902 let source = &combine.output;
4903 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4904 let engine = &self.ranks[index + 1];
4905 let _main = engine.gpu.enter_main()?;
4906 if engine.ctx().ordinal() != combine.peer_devices[index] {
4907 return Err(format!(
4908 "Step owner-grouped combine peer {} device changed",
4909 index + 1
4910 )
4911 .into());
4912 }
4913 let mut destination = destination_buffer.slice_mut(0..values);
4914 engine
4915 .stream()
4916 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4917 }
4918 combine.broadcast_generation = Some(plan.generation);
4919 Ok(())
4920 }
4921
4922 pub fn collect_step_grouped_expert_parallel_broadcast(
4923 &self,
4924 plan: &PreparedStepGroupedExpertParallelGate,
4925 combine: &PreparedPeerWeightedRouteCombine,
4926 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4927 if !plan.ready
4928 || combine.output_generation != Some(plan.generation)
4929 || combine.broadcast_generation != Some(plan.generation)
4930 || combine.peer_outputs.len() + 1 != self.ranks.len()
4931 {
4932 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4933 }
4934 let values = combine
4935 .tokens
4936 .checked_mul(combine.width)
4937 .ok_or("Step owner-grouped combine collection size overflow")?;
4938 let mut outputs = Vec::with_capacity(self.ranks.len());
4939 {
4940 let root = &self.ranks[0];
4941 let _main = root.gpu.enter_main()?;
4942 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4943 }
4944 for (index, output) in combine.peer_outputs.iter().enumerate() {
4945 let engine = &self.ranks[index + 1];
4946 let _main = engine.gpu.enter_main()?;
4947 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4948 }
4949 Ok(outputs)
4950 }
4951
4952 pub fn finish_step_grouped_expert_parallel_layer(
4954 &self,
4955 plan: &PreparedStepGroupedExpertParallelGate,
4956 combine: &PreparedPeerWeightedRouteCombine,
4957 shared: &ResidentReplicatedDeviceRows,
4958 residual: &ResidentReplicatedDeviceRows,
4959 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4960 validate_replicated_device_rows(&self.ranks, shared)?;
4961 validate_replicated_device_rows(&self.ranks, residual)?;
4962 if !plan.ready
4963 || plan.executed_generation != Some(plan.generation)
4964 || combine.output_generation != Some(plan.generation)
4965 || combine.broadcast_generation != Some(plan.generation)
4966 || combine.projection_generation != plan.generation
4967 || combine.peer_outputs.len() + 1 != self.ranks.len()
4968 || shared.tokens != combine.tokens
4969 || residual.tokens != combine.tokens
4970 || shared.width != combine.width
4971 || residual.width != combine.width
4972 {
4973 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4974 }
4975 let values = combine
4976 .tokens
4977 .checked_mul(combine.width)
4978 .ok_or("Step full-layer output size overflow")?;
4979 let mut ranks = Vec::with_capacity(self.ranks.len());
4980 for rank in 0..self.ranks.len() {
4981 let engine = &self.ranks[rank];
4982 let _main = engine.gpu.enter_main()?;
4983 let routed = if rank == 0 {
4984 &combine.output
4985 } else {
4986 &combine.peer_outputs[rank - 1]
4987 };
4988 let mut ffn = engine.uninit(values)?;
4989 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4990 let mut output = engine.uninit(values)?;
4991 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4992 ranks.push(output);
4993 }
4994 Ok(ResidentReplicatedDeviceRows {
4995 ranks,
4996 tokens: combine.tokens,
4997 width: combine.width,
4998 })
4999 }
5000
5001 pub fn run_step_grouped_expert_parallel_combine(
5002 &self,
5003 plan: &PreparedStepGroupedExpertParallelGate,
5004 combine: &mut PreparedPeerWeightedRouteCombine,
5005 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5006 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5007 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5008 }
5009
5010 pub fn upload_tensor_parallel(
5011 &self,
5012 gate: E4m3ExpertBank<'_>,
5013 up: E4m3ExpertBank<'_>,
5014 down: E4m3ExpertBank<'_>,
5015 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5016 gate.validate()?;
5017 up.validate()?;
5018 down.validate()?;
5019 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5020 return Err("TP gate/up/down expert counts differ".into());
5021 }
5022 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5023 return Err("TP gate/up dimensions differ".into());
5024 }
5025 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5026 return Err(format!(
5027 "TP down {}x{} does not invert gate/up {}x{}",
5028 down.out_features, down.in_features, gate.out_features, gate.in_features
5029 )
5030 .into());
5031 }
5032 let tp = self.ranks.len();
5033 validate_column_bank_shape(gate, tp)?;
5034 validate_column_bank_shape(up, tp)?;
5035 validate_row_bank_shape(down, tp)?;
5036
5037 let mut gate_ranks = Vec::with_capacity(tp);
5038 let mut up_ranks = Vec::with_capacity(tp);
5039 let mut down_ranks = Vec::with_capacity(tp);
5040 for (rank, engine) in self.ranks.iter().enumerate() {
5041 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5042 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5043 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5044 }
5045 Ok(ResidentTensorParallel {
5046 bank: ResidentTpExpertBank {
5047 gate: gate_ranks,
5048 up: up_ranks,
5049 down: down_ranks,
5050 expert_count: gate.expert_count,
5051 input_width: gate.in_features,
5052 expert_width: gate.out_features,
5053 },
5054 })
5055 }
5056
5057 pub fn run_tensor_parallel_routes(
5058 &self,
5059 experts: &ResidentTensorParallel,
5060 input: &[f32],
5061 tokens: usize,
5062 selected: &[usize],
5063 route_weights: &[f32],
5064 experts_per_token: usize,
5065 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5066 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5067 validate_activations(input, tokens, experts.bank.input_width)?;
5068 let pairs = tokens
5069 .checked_mul(experts_per_token)
5070 .ok_or("TP route count overflow")?;
5071 if selected.len() != pairs || route_weights.len() != pairs {
5072 return Err(format!(
5073 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5074 {experts_per_token} ({pairs})",
5075 selected.len(),
5076 route_weights.len(),
5077 )
5078 .into());
5079 }
5080 if !route_weights.iter().all(|weight| weight.is_finite()) {
5081 return Err("TP route weights contain a non-finite value".into());
5082 }
5083
5084 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5085 for token in 0..tokens {
5086 let input_row =
5087 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5088 for slot in 0..experts_per_token {
5089 let pair = token * experts_per_token + slot;
5090 let expert = selected[pair];
5091 if expert >= experts.bank.expert_count {
5092 return Err(format!(
5093 "TP selected expert {expert} outside 0..{}",
5094 experts.bank.expert_count
5095 )
5096 .into());
5097 }
5098 let down = if self.native_p2p {
5099 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5100 } else {
5101 let gate =
5102 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5103 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5104 let activated: Vec<f32> = gate
5105 .iter()
5106 .zip(&up)
5107 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5108 .collect();
5109 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5110 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5111 };
5112 let weight = route_weights[pair];
5113 for (sum, value) in output
5114 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5115 .iter_mut()
5116 .zip(down)
5117 {
5118 *sum += weight * value;
5119 }
5120 }
5121 }
5122 Ok(output)
5123 }
5124
5125 fn run_column_bank_expert(
5126 &self,
5127 ranks: &[ResidentE4m3ExpertBankRank],
5128 expert: usize,
5129 input: &[f32],
5130 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5131 let local_out = ranks
5132 .first()
5133 .ok_or("TP column bank has no ranks")?
5134 .out_features;
5135 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5136 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5137 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5138 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5139 }
5140 Ok(gathered)
5141 }
5142
5143 fn run_row_bank_expert(
5144 &self,
5145 ranks: &[ResidentE4m3ExpertBankRank],
5146 expert: usize,
5147 input: &[f32],
5148 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5149 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5150 if input.len() != local_in * ranks.len() {
5151 return Err(format!(
5152 "TP row input {} != {} ranks x {local_in}",
5153 input.len(),
5154 ranks.len()
5155 )
5156 .into());
5157 }
5158 let out_features = ranks[0].out_features;
5159 let mut reduced = vec![0.0f32; out_features];
5160 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5161 let blocks = bank
5162 .k_blocks
5163 .ok_or("TP row bank is not packed in native K-block order")?;
5164 if blocks * FP8_BLOCK != local_in {
5165 return Err(format!(
5166 "TP row bank has {blocks} blocks but local input width is {local_in}"
5167 )
5168 .into());
5169 }
5170 for block in 0..blocks {
5171 let global_start = rank * local_in + block * FP8_BLOCK;
5172 let partial = run_resident_bank_expert_block(
5173 engine,
5174 bank,
5175 expert,
5176 block,
5177 &input[global_start..global_start + FP8_BLOCK],
5178 )?;
5179 for (sum, value) in reduced.iter_mut().zip(partial) {
5180 *sum += value;
5181 }
5182 }
5183 }
5184 Ok(reduced)
5185 }
5186
5187 fn run_tensor_parallel_expert_native(
5188 &self,
5189 bank: &ResidentTpExpertBank,
5190 expert: usize,
5191 input: &[f32],
5192 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5193 if !self.native_p2p || self.ranks.len() < 2 {
5194 return Err("native TP expert execution requires at least two P2P ranks".into());
5195 }
5196 let local_out = bank
5197 .gate
5198 .first()
5199 .ok_or("native TP gate bank has no ranks")?
5200 .out_features;
5201 if local_out * self.ranks.len() != bank.expert_width {
5202 return Err(format!(
5203 "native TP gate shards {}x{local_out} != expert width {}",
5204 self.ranks.len(),
5205 bank.expert_width
5206 )
5207 .into());
5208 }
5209
5210 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5213 let root_input = {
5214 let root = &self.ranks[0];
5215 let _main = root.gpu.enter_main()?;
5216 root.htod(input)?
5217 };
5218 rank_inputs.push(root_input);
5219 for engine in &self.ranks[1..] {
5220 let peer_input = {
5221 let _main = engine.gpu.enter_main()?;
5222 let mut peer_input = engine.uninit(input.len())?;
5223 engine
5224 .stream()
5225 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5226 peer_input
5227 };
5228 rank_inputs.push(peer_input);
5229 }
5230
5231 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5232 let mut up_shards = Vec::with_capacity(self.ranks.len());
5233 for rank in 0..self.ranks.len() {
5234 gate_shards.push(run_resident_bank_expert_device(
5235 &self.ranks[rank],
5236 &bank.gate[rank],
5237 expert,
5238 &rank_inputs[rank],
5239 1,
5240 )?);
5241 up_shards.push(run_resident_bank_expert_device(
5242 &self.ranks[rank],
5243 &bank.up[rank],
5244 expert,
5245 &rank_inputs[rank],
5246 1,
5247 )?);
5248 }
5249
5250 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5254 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5255 let activated = gate
5256 .iter()
5257 .zip(&up)
5258 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5259 .collect::<Vec<_>>();
5260 debug_assert_eq!(activated.len(), bank.expert_width);
5261
5262 let root_activated = {
5263 let root = &self.ranks[0];
5264 let _main = root.gpu.enter_main()?;
5265 root.htod(&activated)?
5266 };
5267 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5268 for (rank, engine) in self.ranks.iter().enumerate() {
5269 let start = rank * local_out;
5270 let source = root_activated.slice(start..start + local_out);
5271 let local = {
5272 let _main = engine.gpu.enter_main()?;
5273 let mut local = engine.uninit(local_out)?;
5274 engine.stream().memcpy_dtod(&source, &mut local)?;
5275 local
5276 };
5277 rank_activated.push(local);
5278 }
5279
5280 let out_features = bank
5281 .down
5282 .first()
5283 .ok_or("native TP down bank has no ranks")?
5284 .out_features;
5285 let mut reduced = {
5286 let root = &self.ranks[0];
5287 let _main = root.gpu.enter_main()?;
5288 root.htod(&vec![0.0f32; out_features])?
5289 };
5290 let mut remote_partial_keepalive = Vec::new();
5291 for rank in 0..self.ranks.len() {
5292 let down = &bank.down[rank];
5293 let blocks = down
5294 .k_blocks
5295 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5296 if blocks * FP8_BLOCK != local_out {
5297 return Err(format!(
5298 "native TP rank {rank} has {blocks} blocks but local activation width is \
5299 {local_out}"
5300 )
5301 .into());
5302 }
5303 for block in 0..blocks {
5304 let start = block * FP8_BLOCK;
5305 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5306 let partial = run_resident_bank_expert_block_device(
5307 &self.ranks[rank],
5308 down,
5309 expert,
5310 block,
5311 &input_block,
5312 )?;
5313 let root_partial = if rank == 0 {
5314 partial
5315 } else {
5316 let root = &self.ranks[0];
5317 let _main = root.gpu.enter_main()?;
5318 let mut peer_partial = root.uninit(out_features)?;
5319 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5320 remote_partial_keepalive.push(partial);
5321 peer_partial
5322 };
5323 let next = {
5324 let root = &self.ranks[0];
5325 let _main = root.gpu.enter_main()?;
5326 let mut next = root.uninit(out_features)?;
5327 root.add(&reduced, &root_partial, &mut next, out_features)?;
5328 next
5329 };
5330 reduced = next;
5331 }
5332 }
5333 let output = {
5334 let root = &self.ranks[0];
5335 let _main = root.gpu.enter_main()?;
5336 root.dtoh(&reduced)?
5337 };
5338 drop(remote_partial_keepalive);
5339 Ok(output)
5340 }
5341
5342 pub fn gather_native_column_shards_device(
5344 &self,
5345 shards: &[CudaSlice<f32>],
5346 tokens: usize,
5347 local_out: usize,
5348 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5349 let shard_len = tokens
5350 .checked_mul(local_out)
5351 .ok_or("native TP gather shard size overflow")?;
5352 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5353 return Err("native TP gather shard geometry mismatch".into());
5354 }
5355 for engine in &self.ranks[1..] {
5359 let _main = engine.gpu.enter_main()?;
5360 engine.stream().synchronize()?;
5361 }
5362 let root = &self.ranks[0];
5363 let _main = root.gpu.enter_main()?;
5364 let global_out = shards
5365 .len()
5366 .checked_mul(local_out)
5367 .ok_or("native TP gather output width overflow")?;
5368 let gathered_len = tokens
5369 .checked_mul(global_out)
5370 .ok_or("native TP gather output size overflow")?;
5371 let mut gathered = root.uninit(gathered_len)?;
5372 if self.bulk_p2p {
5373 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5374 if shards.len() > 1 {
5375 let mut staging = root.uninit(shard_len)?;
5376 for (rank, shard) in shards.iter().enumerate().skip(1) {
5377 root.stream().memcpy_dtod(shard, &mut staging)?;
5378 root.place_rows_strided(
5379 &staging,
5380 &mut gathered,
5381 local_out,
5382 tokens,
5383 global_out,
5384 rank * local_out,
5385 )?;
5386 }
5387 }
5388 } else {
5389 for token in 0..tokens {
5390 for (rank, shard) in shards.iter().enumerate() {
5391 let source = shard.slice(token * local_out..(token + 1) * local_out);
5392 let start = token * global_out + rank * local_out;
5393 let mut destination = gathered.slice_mut(start..start + local_out);
5394 root.stream().memcpy_dtod(&source, &mut destination)?;
5395 }
5396 }
5397 }
5398 Ok(gathered)
5399 }
5400
5401 pub fn gather_native_column_shards(
5402 &self,
5403 shards: &[CudaSlice<f32>],
5404 tokens: usize,
5405 local_out: usize,
5406 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5407 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5408 let root = &self.ranks[0];
5409 let _main = root.gpu.enter_main()?;
5410 root.dtoh(&gathered)
5411 }
5412
5413 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5414 &self.decode_v2
5415 }
5416
5417 pub(crate) fn decode_v2_ensure(
5426 &self,
5427 e: &Engine,
5428 q_m: &ResidentBf16ColumnParallel,
5429 k_m: &ResidentBf16ColumnParallel,
5430 v_m: &ResidentBf16ColumnParallel,
5431 o_m: &ResidentStepBf16RowParallel,
5432 heads: usize,
5433 ) -> Result<usize, Box<dyn std::error::Error>> {
5434 if self.ranks.len() > 1 && !self.native_p2p {
5435 return Err("step TP decode v2 requires native P2P ranks".into());
5436 }
5437 let ranks = self.ranks.len();
5438 let fused_door = step_tp_qkv_fused_enabled()?;
5442 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5443 ResidentBf16Weight::F32(_) => true,
5444 ResidentBf16Weight::Bf16(_) => fused_door,
5445 };
5446 for matrix in [q_m, k_m, v_m] {
5447 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5448 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5449 return Err("step TP decode v2 QKV geometry mismatch".into());
5450 }
5451 for rank in &matrix.ranks {
5452 if !arm_ok(&rank.weight) {
5453 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5454 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5455 .into());
5456 }
5457 }
5458 }
5459 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5460 for blocks in &o_m.ranks {
5461 for block in blocks {
5462 if !arm_ok(&block.weight) {
5463 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5464 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5465 .into());
5466 }
5467 }
5468 }
5469 if v_m.out_features != k_m.out_features
5470 || o_m.in_features != q_m.out_features
5471 || heads == 0
5472 || heads % ranks != 0
5473 {
5474 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5475 }
5476 let local_q_dim = q_m.out_features / ranks;
5477 let local_kv_dim = k_m.out_features / ranks;
5478 let o_out = o_m.out_features;
5479 let o_block_cols = o_m.canonical_chunk_cols;
5480 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5481 if blocks_per_rank == 0
5482 || o_m
5483 .ranks
5484 .iter()
5485 .any(|blocks| blocks.len() != blocks_per_rank)
5486 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5487 {
5488 return Err("step TP decode v2 O canonical block grid mismatch".into());
5489 }
5490
5491 let mut guard = self
5492 .decode_v2
5493 .lock()
5494 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5495 if let Some(index) = guard.iter().position(|ws| {
5496 ws.local_q_dim == local_q_dim
5497 && ws.local_kv_dim == local_kv_dim
5498 && ws.heads == heads
5499 && ws.o_out == o_out
5500 && ws.o_block_cols == o_block_cols
5501 && ws.blocks_per_rank == blocks_per_rank
5502 && ws.e_device == e.ctx().ordinal()
5503 && ws.q.len() == ranks
5504 }) {
5505 return Ok(index);
5506 }
5507
5508 let mut q_raw = Vec::with_capacity(ranks);
5509 let mut k_raw = Vec::with_capacity(ranks);
5510 let mut v_raw = Vec::with_capacity(ranks);
5511 let mut q = Vec::with_capacity(ranks);
5512 let mut k = Vec::with_capacity(ranks);
5513 let mut pos = Vec::with_capacity(ranks);
5514 let mut gate = Vec::with_capacity(ranks);
5515 let mut attn_out = Vec::with_capacity(ranks);
5516 let mut gated = Vec::with_capacity(ranks);
5517 let mut fuse_ctr = Vec::with_capacity(ranks);
5518 let mut o_partials = Vec::with_capacity(ranks);
5519 let mut ev_rank = Vec::with_capacity(ranks);
5520 let direct_join = oproj_direct_on();
5521 for (rank, engine) in self.ranks.iter().enumerate() {
5522 let _main = engine.gpu.enter_main()?;
5523 q_raw.push(engine.uninit(local_q_dim)?);
5524 k_raw.push(engine.uninit(local_kv_dim)?);
5525 v_raw.push(engine.uninit(local_kv_dim)?);
5526 q.push(engine.uninit(local_q_dim)?);
5527 k.push(engine.uninit(local_kv_dim)?);
5528 pos.push(engine.htod_i32(&[0])?);
5529 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5530 gate.push(engine.uninit(heads / ranks)?);
5531 attn_out.push(engine.uninit(local_q_dim)?);
5532 gated.push(engine.uninit(local_q_dim)?);
5533 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5534 for _ in 0..blocks_per_rank {
5535 if direct_join && rank != 0 {
5538 let root = &self.ranks[0];
5539 let _root_main = root.gpu.enter_main()?;
5540 rank_partials.push(root.uninit(o_out)?);
5541 } else {
5542 rank_partials.push(engine.uninit(o_out)?);
5543 }
5544 }
5545 o_partials.push(rank_partials);
5546 ev_rank.push(engine.ctx().new_event(None)?);
5547 }
5548 let root = &self.ranks[0];
5549 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5550 let _main = root.gpu.enter_main()?;
5551 (
5552 root.uninit(o_out)?,
5553 root.uninit(o_out)?,
5554 root.uninit(o_out)?,
5555 root.htod(&vec![0.0f32; o_out])?,
5556 root.uninit(ranks * local_kv_dim)?,
5557 root.uninit(ranks * local_kv_dim)?,
5558 root.ctx().new_event(None)?,
5559 root.ctx().new_event(None)?,
5560 )
5561 };
5562 let (gate_e, ev_entry) = {
5563 let _main = e.gpu.enter_main()?;
5564 (e.uninit(heads)?, e.ctx().new_event(None)?)
5565 };
5566 let raw_attn_in = Vec::new();
5567 let raw_pos = Vec::new();
5568 guard.push(StepTpDecodeV2Ws {
5569 tcol_q: Vec::new(),
5570 tcol_k: Vec::new(),
5571 tcol_v: Vec::new(),
5572 tcol_g: Vec::new(),
5573 tcol_in: Vec::new(),
5574 tcol_cap: 0,
5575 w8_aq: Vec::new(),
5576 w8_ad: Vec::new(),
5577 w8_in: 0,
5578 w8o_aq: Vec::new(),
5579 w8o_ad: Vec::new(),
5580 w8o_in: 0,
5581 w8t_aq: Vec::new(),
5582 w8t_ad: Vec::new(),
5583 w8t_in: 0,
5584 w8t_oaq: Vec::new(),
5585 w8t_oad: Vec::new(),
5586 w8t_oin: 0,
5587 w8t_cap: 0,
5588 fa2_q: Vec::new(),
5589 fa2_gate: Vec::new(),
5590 fa2_gated: Vec::new(),
5591 fa2_cap: 0,
5592 rope_k_t: Vec::new(),
5593 rope_ctr_t: Vec::new(),
5594 rope_pos_t: Vec::new(),
5595 rows_tabs: Vec::new(),
5596 rows_tab_t: Vec::new(),
5597 rows_tab_shadow: Vec::new(),
5598 tcol_gated: Vec::new(),
5599 tcol_opart: Vec::new(),
5600 tcol_opeer: None,
5601 tcol_omix: None,
5602 tcol_ocap: 0,
5603 q_raw,
5604 k_raw,
5605 v_raw,
5606 q,
5607 k,
5608 pos,
5609 fuse_ctr,
5610 gate,
5611 attn_out,
5612 gated,
5613 o_partials,
5614 ev_rank,
5615 peer_partial,
5616 reduce_a,
5617 reduce_b,
5618 zeros,
5619 k_shadow,
5620 v_shadow,
5621 ev_refresh,
5622 ev_oproj,
5623 gate_e,
5624 attn_in: Vec::new(),
5625 h_stage: None,
5626 pos_stage: None,
5627 raw_h_stage: 0,
5628 raw_pos_stage: 0,
5629 raw_attn_in,
5630 raw_pos,
5631 raw_o_partial1: 0,
5632 raw_peer_partial: 0,
5633 raw_k1: 0,
5634 raw_v1: 0,
5635 raw_k_shadow: 0,
5636 raw_v_shadow: 0,
5637 raw_mixed_stage_e: 0,
5638 raw_reduce_a: 0,
5639 raw_shadow_stage_e: (0, 0),
5640 ev_entry,
5641 e_device: e.ctx().ordinal(),
5642 local_q_dim,
5643 local_kv_dim,
5644 heads,
5645 o_out,
5646 o_block_cols,
5647 blocks_per_rank,
5648 });
5649 eprintln!(
5650 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5651 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5652 residency=persistent ordering=evented performance_claim=false"
5653 );
5654 Ok(guard.len() - 1)
5655 }
5656
5657 #[allow(clippy::too_many_arguments)]
5665 #[allow(clippy::too_many_arguments)]
5670 pub fn decode_v2_input_qkv_tcol(
5671 &self,
5672 ws_index: usize,
5673 e: &Engine,
5674 h_t: &CudaSlice<f32>,
5675 t: usize,
5676 q_m: &ResidentBf16ColumnParallel,
5677 k_m: &ResidentBf16ColumnParallel,
5678 v_m: &ResidentBf16ColumnParallel,
5679 gate_shards: Option<StepTpGateShards<'_>>,
5680 ) -> Result<(), Box<dyn std::error::Error>> {
5681 let ranks = self.ranks.len();
5682 let mut guard = self
5683 .decode_v2
5684 .lock()
5685 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5686 let ws = guard
5687 .get_mut(ws_index)
5688 .ok_or("step TP decode v2 workspace index out of range")?;
5689 let in_f = q_m.in_features;
5690 if h_t.len() < t * in_f || t == 0 || t > 32 {
5691 return Err("decode_v2_input_qkv_tcol geometry".into());
5692 }
5693 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5695 ws.tcol_q.clear();
5696 ws.tcol_k.clear();
5697 ws.tcol_v.clear();
5698 ws.tcol_g.clear();
5699 ws.tcol_in.clear();
5700 for engine in &self.ranks {
5701 let _m = engine.gpu.enter_main()?;
5702 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
5703 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
5704 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
5705 ws.tcol_g
5706 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
5707 ws.tcol_in.push(engine.uninit(32 * in_f)?);
5708 }
5709 ws.tcol_cap = 32;
5710 }
5711 use cudarc::driver::DevicePtr;
5713 let raw_src = {
5714 let _main = e.gpu.enter_main()?;
5715 let stream = e.stream();
5716 let (p, _g) = h_t.device_ptr(&stream);
5717 ws.ev_entry.record(&stream)?;
5718 p as u64
5719 };
5720 for rank in 0..ranks {
5721 let engine = &self.ranks[rank];
5722 let _main = engine.gpu.enter_main()?;
5723 engine.stream().wait(&ws.ev_entry)?;
5724 let raw_dst = {
5725 let stream = engine.stream();
5726 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5727 p as u64
5728 };
5729 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5730 let out_g = match &gate_shards {
5731 Some(_) => ws.heads / ranks,
5732 None => 0,
5733 };
5734 match (
5735 &q_m.ranks[rank].weight,
5736 &k_m.ranks[rank].weight,
5737 &v_m.ranks[rank].weight,
5738 ) {
5739 (
5740 ResidentBf16Weight::Bf16(wq),
5741 ResidentBf16Weight::Bf16(wk),
5742 ResidentBf16Weight::Bf16(wv),
5743 ) => {
5744 let wg = match &gate_shards {
5745 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5746 Some(StepTpGateShards::F32(_)) => {
5747 return Err(
5748 "tcol verify: gate shard class does not match bf16 QKV".into()
5749 );
5750 }
5751 None => wq,
5752 };
5753 let StepTpDecodeV2Ws {
5754 tcol_q,
5755 tcol_k,
5756 tcol_v,
5757 tcol_g,
5758 tcol_in,
5759 local_q_dim,
5760 local_kv_dim,
5761 w8t_aq,
5762 w8t_ad,
5763 w8t_in,
5764 w8t_cap,
5765 ..
5766 } = &mut *ws;
5767 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5770 let refk = *REFK
5771 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5772 if refk {
5773 let lq = *local_q_dim;
5774 let lkv = *local_kv_dim;
5775 let mut hrow = engine.uninit(in_f)?;
5776 let mut qr = engine.uninit(lq)?;
5777 let mut kr = engine.uninit(lkv)?;
5778 let mut vr = engine.uninit(lkv)?;
5779 let mut gr = engine.uninit(out_g.max(1))?;
5780 for c in 0..t {
5781 {
5782 let mut dst = hrow.slice_mut(0..in_f);
5783 engine.stream().memcpy_dtod(
5784 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5785 &mut dst,
5786 )?;
5787 }
5788 engine.matvec_bf16_qkvg_into(
5789 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5790 lq, lkv, out_g,
5791 )?;
5792 let stream = engine.stream();
5793 {
5794 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5795 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5796 }
5797 {
5798 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5799 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5800 }
5801 {
5802 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5803 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5804 }
5805 if out_g > 0 {
5806 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5807 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5808 }
5809 }
5810 } else if crate::step_tp_w8_on()
5811 && q_m.ranks[rank].q8.is_some()
5812 && k_m.ranks[rank].q8.is_some()
5813 && v_m.ranks[rank].q8.is_some()
5814 && in_f % 32 == 0
5815 {
5816 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
5822 w8t_aq.clear();
5823 w8t_ad.clear();
5824 for e_rank in &self.ranks {
5825 let _m = e_rank.gpu.enter_main()?;
5826 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
5827 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
5828 }
5829 *w8t_in = in_f;
5830 *w8t_cap = 32;
5831 }
5832 engine.quantize_q8_1_into(
5833 &tcol_in[rank],
5834 t,
5835 in_f,
5836 &mut w8t_aq[rank],
5837 &mut w8t_ad[rank],
5838 )?;
5839 engine.qmatvec_q8_0_qkv_rp_t_into(
5840 q_m.ranks[rank].q8.as_ref().unwrap(),
5841 k_m.ranks[rank].q8.as_ref().unwrap(),
5842 v_m.ranks[rank].q8.as_ref().unwrap(),
5843 &w8t_aq[rank],
5844 &w8t_ad[rank],
5845 &mut tcol_q[rank],
5846 &mut tcol_k[rank],
5847 &mut tcol_v[rank],
5848 in_f,
5849 *local_q_dim,
5850 *local_kv_dim,
5851 t,
5852 )?;
5853 if out_g > 0 {
5854 engine.matvec_bf16_rows_into(
5855 wg,
5856 &tcol_in[rank],
5857 &mut tcol_g[rank],
5858 in_f,
5859 out_g,
5860 t,
5861 )?;
5862 }
5863 } else {
5864 engine.matvec_bf16_qkvg_tcol_into(
5865 wq,
5866 wk,
5867 wv,
5868 wg,
5869 &tcol_in[rank],
5870 &mut tcol_q[rank],
5871 &mut tcol_k[rank],
5872 &mut tcol_v[rank],
5873 &mut tcol_g[rank],
5874 in_f,
5875 *local_q_dim,
5876 *local_kv_dim,
5877 out_g,
5878 t,
5879 )?;
5880 }
5881 }
5882 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5883 }
5884 }
5885 Ok(())
5886 }
5887
5888 pub(crate) fn decode_v2_oproj_tcol_eligible(
5892 &self,
5893 ws: &StepTpDecodeV2Ws,
5894 o_m: &ResidentStepBf16RowParallel,
5895 ) -> bool {
5896 self.ranks.len() == 2
5897 && ws.blocks_per_rank == 4
5898 && step_tp_qkv_fused_enabled().unwrap_or(false)
5899 && no_local_shadow_on()
5900 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5901 && o_m
5902 .ranks
5903 .iter()
5904 .flatten()
5905 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5906 }
5907
5908 pub(crate) fn decode_v2_stash_fa2(
5913 &self,
5914 ws: &mut StepTpDecodeV2Ws,
5915 e: &Engine,
5916 col: usize,
5917 ) -> Result<(), Box<dyn std::error::Error>> {
5918 let ranks = self.ranks.len();
5919 if col >= 32 {
5920 return Err("decode_v2_stash_fa2 column out of range".into());
5921 }
5922 let lq = ws.local_q_dim;
5923 let lg = (ws.heads / ranks).max(1);
5924 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
5925 ws.fa2_q.clear();
5926 ws.fa2_gate.clear();
5927 ws.fa2_gated.clear();
5928 ws.rope_k_t.clear();
5929 ws.rope_ctr_t.clear();
5930 ws.rope_pos_t.clear();
5931 ws.rows_tab_t.clear();
5932 for engine in &self.ranks {
5933 let _m = engine.gpu.enter_main()?;
5934 ws.fa2_q.push(engine.uninit(32 * lq)?);
5935 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5936 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5937 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
5938 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5939 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5940 ws.rows_tab_t
5941 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
5942 }
5943 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5944 ws.fa2_cap = 32;
5945 }
5946 for rank in 0..ranks {
5947 let engine = &self.ranks[rank];
5948 let _main = engine.gpu.enter_main()?;
5949 {
5950 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5951 engine
5952 .stream()
5953 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5954 }
5955 {
5956 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5957 engine
5958 .stream()
5959 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5960 }
5961 ws.ev_rank[rank].record(&engine.stream())?;
5962 }
5963 {
5964 let _main = e.gpu.enter_main()?;
5965 for ev in ws.ev_rank.iter() {
5966 e.stream().wait(ev)?;
5967 }
5968 }
5969 Ok(())
5970 }
5971
5972 #[allow(clippy::too_many_arguments)]
5979 pub(crate) fn decode_v2_spec_fa2_join(
5980 &self,
5981 ws_index: usize,
5982 e: &Engine,
5983 o_m: &ResidentStepBf16RowParallel,
5984 kv: &ResidentTpKvCache,
5985 head_dim: usize,
5986 window: usize,
5987 bucket_max: usize,
5988 scale: f32,
5989 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5990 let ranks = self.ranks.len();
5991 static ONCE: std::sync::Once = std::sync::Once::new();
5993 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
5994 {
5995 let mut guard = self
5996 .decode_v2
5997 .lock()
5998 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5999 let ws = guard
6000 .get_mut(ws_index)
6001 .ok_or("step TP decode v2 workspace index out of range")?;
6002 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6003 return Err("spec fa2 join without stashed columns".into());
6004 }
6005 let lq = ws.local_q_dim;
6006 let local_heads = (ws.heads / ranks).max(1);
6007 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6008 let capacity = kv.physical_capacity();
6009 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6010 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6012 ws.tcol_gated.clear();
6013 ws.tcol_opart.clear();
6014 for engine in &self.ranks {
6015 let _m = engine.gpu.enter_main()?;
6016 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6017 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6018 }
6019 let root = &self.ranks[0];
6020 let _m = root.gpu.enter_main()?;
6021 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6022 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6023 ws.tcol_ocap = 32;
6024 }
6025 for rank in 0..ranks {
6026 let engine = &self.ranks[rank];
6027 let _main = engine.gpu.enter_main()?;
6028 let rank_cache = kv
6029 .rank(rank)
6030 .ok_or("spec fa2 join lost its KV cache rank")?;
6031 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6032 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6033 {
6034 let StepTpDecodeV2Ws {
6035 fa2_q,
6036 fa2_gate,
6037 fa2_gated,
6038 ..
6039 } = &mut *ws;
6040 engine.fa_decode_dcw2(
6041 &fa2_q[rank],
6042 &k_ring,
6043 &v_ring,
6044 &mut fa2_gated[rank],
6045 head_dim,
6046 local_heads,
6047 local_kv_heads,
6048 rank_cache.len_d(),
6049 rank_cache.base_d(),
6050 window,
6051 bucket_max,
6052 scale,
6053 k_tok_bytes,
6054 v_tok_bytes,
6055 &fa2_gate[rank],
6056 )?;
6057 }
6058 let StepTpDecodeV2Ws {
6061 fa2_gated,
6062 tcol_gated,
6063 ..
6064 } = &mut *ws;
6065 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6066 engine
6067 .stream()
6068 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6069 }
6070 }
6071 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6072 }
6073
6074 #[allow(clippy::too_many_arguments)]
6084 pub(crate) fn decode_v2_rope_fa_rows(
6085 &self,
6086 ws_index: usize,
6087 e: &Engine,
6088 o_m: &ResidentStepBf16RowParallel,
6089 session_parts: &[Vec<[u64; 4]>],
6090 tab_keys: &[u64],
6091 positions: &[i32],
6092 stage_pos: bool,
6093 same_session: bool,
6094 q_norms: &[CudaSlice<f32>],
6095 k_norms: &[CudaSlice<f32>],
6096 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6097 t: usize,
6098 head_dim: usize,
6099 n_rot: usize,
6100 window: usize,
6101 max_ns: usize,
6102 scale: f32,
6103 k_tok_bytes: usize,
6104 v_tok_bytes: usize,
6105 eps: f32,
6106 rope_base: f32,
6107 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6108 use cudarc::driver::DevicePtr;
6109 let ranks = self.ranks.len();
6110 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6111 return Err("rope fa rows geometry".into());
6112 }
6113 {
6114 let mut guard = self
6115 .decode_v2
6116 .lock()
6117 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6118 let ws = guard
6119 .get_mut(ws_index)
6120 .ok_or("step TP decode v2 workspace index out of range")?;
6121 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6122 return Err("rope fa rows without tcol slabs".into());
6123 }
6124 let lq = ws.local_q_dim;
6125 let lkv = ws.local_kv_dim;
6126 let lg = (ws.heads / ranks).max(1);
6127 let local_heads = (ws.heads / ranks).max(1);
6128 let local_kv_heads = (lkv / head_dim).max(1);
6129 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6131 ws.fa2_q.clear();
6132 ws.fa2_gate.clear();
6133 ws.fa2_gated.clear();
6134 ws.rope_k_t.clear();
6135 ws.rope_ctr_t.clear();
6136 ws.rope_pos_t.clear();
6137 ws.rows_tab_t.clear();
6138 for engine in &self.ranks {
6139 let _m = engine.gpu.enter_main()?;
6140 ws.fa2_q.push(engine.uninit(32 * lq)?);
6141 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6142 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6143 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6144 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6145 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6146 ws.rows_tab_t
6147 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6148 }
6149 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6150 ws.fa2_cap = 32;
6151 }
6152 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6153 ws.tcol_gated.clear();
6154 ws.tcol_opart.clear();
6155 for engine in &self.ranks {
6156 let _m = engine.gpu.enter_main()?;
6157 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6158 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6159 }
6160 let root = &self.ranks[0];
6161 let _m = root.gpu.enter_main()?;
6162 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6163 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6164 ws.tcol_ocap = 32;
6165 }
6166 for rank in 0..ranks {
6167 let engine = &self.ranks[rank];
6168 let _main = engine.gpu.enter_main()?;
6169 if stage_pos {
6170 let host: Vec<i32> = positions[..t].to_vec();
6171 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6172 engine.stream().memcpy_htod(&host, &mut view)?;
6173 }
6174 let ctr_base = {
6193 let s = engine.stream();
6194 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6195 p as u64
6196 };
6197 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6198 if rows_tab_stale_scan() {
6204 if ws.rows_tab_shadow.len() != ranks {
6205 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6206 }
6207 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6208 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank]) {
6209 if prev != &host {
6210 let words = ["k", "v", "len", "base", "ctr", "back"];
6211 let moved: Vec<String> = (0..host.len())
6212 .filter(|&i| prev.get(i) != Some(&host[i]))
6213 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6214 .collect();
6215 let stale =
6216 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6217 eprintln!(
6218 "[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",
6219 tab_keys[rank],
6220 moved.join(",")
6221 );
6222 }
6223 }
6224 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
6225 }
6226 let legacy_memo = !rows_tab_restage_on();
6227 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6228 let tab = engine.stream().clone_htod(&host)?;
6229 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6230 }
6231 if !legacy_memo {
6232 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
6233 engine.stream().memcpy_htod(&host, &mut view)?;
6234 }
6235 let StepTpDecodeV2Ws {
6236 tcol_q,
6237 tcol_k,
6238 tcol_v,
6239 tcol_g,
6240 fa2_q,
6241 fa2_gated,
6242 rope_k_t,
6243 rope_pos_t,
6244 rows_tabs,
6245 rows_tab_t,
6246 ..
6247 } = &mut *ws;
6248 let tab = if legacy_memo {
6249 rows_tabs[rank]
6250 .get(&tab_keys[rank])
6251 .ok_or("rows tab memo lost its entry")?
6252 } else {
6253 &rows_tab_t[rank]
6254 };
6255 engine.qk_norm_rope_append_inc_dcw_rows(
6256 &tcol_q[rank],
6257 &tcol_k[rank],
6258 &tcol_v[rank],
6259 &q_norms[rank],
6260 &k_norms[rank],
6261 &mut fa2_q[rank],
6262 &mut rope_k_t[rank],
6263 tab,
6264 &rope_pos_t[rank],
6265 same_session,
6266 t,
6267 lkv,
6268 lkv,
6269 k_tok_bytes,
6270 v_tok_bytes,
6271 head_dim,
6272 n_rot,
6273 local_heads,
6274 local_kv_heads,
6275 eps,
6276 rope_base,
6277 1.0,
6278 rope_freqs[rank],
6279 )?;
6280 engine.fa_decode_dcw_rows(
6281 &fa2_q[rank],
6282 tab,
6283 &mut fa2_gated[rank],
6284 t,
6285 head_dim,
6286 local_heads,
6287 local_kv_heads,
6288 window,
6289 max_ns,
6290 scale,
6291 k_tok_bytes,
6292 v_tok_bytes,
6293 &tcol_g[rank],
6294 )?;
6295 let StepTpDecodeV2Ws {
6296 fa2_gated,
6297 tcol_gated,
6298 ..
6299 } = &mut *ws;
6300 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6301 engine
6302 .stream()
6303 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6304 }
6305 }
6306 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6307 }
6308
6309 #[allow(clippy::too_many_arguments)]
6316 pub(crate) fn decode_v2_fa_rows_join(
6317 &self,
6318 ws_index: usize,
6319 e: &Engine,
6320 o_m: &ResidentStepBf16RowParallel,
6321 tabs: &[&crate::CudaSlice<u64>],
6322 t: usize,
6323 head_dim: usize,
6324 window: usize,
6325 max_ns: usize,
6326 scale: f32,
6327 k_tok_bytes: usize,
6328 v_tok_bytes: usize,
6329 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6330 let ranks = self.ranks.len();
6331 if tabs.len() != ranks {
6332 return Err("fa rows join needs one table per rank".into());
6333 }
6334 {
6335 let mut guard = self
6336 .decode_v2
6337 .lock()
6338 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6339 let ws = guard
6340 .get_mut(ws_index)
6341 .ok_or("step TP decode v2 workspace index out of range")?;
6342 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6343 return Err("fa rows join without stashed rows".into());
6344 }
6345 let lq = ws.local_q_dim;
6346 let local_heads = (ws.heads / ranks).max(1);
6347 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6348 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6349 ws.tcol_gated.clear();
6350 ws.tcol_opart.clear();
6351 for engine in &self.ranks {
6352 let _m = engine.gpu.enter_main()?;
6353 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6354 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6355 }
6356 let root = &self.ranks[0];
6357 let _m = root.gpu.enter_main()?;
6358 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6359 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6360 ws.tcol_ocap = 32;
6361 }
6362 for rank in 0..ranks {
6363 let engine = &self.ranks[rank];
6364 let _main = engine.gpu.enter_main()?;
6365 {
6366 let StepTpDecodeV2Ws {
6367 fa2_q,
6368 fa2_gate,
6369 fa2_gated,
6370 ..
6371 } = &mut *ws;
6372 engine.fa_decode_dcw_rows(
6373 &fa2_q[rank],
6374 tabs[rank],
6375 &mut fa2_gated[rank],
6376 t,
6377 head_dim,
6378 local_heads,
6379 local_kv_heads,
6380 window,
6381 max_ns,
6382 scale,
6383 k_tok_bytes,
6384 v_tok_bytes,
6385 &fa2_gate[rank],
6386 )?;
6387 }
6388 let StepTpDecodeV2Ws {
6389 fa2_gated,
6390 tcol_gated,
6391 ..
6392 } = &mut *ws;
6393 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6394 engine
6395 .stream()
6396 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6397 }
6398 }
6399 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6400 }
6401
6402 pub(crate) fn decode_v2_stash_gated(
6407 &self,
6408 ws: &mut StepTpDecodeV2Ws,
6409 e: &Engine,
6410 col: usize,
6411 ) -> Result<(), Box<dyn std::error::Error>> {
6412 let ranks = self.ranks.len();
6413 if col >= 32 {
6417 return Err("decode_v2_stash_gated column out of range".into());
6418 }
6419 let lq = ws.local_q_dim;
6420 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6421 ws.tcol_gated.clear();
6422 ws.tcol_opart.clear();
6423 for engine in &self.ranks {
6424 let _m = engine.gpu.enter_main()?;
6425 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6426 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6427 }
6428 let root = &self.ranks[0];
6429 let _m = root.gpu.enter_main()?;
6430 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6431 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6432 ws.tcol_ocap = 32;
6433 }
6434 for rank in 0..ranks {
6435 let engine = &self.ranks[rank];
6436 let _main = engine.gpu.enter_main()?;
6437 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6438 engine
6439 .stream()
6440 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6441 ws.ev_rank[rank].record(&engine.stream())?;
6445 }
6446 {
6447 let _main = e.gpu.enter_main()?;
6448 for ev in ws.ev_rank.iter() {
6449 e.stream().wait(ev)?;
6450 }
6451 }
6452 Ok(())
6453 }
6454
6455 pub(crate) fn decode_v2_oproj_tcol(
6461 &self,
6462 ws_index: usize,
6463 e: &Engine,
6464 o_m: &ResidentStepBf16RowParallel,
6465 t: usize,
6466 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6467 let ranks = self.ranks.len();
6468 let mut guard = self
6469 .decode_v2
6470 .lock()
6471 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6472 let ws = guard
6473 .get_mut(ws_index)
6474 .ok_or("step TP decode v2 workspace index out of range")?;
6475 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6476 return Err("decode_v2_oproj_tcol geometry".into());
6477 }
6478 for rank in 0..ranks {
6479 let engine = &self.ranks[rank];
6480 let _main = engine.gpu.enter_main()?;
6481 let mut weights = Vec::with_capacity(4);
6482 for block in 0..4 {
6483 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6484 return Err("tcol o_proj requires bf16-resident O blocks".into());
6485 };
6486 weights.push(weight);
6487 }
6488 {
6489 let StepTpDecodeV2Ws {
6490 tcol_gated,
6491 tcol_opart,
6492 local_q_dim,
6493 o_block_cols,
6494 o_out,
6495 w8t_oaq,
6496 w8t_oad,
6497 w8t_oin,
6498 w8t_cap,
6499 ..
6500 } = &mut *ws;
6501 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6504 let refk = *REFK
6505 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6506 if refk {
6507 let lq = *local_q_dim;
6508 let mut xr = engine.uninit(lq)?;
6509 let mut yr = engine.uninit(*o_out)?;
6510 for c in 0..t {
6511 {
6512 let mut dst = xr.slice_mut(0..lq);
6513 engine.stream().memcpy_dtod(
6514 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6515 &mut dst,
6516 )?;
6517 }
6518 engine.matvec_bf16_b4_into(
6519 [weights[0], weights[1], weights[2], weights[3]],
6520 &xr,
6521 &mut yr,
6522 *o_block_cols,
6523 *o_out,
6524 )?;
6525 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6526 engine
6527 .stream()
6528 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6529 }
6530 } else if crate::step_tp_w8_on()
6531 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
6532 && (4 * *o_block_cols) % 32 == 0
6533 {
6534 let in_f = 4 * *o_block_cols;
6538 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
6539 w8t_oaq.clear();
6540 w8t_oad.clear();
6541 for e_rank in &self.ranks {
6542 let _m = e_rank.gpu.enter_main()?;
6543 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6544 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6545 }
6546 *w8t_oin = in_f;
6547 *w8t_cap = (*w8t_cap).max(32);
6548 }
6549 engine.quantize_q8_1_into(
6550 &tcol_gated[rank],
6551 t,
6552 in_f,
6553 &mut w8t_oaq[rank],
6554 &mut w8t_oad[rank],
6555 )?;
6556 engine.qmatvec_q8_0_b4_rp_t_into(
6557 [
6558 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6559 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6560 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6561 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6562 ],
6563 &w8t_oaq[rank],
6564 &w8t_oad[rank],
6565 &mut tcol_opart[rank],
6566 *o_block_cols,
6567 *o_out,
6568 t,
6569 )?;
6570 } else {
6571 engine.matvec_bf16_b4_tcol_into(
6572 [weights[0], weights[1], weights[2], weights[3]],
6573 &tcol_gated[rank],
6574 &mut tcol_opart[rank],
6575 *o_block_cols,
6576 *o_out,
6577 t,
6578 )?;
6579 }
6580 }
6581 if rank != 0 {
6582 ws.ev_rank[rank].record(&engine.stream())?;
6583 }
6584 }
6585 let root = &self.ranks[0];
6586 {
6587 let _main = root.gpu.enter_main()?;
6588 for ev in ws.ev_rank.iter().skip(1) {
6589 root.stream().wait(ev)?;
6590 }
6591 {
6592 let StepTpDecodeV2Ws {
6593 tcol_opart,
6594 tcol_opeer,
6595 tcol_omix,
6596 o_out,
6597 ..
6598 } = &mut *ws;
6599 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6600 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6601 {
6602 let mut dst = opeer.slice_mut(0..t * *o_out);
6603 root.stream()
6604 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6605 }
6606 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6609 }
6610 ws.ev_oproj.record(&root.stream())?;
6611 }
6612 let _main = e.gpu.enter_main()?;
6613 e.stream().wait(&ws.ev_oproj)?;
6614 let mut out = e.uninit(t * ws.o_out)?;
6615 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6616 e.stream().memcpy_dtod(
6617 &omix.slice(0..t * ws.o_out),
6618 &mut out.slice_mut(0..t * ws.o_out),
6619 )?;
6620 Ok(out)
6621 }
6622
6623 pub(crate) fn decode_v2_input_qkv(
6624 &self,
6625 ws: &mut StepTpDecodeV2Ws,
6626 e: &Engine,
6627 h: &CudaSlice<f32>,
6628 pos_d: &CudaSlice<i32>,
6629 gate_raw: Option<&CudaSlice<f32>>,
6630 gate_shards: Option<StepTpGateShards<'_>>,
6631 decode_input: &mut ResidentReplicatedDeviceRows,
6632 q_m: &ResidentBf16ColumnParallel,
6633 k_m: &ResidentBf16ColumnParallel,
6634 v_m: &ResidentBf16ColumnParallel,
6635 q_norm: &[CudaSlice<f32>],
6636 k_norm: &[CudaSlice<f32>],
6637 head_dim: usize,
6638 n_rot: usize,
6639 rope_base: f32,
6640 rope_freqs: &[Option<&CudaSlice<f32>>],
6641 rms_eps: f32,
6642 defer_norm_rope: bool,
6643 tcol_col: Option<usize>,
6644 ) -> Result<(), Box<dyn std::error::Error>> {
6645 let ranks = self.ranks.len();
6646 validate_replicated_device_rows(&self.ranks, decode_input)?;
6647 if decode_input.tokens != 1
6648 || decode_input.width != q_m.in_features
6649 || pos_d.len() != 1
6650 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6651 || gate_raw.is_none() != gate_shards.is_some()
6652 || gate_shards.as_ref().is_some_and(|shards| match shards {
6653 StepTpGateShards::F32(shards) => shards.len() != ranks,
6654 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6655 })
6656 || q_norm.len() != ranks
6657 || k_norm.len() != ranks
6658 || rope_freqs.len() != ranks
6659 || e.ctx().ordinal() != ws.e_device
6660 {
6661 return Err("step TP decode v2 input geometry mismatch".into());
6662 }
6663
6664 let qkv_fused = step_tp_qkv_fused_enabled()?;
6665 if gate_shards.is_some() && !qkv_fused {
6666 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6667 }
6668 let values = decode_input.width;
6669 if h.len() != values {
6670 return Err(format!(
6671 "step TP decode v2 hidden width {} != replicated width {values}",
6672 h.len()
6673 )
6674 .into());
6675 }
6676
6677 if qkv_fused {
6678 if ws.h_stage.is_none() {
6682 use cudarc::driver::DevicePtr;
6683 let _main = e.gpu.enter_main()?;
6684 let h_stage = e.uninit(values)?;
6685 let pos_stage = e.htod_i32(&[0])?;
6686 {
6687 let stream = e.stream();
6688 let (hp, _g0) = h_stage.device_ptr(&stream);
6689 let (pp, _g1) = pos_stage.device_ptr(&stream);
6690 ws.raw_h_stage = hp as u64;
6691 ws.raw_pos_stage = pp as u64;
6692 }
6693 ws.h_stage = Some(h_stage);
6694 ws.pos_stage = Some(pos_stage);
6695 for rank in 0..ranks {
6696 use cudarc::driver::DevicePtr;
6697 let engine = &self.ranks[rank];
6698 let _rmain = engine.gpu.enter_main()?;
6699 let attn_in = engine.uninit(values)?;
6700 let (dp, pp) = {
6701 let stream = engine.stream();
6702 let (dp, _g2) = attn_in.device_ptr(&stream);
6703 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6704 (dp as u64, pp as u64)
6705 };
6706 ws.raw_attn_in.push(dp);
6707 ws.raw_pos.push(pp);
6708 ws.attn_in.push(attn_in);
6709 }
6710 {
6711 use cudarc::driver::DevicePtr;
6712 let root = &self.ranks[0];
6713 let _rmain = root.gpu.enter_main()?;
6714 let stream = root.stream();
6715 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6716 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6717 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6718 ws.raw_peer_partial = a as u64;
6719 ws.raw_k_shadow = b as u64;
6720 ws.raw_v_shadow = c as u64;
6721 }
6722 {
6723 use cudarc::driver::DevicePtr;
6724 let rank1 = &self.ranks[1];
6725 let _rmain = rank1.gpu.enter_main()?;
6726 let stream = rank1.stream();
6727 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6728 let (b, _g) = ws.k[1].device_ptr(&stream);
6729 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6730 ws.raw_o_partial1 = a as u64;
6731 ws.raw_k1 = b as u64;
6732 ws.raw_v1 = c as u64;
6733 }
6734 }
6735 {
6736 let _main = e.gpu.enter_main()?;
6737 {
6738 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6741 let mut dst = h_stage.slice_mut(0..values);
6742 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6743 }
6744 {
6745 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6746 let mut dst = pos_stage.slice_mut(0..1);
6747 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6748 }
6749 ws.ev_entry.record(&e.stream())?;
6750 }
6751 for rank in 0..ranks {
6752 let engine = &self.ranks[rank];
6753 let _main = engine.gpu.enter_main()?;
6754 engine.stream().wait(&ws.ev_entry)?;
6755 }
6756 } else {
6757 {
6759 let _main = e.gpu.enter_main()?;
6760 if let Some(gate_raw) = gate_raw {
6761 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6762 e.stream()
6763 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6764 }
6765 ws.ev_entry.record(&e.stream())?;
6766 }
6767 {
6768 let root = &self.ranks[0];
6769 let _main = root.gpu.enter_main()?;
6770 root.stream().wait(&ws.ev_entry)?;
6771 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6772 root.stream()
6773 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6774 ws.ev_refresh.record(&root.stream())?;
6775 }
6776 for rank in 1..ranks {
6777 let engine = &self.ranks[rank];
6778 let _main = engine.gpu.enter_main()?;
6779 engine.stream().wait(&ws.ev_refresh)?;
6780 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6781 let mut destination = peer_rows[0].slice_mut(0..values);
6782 engine
6783 .stream()
6784 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6785 }
6786 }
6787 for rank in 0..ranks {
6788 self.decode_v2_input_qkv_rank(
6789 ws,
6790 pos_d,
6791 decode_input,
6792 q_m,
6793 k_m,
6794 v_m,
6795 q_norm,
6796 k_norm,
6797 head_dim,
6798 n_rot,
6799 rope_base,
6800 rope_freqs,
6801 rms_eps,
6802 gate_shards.as_ref(),
6803 qkv_fused,
6804 defer_norm_rope,
6805 rank,
6806 tcol_col,
6807 )?;
6808 }
6809 Ok(())
6810 }
6811
6812 #[allow(clippy::too_many_arguments)]
6815 pub(crate) fn decode_v2_input_qkv_rank(
6816 &self,
6817 ws: &mut StepTpDecodeV2Ws,
6818 pos_d: &CudaSlice<i32>,
6819 decode_input: &mut ResidentReplicatedDeviceRows,
6820 q_m: &ResidentBf16ColumnParallel,
6821 k_m: &ResidentBf16ColumnParallel,
6822 v_m: &ResidentBf16ColumnParallel,
6823 q_norm: &[CudaSlice<f32>],
6824 k_norm: &[CudaSlice<f32>],
6825 head_dim: usize,
6826 n_rot: usize,
6827 rope_base: f32,
6828 rope_freqs: &[Option<&CudaSlice<f32>>],
6829 rms_eps: f32,
6830 gate_shards: Option<&StepTpGateShards<'_>>,
6831 qkv_fused: bool,
6832 defer_norm_rope: bool,
6833 rank: usize,
6834 tcol_col: Option<usize>,
6835 ) -> Result<(), Box<dyn std::error::Error>> {
6836 let ranks = self.ranks.len();
6837 let local_heads = ws.local_q_dim / head_dim;
6838 let local_kv_heads = ws.local_kv_dim / head_dim;
6839 let engine = &self.ranks[rank];
6840 let _main = engine.gpu.enter_main()?;
6841 let ws_e_device = ws.e_device;
6842 if qkv_fused && tcol_col.is_some() {
6847 let c = tcol_col.expect("checked");
6848 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6849 return Err("tcol select without precompute".into());
6850 }
6851 if engine.ctx().ordinal() != ws_e_device {
6855 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6856 }
6857 let StepTpDecodeV2Ws {
6858 tcol_q,
6859 tcol_k,
6860 tcol_v,
6861 tcol_g,
6862 q_raw,
6863 k_raw,
6864 v_raw,
6865 gate,
6866 local_q_dim,
6867 local_kv_dim,
6868 heads,
6869 ..
6870 } = &mut *ws;
6871 let lg = *heads / ranks;
6872 let stream = engine.stream();
6873 {
6874 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6875 stream.memcpy_dtod(
6876 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6877 &mut dst,
6878 )?;
6879 }
6880 {
6881 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6882 stream.memcpy_dtod(
6883 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6884 &mut dst,
6885 )?;
6886 }
6887 {
6888 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6889 stream.memcpy_dtod(
6890 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6891 &mut dst,
6892 )?;
6893 }
6894 if lg > 0 {
6895 let mut dst = gate[rank].slice_mut(0..lg);
6896 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6897 }
6898 if !defer_norm_rope {
6899 } else {
6903 return Ok(());
6904 }
6905 }
6906 if qkv_fused {
6907 let same_dev = engine.ctx().ordinal() == ws.e_device;
6912 if !same_dev {
6913 raw_copy_bytes(
6914 ws.raw_attn_in[rank],
6915 ws.raw_h_stage,
6916 q_m.in_features * 4,
6917 engine,
6918 )?;
6919 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6920 }
6921 let StepTpDecodeV2Ws {
6922 q_raw,
6923 k_raw,
6924 v_raw,
6925 gate,
6926 gate_e,
6927 attn_in,
6928 h_stage,
6929 heads,
6930 local_q_dim,
6931 local_kv_dim,
6932 w8_aq,
6933 w8_ad,
6934 w8_in,
6935 ..
6936 } = &mut *ws;
6937 let input_ref: &CudaSlice<f32> = if same_dev {
6938 h_stage
6939 .as_ref()
6940 .ok_or("step TP decode v2 stage not armed")?
6941 } else {
6942 &attn_in[rank]
6943 };
6944 match (
6945 &q_m.ranks[rank].weight,
6946 &k_m.ranks[rank].weight,
6947 &v_m.ranks[rank].weight,
6948 ) {
6949 (
6950 ResidentBf16Weight::F32(wq),
6951 ResidentBf16Weight::F32(wk),
6952 ResidentBf16Weight::F32(wv),
6953 ) => {
6954 let (wg, out_g) = match &gate_shards {
6955 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6956 Some(StepTpGateShards::Bf16(_)) => {
6957 return Err("step TP decode v2 gate shard class does not \
6958 match the F32 projections"
6959 .into());
6960 }
6961 None => (&*gate_e, 0),
6963 };
6964 engine.matvec_f32_qkv_into(
6965 wq,
6966 wk,
6967 wv,
6968 wg,
6969 input_ref,
6970 &mut q_raw[rank],
6971 &mut k_raw[rank],
6972 &mut v_raw[rank],
6973 &mut gate[rank],
6974 q_m.in_features,
6975 *local_q_dim,
6976 *local_kv_dim,
6977 out_g,
6978 )?;
6979 }
6980 (
6981 ResidentBf16Weight::Bf16(wq),
6982 ResidentBf16Weight::Bf16(wk),
6983 ResidentBf16Weight::Bf16(wv),
6984 ) => {
6985 let (wg, out_g) = match &gate_shards {
6986 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6987 Some(StepTpGateShards::F32(_)) => {
6988 return Err("step TP decode v2 gate shard class does not \
6989 match the bf16 projections"
6990 .into());
6991 }
6992 None => (wq, 0),
6993 };
6994 let in_f = q_m.in_features;
7001 let q8_ready = crate::step_tp_w8_on()
7002 && q_m.ranks[rank].q8.is_some()
7003 && k_m.ranks[rank].q8.is_some()
7004 && v_m.ranks[rank].q8.is_some();
7005 if q8_ready {
7006 if *w8_in != in_f || w8_aq.len() != ranks {
7007 w8_aq.clear();
7008 w8_ad.clear();
7009 for e_rank in &self.ranks {
7010 let _m = e_rank.gpu.enter_main()?;
7011 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7012 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7013 }
7014 *w8_in = in_f;
7015 }
7016 engine.quantize_q8_1_into(
7017 input_ref,
7018 1,
7019 in_f,
7020 &mut w8_aq[rank],
7021 &mut w8_ad[rank],
7022 )?;
7023 engine.qmatvec_q8_0_qkv_rp_into(
7028 q_m.ranks[rank].q8.as_ref().unwrap(),
7029 k_m.ranks[rank].q8.as_ref().unwrap(),
7030 v_m.ranks[rank].q8.as_ref().unwrap(),
7031 &w8_aq[rank],
7032 &w8_ad[rank],
7033 &mut q_raw[rank],
7034 &mut k_raw[rank],
7035 &mut v_raw[rank],
7036 in_f,
7037 *local_q_dim,
7038 *local_kv_dim,
7039 )?;
7040 if out_g > 0 {
7041 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7042 }
7043 } else {
7044 engine.matvec_bf16_qkvg_into(
7045 wq,
7046 wk,
7047 wv,
7048 wg,
7049 input_ref,
7050 &mut q_raw[rank],
7051 &mut k_raw[rank],
7052 &mut v_raw[rank],
7053 &mut gate[rank],
7054 q_m.in_features,
7055 *local_q_dim,
7056 *local_kv_dim,
7057 out_g,
7058 )?;
7059 }
7060 }
7061 _ => {
7062 return Err("step TP decode v2 QKV projections mix residency classes".into());
7063 }
7064 }
7065 } else {
7066 for (matrix, local_out, raw) in [
7067 (q_m, ws.local_q_dim, &mut ws.q_raw),
7068 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7069 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7070 ] {
7071 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7072 return Err("step TP decode v2 lost its F32 projection residency".into());
7073 };
7074 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7075 engine.linear_f32_resident_canonical_rows_t1_into(
7076 &decode_input.ranks[rank],
7077 values_w,
7078 &mut raw[rank],
7079 matrix.in_features,
7080 local_out,
7081 chunk_rows,
7082 )?;
7083 }
7084 }
7085 if qkv_fused && defer_norm_rope {
7086 } else if qkv_fused {
7088 let StepTpDecodeV2Ws {
7091 q_raw,
7092 k_raw,
7093 q,
7094 k,
7095 pos,
7096 pos_stage,
7097 ..
7098 } = &mut *ws;
7099 let same_dev = engine.ctx().ordinal() == ws_e_device;
7100 let pos_ref: &CudaSlice<i32> = if same_dev {
7101 pos_stage
7102 .as_ref()
7103 .ok_or("step TP decode v2 pos stage not armed")?
7104 } else {
7105 &pos[rank]
7106 };
7107 engine.qk_norm_rope_into(
7108 &q_raw[rank],
7109 &k_raw[rank],
7110 &q_norm[rank],
7111 &k_norm[rank],
7112 &mut q[rank],
7113 &mut k[rank],
7114 pos_ref,
7115 head_dim,
7116 n_rot,
7117 local_heads,
7118 local_kv_heads,
7119 rms_eps,
7120 rope_base,
7121 1.0,
7122 rope_freqs[rank],
7123 )?;
7124 } else {
7125 engine.rms_norm(
7126 &ws.q_raw[rank],
7127 &q_norm[rank],
7128 &mut ws.q[rank],
7129 head_dim,
7130 local_heads,
7131 rms_eps,
7132 )?;
7133 engine.rms_norm(
7134 &ws.k_raw[rank],
7135 &k_norm[rank],
7136 &mut ws.k[rank],
7137 head_dim,
7138 local_kv_heads,
7139 rms_eps,
7140 )?;
7141 {
7142 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7143 engine
7144 .stream()
7145 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7146 }
7147 engine.rope_neox2(
7148 &mut ws.q[rank],
7149 &mut ws.k[rank],
7150 &ws.pos[rank],
7151 head_dim,
7152 n_rot,
7153 local_heads,
7154 local_kv_heads,
7155 1,
7156 rope_base,
7157 1.0,
7158 rope_freqs[rank],
7159 )?;
7160 }
7161 if gate_shards.is_none() {
7162 let gate_start = rank * (ws.heads / ranks);
7163 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7164 engine.stream().memcpy_dtod(
7165 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7166 &mut gate_dst,
7167 )?;
7168 }
7169 Ok(())
7170 }
7171
7172 pub(crate) fn decode_v2_finish_rank_partial(
7176 &self,
7177 ws: &mut StepTpDecodeV2Ws,
7178 o_m: &ResidentStepBf16RowParallel,
7179 o_fused: bool,
7180 rank: usize,
7181 ) -> Result<(), Box<dyn std::error::Error>> {
7182 let engine = &self.ranks[rank];
7183 let _main = engine.gpu.enter_main()?;
7184 if o_fused {
7185 let StepTpDecodeV2Ws {
7186 gated,
7187 o_partials,
7188 o_block_cols,
7189 o_out,
7190 w8o_aq,
7191 w8o_ad,
7192 w8o_in,
7193 ..
7194 } = &mut *ws;
7195 let all_f32 = o_m.ranks[rank]
7196 .iter()
7197 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7198 if all_f32 {
7199 let mut weights = Vec::with_capacity(4);
7200 for block in 0..4 {
7201 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7202 unreachable!("all_f32 checked above");
7203 };
7204 weights.push(weight);
7205 }
7206 engine.matvec_f32_b4_into(
7207 [weights[0], weights[1], weights[2], weights[3]],
7208 &gated[rank],
7209 &mut o_partials[rank][0],
7210 *o_block_cols,
7211 *o_out,
7212 )?;
7213 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
7214 let in_f = 4 * *o_block_cols;
7219 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
7220 w8o_aq.clear();
7221 w8o_ad.clear();
7222 for e_rank in &self.ranks {
7223 let _m = e_rank.gpu.enter_main()?;
7224 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7225 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7226 }
7227 *w8o_in = in_f;
7228 }
7229 engine.quantize_q8_1_into(
7230 &gated[rank],
7231 1,
7232 in_f,
7233 &mut w8o_aq[rank],
7234 &mut w8o_ad[rank],
7235 )?;
7236 engine.qmatvec_q8_0_b4_rp_into(
7237 [
7238 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7239 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7240 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7241 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7242 ],
7243 &w8o_aq[rank],
7244 &w8o_ad[rank],
7245 &mut o_partials[rank][0],
7246 *o_block_cols,
7247 *o_out,
7248 )?;
7249 } else {
7250 let mut weights = Vec::with_capacity(4);
7251 for block in 0..4 {
7252 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7253 return Err("step TP decode v2 O projections mix residency classes".into());
7254 };
7255 weights.push(weight);
7256 }
7257 engine.matvec_bf16_b4_into(
7258 [weights[0], weights[1], weights[2], weights[3]],
7259 &gated[rank],
7260 &mut o_partials[rank][0],
7261 *o_block_cols,
7262 *o_out,
7263 )?;
7264 }
7265 } else {
7266 for block in 0..ws.blocks_per_rank {
7267 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7268 return Err("step TP decode v2 lost its F32 O residency".into());
7269 };
7270 let x =
7271 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7272 let w = weight.slice(0..weight.len());
7273 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7274 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7275 }
7276 }
7277 Ok(())
7278 }
7279
7280 pub(crate) fn decode_v2_finish(
7288 &self,
7289 ws: &mut StepTpDecodeV2Ws,
7290 e: &Engine,
7291 o_m: &ResidentStepBf16RowParallel,
7292 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7293 let ranks = self.ranks.len();
7294 if e.ctx().ordinal() != ws.e_device {
7295 return Err("step TP decode v2 finish engine changed".into());
7296 }
7297 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7302
7303 for rank in 0..ranks {
7306 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7307 if rank == 0 {
7308 continue;
7311 }
7312 let engine = &self.ranks[rank];
7313 let _main = engine.gpu.enter_main()?;
7314 ws.ev_rank[rank].record(&engine.stream())?;
7315 }
7316
7317 let root = &self.ranks[0];
7319 #[allow(unused_assignments)]
7320 let mut final_in_a = false;
7321 {
7322 let _main = root.gpu.enter_main()?;
7323 for ev in ws.ev_rank.iter().skip(1) {
7324 root.stream().wait(ev)?;
7325 }
7326 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7327 ws.ev_oproj.record(&root.stream())?;
7333 let _main = e.gpu.enter_main()?;
7334 e.stream().wait(&ws.ev_oproj)?;
7335 let mut output = e.uninit(ws.o_out)?;
7336 if oproj_tail_on() && oproj_tail_eligible() {
7337 use cudarc::driver::DevicePtr;
7340 let stream = e.stream();
7341 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7342 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7343 set_oproj_tail((p0 as u64, p1 as u64));
7344 return Ok(output);
7345 }
7346 e.add(
7347 &ws.o_partials[0][0],
7348 &ws.o_partials[1][0],
7349 &mut output,
7350 ws.o_out,
7351 )?;
7352 return Ok(output);
7353 }
7354 if o_fused {
7355 self.decode_v2_finish_root_fused(ws)?;
7356 ws.ev_oproj.record(&root.stream())?;
7357 let _main = e.gpu.enter_main()?;
7358 e.stream().wait(&ws.ev_oproj)?;
7359 let mut output = e.uninit(ws.o_out)?;
7360 e.stream().memcpy_dtod(
7361 &ws.reduce_a.slice(0..ws.o_out),
7362 &mut output.slice_mut(0..ws.o_out),
7363 )?;
7364 return Ok(output);
7365 }
7366 let mut first = true;
7367 let mut current_is_a = false;
7368 for rank in 0..ranks {
7369 for block in 0..ws.blocks_per_rank {
7370 let use_peer = rank != 0;
7371 if use_peer {
7372 root.stream()
7373 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
7374 }
7375 match (first, current_is_a, use_peer) {
7377 (true, _, true) => {
7378 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7379 }
7380 (true, _, false) => root.add(
7381 &ws.zeros,
7382 &ws.o_partials[0][block],
7383 &mut ws.reduce_a,
7384 ws.o_out,
7385 )?,
7386 (false, true, true) => {
7387 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7388 }
7389 (false, true, false) => root.add(
7390 &ws.reduce_a,
7391 &ws.o_partials[0][block],
7392 &mut ws.reduce_b,
7393 ws.o_out,
7394 )?,
7395 (false, false, true) => {
7396 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7397 }
7398 (false, false, false) => root.add(
7399 &ws.reduce_b,
7400 &ws.o_partials[0][block],
7401 &mut ws.reduce_a,
7402 ws.o_out,
7403 )?,
7404 }
7405 current_is_a = first || !current_is_a;
7406 first = false;
7407 }
7408 }
7409 final_in_a = current_is_a;
7410
7411 for rank in 0..ranks {
7412 let start = rank * ws.local_kv_dim;
7413 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
7414 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
7415 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
7416 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
7417 }
7418 ws.ev_oproj.record(&root.stream())?;
7419 }
7420
7421 let _main = e.gpu.enter_main()?;
7425 e.stream().wait(&ws.ev_oproj)?;
7426 let mut output = e.uninit(ws.o_out)?;
7427 let source = if final_in_a {
7428 &ws.reduce_a
7429 } else {
7430 &ws.reduce_b
7431 };
7432 e.stream().memcpy_dtod(
7433 &source.slice(0..ws.o_out),
7434 &mut output.slice_mut(0..ws.o_out),
7435 )?;
7436 Ok(output)
7437 }
7438
7439 pub fn run_routed_experts(
7440 &self,
7441 experts: &ResidentExpertParallel,
7442 input: &[f32],
7443 tokens: usize,
7444 selected: &[usize],
7445 route_weights: &[f32],
7446 experts_per_token: usize,
7447 activation_limit: Option<f32>,
7448 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7449 validate_step_expert_activation_limit(activation_limit)?;
7450 validate_ep_residency(&self.ranks, experts)?;
7451 validate_activations(input, tokens, experts.input_width)?;
7452 let pairs = tokens
7453 .checked_mul(experts_per_token)
7454 .ok_or("EP route count overflow")?;
7455 if selected.len() != pairs || route_weights.len() != pairs {
7456 return Err(format!(
7457 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7458 {experts_per_token} ({pairs})",
7459 selected.len(),
7460 route_weights.len(),
7461 )
7462 .into());
7463 }
7464 if !route_weights.iter().all(|weight| weight.is_finite()) {
7465 return Err("EP route weights contain a non-finite value".into());
7466 }
7467 if self.native_p2p {
7468 return self.run_routed_experts_native(
7469 experts,
7470 input,
7471 tokens,
7472 selected,
7473 route_weights,
7474 experts_per_token,
7475 activation_limit,
7476 );
7477 }
7478
7479 let mut output = vec![0.0f32; tokens * experts.input_width];
7480 let per_rank = experts.expert_count / experts.ranks.len();
7481 for token in 0..tokens {
7482 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7483 for slot in 0..experts_per_token {
7484 let pair = token * experts_per_token + slot;
7485 let expert = selected[pair];
7486 if expert >= experts.expert_count {
7487 return Err(format!(
7488 "EP selected expert {expert} outside 0..{}",
7489 experts.expert_count
7490 )
7491 .into());
7492 }
7493 let owner = expert / per_rank;
7494 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7495 let rank = &experts.ranks[owner];
7496 let engine = &self.ranks[owner];
7497 let gate =
7498 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7499 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7500 let activated: Vec<f32> = gate
7501 .iter()
7502 .zip(&up)
7503 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7504 .collect();
7505 debug_assert_eq!(activated.len(), experts.expert_width);
7506 let down =
7507 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7508 let weight = route_weights[pair];
7509 for (sum, value) in output
7510 [token * experts.input_width..(token + 1) * experts.input_width]
7511 .iter_mut()
7512 .zip(down)
7513 {
7514 *sum += weight * value;
7515 }
7516 }
7517 }
7518 Ok(output)
7519 }
7520
7521 fn run_routed_experts_native(
7522 &self,
7523 experts: &ResidentExpertParallel,
7524 input: &[f32],
7525 tokens: usize,
7526 selected: &[usize],
7527 route_weights: &[f32],
7528 experts_per_token: usize,
7529 activation_limit: Option<f32>,
7530 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7531 if !self.native_p2p || self.ranks.len() < 2 {
7532 return Err("native EP execution requires at least two P2P ranks".into());
7533 }
7534 if self.ep_device_arithmetic {
7535 return self.run_routed_experts_native_device(
7536 experts,
7537 input,
7538 tokens,
7539 selected,
7540 route_weights,
7541 experts_per_token,
7542 activation_limit,
7543 );
7544 }
7545 let mut output = vec![0.0f32; tokens * experts.input_width];
7546 let per_rank = experts.expert_count / experts.ranks.len();
7547 for token in 0..tokens {
7548 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7549 let mut rank_inputs = (0..self.ranks.len())
7550 .map(|_| None)
7551 .collect::<Vec<Option<CudaSlice<f32>>>>();
7552 rank_inputs[0] = Some({
7553 let root = &self.ranks[0];
7554 let _main = root.gpu.enter_main()?;
7555 root.htod(input_row)?
7556 });
7557
7558 for slot in 0..experts_per_token {
7559 let pair = token * experts_per_token + slot;
7560 let expert = selected[pair];
7561 if expert >= experts.expert_count {
7562 return Err(format!(
7563 "EP selected expert {expert} outside 0..{}",
7564 experts.expert_count
7565 )
7566 .into());
7567 }
7568 let owner = expert / per_rank;
7569 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7570 if rank_inputs[owner].is_none() {
7571 let peer_input = {
7572 let root_input = rank_inputs[0]
7573 .as_ref()
7574 .ok_or("native EP lost its root input")?;
7575 let engine = &self.ranks[owner];
7576 let _main = engine.gpu.enter_main()?;
7577 let mut peer_input = engine.uninit(experts.input_width)?;
7578 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7579 peer_input
7580 };
7581 rank_inputs[owner] = Some(peer_input);
7582 }
7583
7584 let rank = &experts.ranks[owner];
7585 let engine = &self.ranks[owner];
7586 let owner_input = rank_inputs[owner]
7587 .as_ref()
7588 .ok_or("native EP owner input is absent after dispatch")?;
7589 let gate = run_resident_bank_expert_device(
7590 engine,
7591 &rank.gate,
7592 local_expert,
7593 owner_input,
7594 1,
7595 )?;
7596 let up = run_resident_bank_expert_device(
7597 engine,
7598 &rank.up,
7599 local_expert,
7600 owner_input,
7601 1,
7602 )?;
7603 let (gate, up) = {
7604 let _main = engine.gpu.enter_main()?;
7605 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
7606 };
7607 let activated = gate
7608 .iter()
7609 .zip(&up)
7610 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7611 .collect::<Vec<_>>();
7612 debug_assert_eq!(activated.len(), experts.expert_width);
7613 let activated = {
7614 let _main = engine.gpu.enter_main()?;
7615 engine.htod(&activated)?
7616 };
7617 let down = run_resident_bank_expert_device(
7618 engine,
7619 &rank.down,
7620 local_expert,
7621 &activated,
7622 1,
7623 )?;
7624 let down = if owner == 0 {
7625 let _main = engine.gpu.enter_main()?;
7626 engine.dtoh(&down)?
7627 } else {
7628 let root = &self.ranks[0];
7629 let _main = root.gpu.enter_main()?;
7630 let mut root_down = root.uninit(experts.input_width)?;
7631 root.stream().memcpy_dtod(&down, &mut root_down)?;
7632 root.dtoh(&root_down)?
7633 };
7634 let weight = route_weights[pair];
7635 for (sum, value) in output
7636 [token * experts.input_width..(token + 1) * experts.input_width]
7637 .iter_mut()
7638 .zip(down)
7639 {
7640 *sum += weight * value;
7641 }
7642 }
7643 }
7644 Ok(output)
7645 }
7646
7647 fn run_routed_experts_native_device(
7648 &self,
7649 experts: &ResidentExpertParallel,
7650 input: &[f32],
7651 tokens: usize,
7652 selected: &[usize],
7653 route_weights: &[f32],
7654 experts_per_token: usize,
7655 activation_limit: Option<f32>,
7656 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7657 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
7658 return Err(
7659 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
7660 );
7661 }
7662 let mut output = Vec::with_capacity(tokens * experts.input_width);
7663 let per_rank = experts.expert_count / experts.ranks.len();
7664 let root = &self.ranks[0];
7665 for token in 0..tokens {
7666 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7667 let mut rank_inputs = (0..self.ranks.len())
7668 .map(|_| None)
7669 .collect::<Vec<Option<CudaSlice<f32>>>>();
7670 rank_inputs[0] = Some({
7671 let _main = root.gpu.enter_main()?;
7672 root.htod(input_row)?
7673 });
7674 let mut root_output = {
7675 let _main = root.gpu.enter_main()?;
7676 root.zeros(experts.input_width)?
7677 };
7678 let mut remote_down_keepalive = Vec::new();
7679
7680 for slot in 0..experts_per_token {
7681 let pair = token * experts_per_token + slot;
7682 let expert = selected[pair];
7683 if expert >= experts.expert_count {
7684 return Err(format!(
7685 "EP selected expert {expert} outside 0..{}",
7686 experts.expert_count
7687 )
7688 .into());
7689 }
7690 let owner = expert / per_rank;
7691 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7692 if rank_inputs[owner].is_none() {
7693 let peer_input = {
7694 let root_input = rank_inputs[0]
7695 .as_ref()
7696 .ok_or("native EP lost its root input")?;
7697 let engine = &self.ranks[owner];
7698 let _main = engine.gpu.enter_main()?;
7699 let mut peer_input = engine.uninit(experts.input_width)?;
7700 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7701 peer_input
7702 };
7703 rank_inputs[owner] = Some(peer_input);
7704 }
7705
7706 let rank = &experts.ranks[owner];
7707 let engine = &self.ranks[owner];
7708 let owner_input = rank_inputs[owner]
7709 .as_ref()
7710 .ok_or("native EP owner input is absent after dispatch")?;
7711 let gate = run_resident_bank_expert_device(
7712 engine,
7713 &rank.gate,
7714 local_expert,
7715 owner_input,
7716 1,
7717 )?;
7718 let up = run_resident_bank_expert_device(
7719 engine,
7720 &rank.up,
7721 local_expert,
7722 owner_input,
7723 1,
7724 )?;
7725 let activated = {
7726 let _main = engine.gpu.enter_main()?;
7727 let mut activated = engine.uninit(experts.expert_width)?;
7728 if let Some(limit) = activation_limit {
7729 engine.silu_clamped_mul_host_expf(
7730 &gate,
7731 &up,
7732 limit,
7733 &mut activated,
7734 experts.expert_width,
7735 )?;
7736 } else {
7737 engine.silu_mul_host_expf(
7738 &gate,
7739 &up,
7740 &mut activated,
7741 experts.expert_width,
7742 )?;
7743 }
7744 activated
7745 };
7746 let down = run_resident_bank_expert_device(
7747 engine,
7748 &rank.down,
7749 local_expert,
7750 &activated,
7751 1,
7752 )?;
7753 let root_down = if owner == 0 {
7754 down
7755 } else {
7756 let _main = root.gpu.enter_main()?;
7757 let mut root_down = root.uninit(experts.input_width)?;
7758 root.stream().memcpy_dtod(&down, &mut root_down)?;
7759 remote_down_keepalive.push(down);
7763 root_down
7764 };
7765 let _main = root.gpu.enter_main()?;
7766 let mut destination = root_output.slice_mut(0..experts.input_width);
7767 root.axpy_host_into(
7768 &root_down.slice(0..root_down.len()),
7769 route_weights[pair],
7770 &mut destination,
7771 experts.input_width,
7772 )?;
7773 }
7774
7775 let _main = root.gpu.enter_main()?;
7776 let root_output = root.dtoh(&root_output)?;
7777 drop(remote_down_keepalive);
7778 output.extend(root_output);
7779 }
7780 Ok(output)
7781 }
7782}
7783
7784fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7785 if matrix.out_features % tp != 0 {
7786 return Err(format!(
7787 "column-parallel out_features {} is not divisible by TP={tp}",
7788 matrix.out_features
7789 ));
7790 }
7791 let local_out = matrix.out_features / tp;
7792 if local_out % FP8_BLOCK != 0 {
7793 return Err(format!(
7794 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7795 E4M3 scale block"
7796 ));
7797 }
7798 Ok(())
7799}
7800
7801fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7802 if !matches!(tp, 1 | 2 | 4 | 8) {
7803 return Err(format!(
7804 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7805 ));
7806 }
7807 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7808 return Err(format!(
7809 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7810 ));
7811 }
7812 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7813 let local_out = out_features / tp;
7814 if local_out % canonical_rows != 0 {
7815 return Err(format!(
7816 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7817 {canonical_rows}-row chunks"
7818 ));
7819 }
7820 Ok(canonical_rows)
7821}
7822
7823fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7824 if !matches!(tp, 1 | 2 | 4 | 8) {
7825 return Err(format!(
7826 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7827 ));
7828 }
7829 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7830 return Err(format!(
7831 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7832 ));
7833 }
7834 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7835 let local_in = in_features / tp;
7836 if local_in % canonical_cols != 0 {
7837 return Err(format!(
7838 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7839 {canonical_cols}-column chunks"
7840 ));
7841 }
7842 Ok(canonical_cols)
7843}
7844
7845fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7846 if matrix.in_features % tp != 0 {
7847 return Err(format!(
7848 "row-parallel in_features {} is not divisible by TP={tp}",
7849 matrix.in_features
7850 ));
7851 }
7852 let local_in = matrix.in_features / tp;
7853 if local_in % FP8_BLOCK != 0 {
7854 return Err(format!(
7855 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7856 E4M3 scale block"
7857 ));
7858 }
7859 Ok(())
7860}
7861
7862fn upload_rank(
7863 engine: &Engine,
7864 matrix: E4m3BlockMatrix<'_>,
7865) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7866 let _main = engine.gpu.enter_main()?;
7867 matrix.validate()?;
7868 Ok(ResidentE4m3Rank {
7869 codes: engine.htod_bytes(matrix.codes)?,
7870 scales: engine.htod(matrix.scales)?,
7871 out_features: matrix.out_features,
7872 in_features: matrix.in_features,
7873 })
7874}
7875
7876fn upload_bf16_rank(
7877 engine: &Engine,
7878 matrix: Bf16Matrix<'_>,
7879 f32_mirror: bool,
7880) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7881 let _main = engine.gpu.enter_main()?;
7882 matrix.validate()?;
7883 let bytes = engine.htod_bytes(matrix.bytes)?;
7884 let weight = if f32_mirror {
7885 let values = matrix
7886 .out_features
7887 .checked_mul(matrix.in_features)
7888 .ok_or("resident BF16 mirror element count overflow")?;
7889 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7890 } else {
7891 ResidentBf16Weight::Bf16(bytes)
7892 };
7893 let q8 = if crate::step_tp_w8_on() && matrix.in_features % 32 == 0 {
7897 if let ResidentBf16Weight::Bf16(bytes) = &weight {
7898 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
7905 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
7906 engine.encode_q8_0_from_bf16(
7907 bytes,
7908 &mut interleaved,
7909 matrix.in_features,
7910 matrix.out_features,
7911 )?;
7912 let mirror =
7913 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
7914 Some(mirror)
7915 } else {
7916 None
7917 }
7918 } else {
7919 None
7920 };
7921 Ok(ResidentBf16Rank {
7922 weight,
7923 out_features: matrix.out_features,
7924 in_features: matrix.in_features,
7925 q8,
7926 })
7927}
7928
7929fn upload_expert_bank_rank(
7930 engine: &Engine,
7931 bank: E4m3ExpertBank<'_>,
7932 expert_range: Range<usize>,
7933) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7934 let _main = engine.gpu.enter_main()?;
7935 bank.validate()?;
7936 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7937 return Err(format!(
7938 "invalid EP expert range {expert_range:?} for {} experts",
7939 bank.expert_count
7940 )
7941 .into());
7942 }
7943 let code_stride = bank.out_features * bank.in_features;
7944 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7945 Ok(ResidentE4m3ExpertBankRank {
7946 codes: engine.htod_bytes(
7947 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7948 )?,
7949 scales: engine.htod(
7950 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7951 )?,
7952 expert_range,
7953 out_features: bank.out_features,
7954 in_features: bank.in_features,
7955 code_stride,
7956 scale_stride,
7957 k_blocks: None,
7958 })
7959}
7960
7961fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7962 if bank.out_features % tp != 0 {
7963 return Err(format!(
7964 "TP expert output width {} is not divisible by TP={tp}",
7965 bank.out_features
7966 ));
7967 }
7968 let local_out = bank.out_features / tp;
7969 if local_out % FP8_BLOCK != 0 {
7970 return Err(format!(
7971 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7972 ));
7973 }
7974 Ok(())
7975}
7976
7977fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7978 if bank.in_features % tp != 0 {
7979 return Err(format!(
7980 "TP expert input width {} is not divisible by TP={tp}",
7981 bank.in_features
7982 ));
7983 }
7984 let local_in = bank.in_features / tp;
7985 if local_in % FP8_BLOCK != 0 {
7986 return Err(format!(
7987 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7988 ));
7989 }
7990 Ok(())
7991}
7992
7993fn upload_column_bank_rank(
7994 engine: &Engine,
7995 bank: E4m3ExpertBank<'_>,
7996 tp: usize,
7997 rank: usize,
7998) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7999 let _main = engine.gpu.enter_main()?;
8000 let packed = pack_column_bank_rank(bank, tp, rank)?;
8001 Ok(ResidentE4m3ExpertBankRank {
8002 codes: engine.htod_bytes(&packed.codes)?,
8003 scales: engine.htod(&packed.scales)?,
8004 expert_range: packed.expert_range,
8005 out_features: packed.out_features,
8006 in_features: packed.in_features,
8007 code_stride: packed.code_stride,
8008 scale_stride: packed.scale_stride,
8009 k_blocks: packed.k_blocks,
8010 })
8011}
8012
8013fn pack_column_bank_rank(
8014 bank: E4m3ExpertBank<'_>,
8015 tp: usize,
8016 rank: usize,
8017) -> Result<PackedE4m3ExpertBankRank, String> {
8018 bank.validate()?;
8019 validate_column_bank_shape(bank, tp)?;
8020 if rank >= tp {
8021 return Err(format!("TP rank {rank} outside 0..{tp}"));
8022 }
8023 let local_out = bank.out_features / tp;
8024 let full_code_stride = bank.out_features * bank.in_features;
8025 let local_code_stride = local_out * bank.in_features;
8026 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8027 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8028 let local_scale_rows = local_out / FP8_BLOCK;
8029 let local_scale_stride = local_scale_rows * scale_cols;
8030 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8031 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8032 let row_start = rank * local_out;
8033 let scale_row_start = rank * local_scale_rows;
8034 for expert in 0..bank.expert_count {
8035 let code_start = expert * full_code_stride + row_start * bank.in_features;
8036 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8037 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8038 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8039 }
8040 Ok(PackedE4m3ExpertBankRank {
8041 codes,
8042 scales,
8043 expert_range: 0..bank.expert_count,
8044 out_features: local_out,
8045 in_features: bank.in_features,
8046 code_stride: local_code_stride,
8047 scale_stride: local_scale_stride,
8048 k_blocks: None,
8049 })
8050}
8051
8052fn upload_row_bank_rank(
8053 engine: &Engine,
8054 bank: E4m3ExpertBank<'_>,
8055 tp: usize,
8056 rank: usize,
8057) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8058 let _main = engine.gpu.enter_main()?;
8059 let packed = pack_row_bank_rank(bank, tp, rank)?;
8060 Ok(ResidentE4m3ExpertBankRank {
8061 codes: engine.htod_bytes(&packed.codes)?,
8062 scales: engine.htod(&packed.scales)?,
8063 expert_range: packed.expert_range,
8064 out_features: packed.out_features,
8065 in_features: packed.in_features,
8066 code_stride: packed.code_stride,
8067 scale_stride: packed.scale_stride,
8068 k_blocks: packed.k_blocks,
8069 })
8070}
8071
8072fn pack_row_bank_rank(
8073 bank: E4m3ExpertBank<'_>,
8074 tp: usize,
8075 rank: usize,
8076) -> Result<PackedE4m3ExpertBankRank, String> {
8077 bank.validate()?;
8078 validate_row_bank_shape(bank, tp)?;
8079 if rank >= tp {
8080 return Err(format!("TP rank {rank} outside 0..{tp}"));
8081 }
8082 let local_in = bank.in_features / tp;
8083 let full_code_stride = bank.out_features * bank.in_features;
8084 let local_code_stride = bank.out_features * local_in;
8085 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8086 let local_scale_cols = local_in / FP8_BLOCK;
8087 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8088 let full_scale_stride = scale_rows * full_scale_cols;
8089 let local_scale_stride = scale_rows * local_scale_cols;
8090 let global_block_start = rank * local_scale_cols;
8091 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8092 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8093 for expert in 0..bank.expert_count {
8094 let expert_code_start = expert * full_code_stride;
8095 let expert_scale_start = expert * full_scale_stride;
8096 for local_block in 0..local_scale_cols {
8097 let global_block = global_block_start + local_block;
8098 let column_start = global_block * FP8_BLOCK;
8099 for row in 0..bank.out_features {
8100 let start = expert_code_start + row * bank.in_features + column_start;
8101 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8102 }
8103 for row in 0..scale_rows {
8104 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8105 }
8106 }
8107 }
8108 Ok(PackedE4m3ExpertBankRank {
8109 codes,
8110 scales,
8111 expert_range: 0..bank.expert_count,
8112 out_features: bank.out_features,
8113 in_features: local_in,
8114 code_stride: local_code_stride,
8115 scale_stride: local_scale_stride,
8116 k_blocks: Some(local_scale_cols),
8117 })
8118}
8119
8120fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8121 if engines.len() != ranks.len() {
8122 return Err(format!(
8123 "resident TP rank count {} != runtime rank count {}",
8124 ranks.len(),
8125 engines.len()
8126 ));
8127 }
8128 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8129 let device = engine.ctx().ordinal();
8130 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8131 return Err(format!(
8132 "resident TP rank {rank} is not owned by runtime device {device}"
8133 ));
8134 }
8135 }
8136 Ok(())
8137}
8138
8139fn validate_tp_bank_residency(
8140 engines: &[Engine],
8141 experts: &ResidentTpExpertBank,
8142) -> Result<(), String> {
8143 if engines.len() != experts.gate.len()
8144 || engines.len() != experts.up.len()
8145 || engines.len() != experts.down.len()
8146 {
8147 return Err(format!(
8148 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8149 experts.gate.len(),
8150 experts.up.len(),
8151 experts.down.len(),
8152 engines.len()
8153 ));
8154 }
8155 for (rank, engine) in engines.iter().enumerate() {
8156 let device = engine.ctx().ordinal();
8157 for (projection, bank) in [
8158 ("gate", &experts.gate[rank]),
8159 ("up", &experts.up[rank]),
8160 ("down", &experts.down[rank]),
8161 ] {
8162 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8163 return Err(format!(
8164 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8165 {device}"
8166 ));
8167 }
8168 }
8169 }
8170 Ok(())
8171}
8172
8173fn validate_ep_residency(
8174 engines: &[Engine],
8175 experts: &ResidentExpertParallel,
8176) -> Result<(), String> {
8177 if engines.len() != experts.ranks.len() {
8178 return Err(format!(
8179 "resident EP rank count {} != runtime rank count {}",
8180 experts.ranks.len(),
8181 engines.len()
8182 ));
8183 }
8184 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
8185 let device = engine.ctx().ordinal();
8186 for (projection, bank) in [
8187 ("gate", &resident.gate),
8188 ("up", &resident.up),
8189 ("down", &resident.down),
8190 ] {
8191 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8192 return Err(format!(
8193 "resident EP rank {rank} {projection} bank is not owned by runtime device \
8194 {device}"
8195 ));
8196 }
8197 }
8198 }
8199 Ok(())
8200}
8201
8202fn run_rank(
8203 engine: &Engine,
8204 matrix: E4m3BlockMatrix<'_>,
8205 activations: &[f32],
8206 tokens: usize,
8207) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8208 let _main = engine.gpu.enter_main()?;
8209 let codes = engine.htod_bytes(matrix.codes)?;
8210 let scales = engine.htod(matrix.scales)?;
8211 let activations = engine.htod(activations)?;
8212 let output = engine.qmatvec_mmq_fp8_blk(
8213 &codes,
8214 &scales,
8215 &activations,
8216 tokens,
8217 matrix.in_features,
8218 matrix.out_features,
8219 )?;
8220 engine.dtoh(&output)
8221}
8222
8223fn run_resident_rank(
8224 engine: &Engine,
8225 matrix: &ResidentE4m3Rank,
8226 activations: &[f32],
8227 tokens: usize,
8228) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8229 let _main = engine.gpu.enter_main()?;
8230 let activations = engine.htod(activations)?;
8231 let output = engine.qmatvec_mmq_fp8_blk(
8232 &matrix.codes,
8233 &matrix.scales,
8234 &activations,
8235 tokens,
8236 matrix.in_features,
8237 matrix.out_features,
8238 )?;
8239 engine.dtoh(&output)
8240}
8241
8242fn run_resident_bf16_rank(
8243 engine: &Engine,
8244 matrix: &ResidentBf16Rank,
8245 activations: &[f32],
8246 tokens: usize,
8247 canonical_chunk_rows: Option<usize>,
8248) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8249 let _main = engine.gpu.enter_main()?;
8250 let activations = engine.htod(activations)?;
8251 let output = run_resident_bf16_rank_device(
8252 engine,
8253 matrix,
8254 &activations,
8255 tokens,
8256 canonical_chunk_rows,
8257 false,
8258 )?;
8259 engine.dtoh(&output)
8260}
8261
8262fn run_resident_bf16_rank_device(
8263 engine: &Engine,
8264 matrix: &ResidentBf16Rank,
8265 activations: &CudaSlice<f32>,
8266 tokens: usize,
8267 canonical_chunk_rows: Option<usize>,
8268 strided_chunk_output: bool,
8269) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8270 let _main = engine.gpu.enter_main()?;
8271 if activations.ordinal() != engine.ctx().ordinal() {
8272 return Err(format!(
8273 "resident BF16 activation device {} != rank device {}",
8274 activations.ordinal(),
8275 engine.ctx().ordinal()
8276 )
8277 .into());
8278 }
8279 if activations.len() != tokens * matrix.in_features {
8280 return Err(format!(
8281 "resident BF16 activation count {} != {tokens}x{}",
8282 activations.len(),
8283 matrix.in_features
8284 )
8285 .into());
8286 }
8287 match (&matrix.weight, canonical_chunk_rows) {
8288 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8289 .linear_bf16_resident_canonical_rows(
8290 activations,
8291 bytes,
8292 tokens,
8293 matrix.in_features,
8294 matrix.out_features,
8295 rows,
8296 ),
8297 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8298 activations,
8299 bytes,
8300 tokens,
8301 matrix.in_features,
8302 matrix.out_features,
8303 ),
8304 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8305 .linear_f32_resident_canonical_rows_strided(
8306 activations,
8307 values,
8308 tokens,
8309 matrix.in_features,
8310 matrix.out_features,
8311 rows,
8312 ),
8313 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8314 activations,
8315 values,
8316 tokens,
8317 matrix.in_features,
8318 matrix.out_features,
8319 rows,
8320 ),
8321 (ResidentBf16Weight::F32(values), None) => engine.linear(
8322 activations,
8323 values,
8324 tokens,
8325 matrix.in_features,
8326 matrix.out_features,
8327 ),
8328 }
8329}
8330
8331fn validate_resident_bf16_ranks(
8332 engines: &[Engine],
8333 ranks: &[ResidentBf16Rank],
8334) -> Result<(), String> {
8335 if engines.len() != ranks.len() {
8336 return Err(format!(
8337 "resident BF16 TP rank count {} != runtime rank count {}",
8338 ranks.len(),
8339 engines.len(),
8340 ));
8341 }
8342 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8343 let device = engine.ctx().ordinal();
8344 if matrix.weight.ordinal() != device {
8345 return Err(format!(
8346 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8347 ));
8348 }
8349 }
8350 Ok(())
8351}
8352
8353fn validate_step_bf16_row_residency(
8354 engines: &[Engine],
8355 matrix: &ResidentStepBf16RowParallel,
8356) -> Result<(), String> {
8357 if engines.len() != matrix.ranks.len() {
8358 return Err(format!(
8359 "resident Step BF16 row rank count {} != runtime rank count {}",
8360 matrix.ranks.len(),
8361 engines.len(),
8362 ));
8363 }
8364 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8365 if matrix.canonical_chunk_cols != canonical_cols {
8366 return Err(format!(
8367 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8368 matrix.canonical_chunk_cols
8369 ));
8370 }
8371 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8372 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8373 if blocks.len() != blocks_per_rank {
8374 return Err(format!(
8375 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8376 blocks.len()
8377 ));
8378 }
8379 let device = engine.ctx().ordinal();
8380 for (block, resident) in blocks.iter().enumerate() {
8381 if resident.weight.ordinal() != device
8382 || resident.in_features != canonical_cols
8383 || resident.out_features != matrix.out_features
8384 {
8385 return Err(format!(
8386 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8387 device or geometry"
8388 ));
8389 }
8390 }
8391 }
8392 Ok(())
8393}
8394
8395fn validate_replicated_device_rows(
8396 engines: &[Engine],
8397 rows: &ResidentReplicatedDeviceRows,
8398) -> Result<(), String> {
8399 let rank_lengths = rows
8400 .ranks
8401 .iter()
8402 .map(|rank_rows| rank_rows.len())
8403 .collect::<Vec<_>>();
8404 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8405 if rows
8406 .ranks
8407 .iter()
8408 .zip(engines)
8409 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8410 {
8411 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8412 }
8413 Ok(())
8414}
8415
8416fn replicated_device_row_values(
8417 tokens: usize,
8418 width: usize,
8419 expected_ranks: usize,
8420 rank_lengths: &[usize],
8421) -> Result<usize, String> {
8422 let values = tokens
8423 .checked_mul(width)
8424 .ok_or("replicated device row size overflow")?;
8425 if tokens == 0
8426 || width == 0
8427 || expected_ranks == 0
8428 || rank_lengths.len() != expected_ranks
8429 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8430 {
8431 return Err(format!(
8432 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8433 tokens,
8434 width,
8435 rank_lengths.len(),
8436 expected_ranks
8437 ));
8438 }
8439 Ok(values)
8440}
8441
8442fn replicated_device_row_source_values(
8443 tokens: usize,
8444 width: usize,
8445 source_len: usize,
8446 source_device: usize,
8447 root_device: usize,
8448) -> Result<usize, String> {
8449 let values = tokens
8450 .checked_mul(width)
8451 .ok_or("replicated device row size overflow")?;
8452 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8453 return Err(format!(
8454 "replicated device row source has inconsistent geometry/device \
8455 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8456 ));
8457 }
8458 Ok(values)
8459}
8460
8461fn bf16_column_shard(
8462 matrix: Bf16Matrix<'_>,
8463 tp: usize,
8464 rank: usize,
8465) -> Result<Bf16Matrix<'_>, String> {
8466 matrix.validate()?;
8467 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8468 return Err(format!(
8469 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8470 matrix.out_features
8471 ));
8472 }
8473 let local_out = matrix.out_features / tp;
8474 let row_bytes = matrix.in_features * 2;
8475 let start = rank * local_out * row_bytes;
8476 Ok(Bf16Matrix {
8477 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8478 out_features: local_out,
8479 in_features: matrix.in_features,
8480 })
8481}
8482
8483fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8484 matrix.validate()?;
8485 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8486 return Err(format!(
8487 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8488 matrix.in_features
8489 ));
8490 }
8491 let local_in = matrix.in_features / tp;
8492 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8493 for row in 0..matrix.out_features {
8494 let start = (row * matrix.in_features + rank * local_in) * 2;
8495 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8496 }
8497 Ok(bytes)
8498}
8499
8500fn bf16_row_block(
8501 matrix: Bf16Matrix<'_>,
8502 col_start: usize,
8503 block_cols: usize,
8504) -> Result<Vec<u8>, String> {
8505 matrix.validate()?;
8506 let col_end = col_start
8507 .checked_add(block_cols)
8508 .ok_or("BF16 row block column overflow")?;
8509 if block_cols == 0 || col_end > matrix.in_features {
8510 return Err(format!(
8511 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8512 matrix.in_features
8513 ));
8514 }
8515 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8516 for row in 0..matrix.out_features {
8517 let start = (row * matrix.in_features + col_start) * 2;
8518 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8519 }
8520 Ok(bytes)
8521}
8522
8523fn run_resident_bank_expert(
8524 engine: &Engine,
8525 bank: &ResidentE4m3ExpertBankRank,
8526 local_expert: usize,
8527 activations: &[f32],
8528 tokens: usize,
8529) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8530 let _main = engine.gpu.enter_main()?;
8531 if bank.k_blocks.is_some() {
8532 return Err("block-major TP row bank requires canonical block execution".into());
8533 }
8534 let local_count = bank.expert_range.end - bank.expert_range.start;
8535 if local_expert >= local_count {
8536 return Err(format!(
8537 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8538 bank.expert_range
8539 )
8540 .into());
8541 }
8542 validate_activations(activations, tokens, bank.in_features)?;
8543 let activations = engine.htod(activations)?;
8544 let weight = bank
8545 .codes
8546 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8547 let scales = bank
8548 .scales
8549 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8550 let input = activations.slice(0..activations.len());
8551 let output = engine.qmatvec_mmq_fp8_blk_view(
8552 &weight,
8553 &scales,
8554 &input,
8555 tokens,
8556 bank.in_features,
8557 bank.out_features,
8558 )?;
8559 engine.dtoh(&output)
8560}
8561
8562fn run_resident_bank_expert_block(
8563 engine: &Engine,
8564 bank: &ResidentE4m3ExpertBankRank,
8565 local_expert: usize,
8566 block: usize,
8567 activations: &[f32],
8568) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8569 let _main = engine.gpu.enter_main()?;
8570 let local_count = bank.expert_range.end - bank.expert_range.start;
8571 if local_expert >= local_count {
8572 return Err(format!(
8573 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8574 bank.expert_range
8575 )
8576 .into());
8577 }
8578 let blocks = bank
8579 .k_blocks
8580 .ok_or("TP row bank is not packed in native K-block order")?;
8581 if block >= blocks {
8582 return Err(format!("TP row block {block} outside 0..{blocks}").into());
8583 }
8584 validate_activations(activations, 1, FP8_BLOCK)?;
8585 let block_code_stride = bank.out_features * FP8_BLOCK;
8586 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8587 if bank.in_features != blocks * FP8_BLOCK
8588 || bank.code_stride != blocks * block_code_stride
8589 || bank.scale_stride != blocks * block_scale_stride
8590 {
8591 return Err("TP row bank block-major geometry is inconsistent".into());
8592 }
8593
8594 let expert_code_start = local_expert * bank.code_stride;
8595 let expert_scale_start = local_expert * bank.scale_stride;
8596 let weight = bank.codes.slice(
8597 expert_code_start + block * block_code_stride
8598 ..expert_code_start + (block + 1) * block_code_stride,
8599 );
8600 let scales = bank.scales.slice(
8601 expert_scale_start + block * block_scale_stride
8602 ..expert_scale_start + (block + 1) * block_scale_stride,
8603 );
8604 let activations = engine.htod(activations)?;
8605 let input = activations.slice(0..activations.len());
8606 let output = engine.qmatvec_mmq_fp8_blk_view(
8607 &weight,
8608 &scales,
8609 &input,
8610 1,
8611 FP8_BLOCK,
8612 bank.out_features,
8613 )?;
8614 engine.dtoh(&output)
8615}
8616
8617fn run_resident_bank_expert_device(
8618 engine: &Engine,
8619 bank: &ResidentE4m3ExpertBankRank,
8620 local_expert: usize,
8621 activations: &CudaSlice<f32>,
8622 tokens: usize,
8623) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8624 let _main = engine.gpu.enter_main()?;
8625 if bank.k_blocks.is_some() {
8626 return Err("block-major TP row bank requires canonical block execution".into());
8627 }
8628 let local_count = bank.expert_range.end - bank.expert_range.start;
8629 if local_expert >= local_count {
8630 return Err(format!(
8631 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8632 bank.expert_range
8633 )
8634 .into());
8635 }
8636 let expected = tokens
8637 .checked_mul(bank.in_features)
8638 .ok_or("native TP activation size overflow")?;
8639 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
8640 return Err(format!(
8641 "native TP activation len/device {}/{} != expected {expected}/{}",
8642 activations.len(),
8643 activations.ordinal(),
8644 engine.ctx().ordinal()
8645 )
8646 .into());
8647 }
8648 let weight = bank
8649 .codes
8650 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8651 let scales = bank
8652 .scales
8653 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8654 let input = activations.slice(0..activations.len());
8655 engine.qmatvec_mmq_fp8_blk_view(
8656 &weight,
8657 &scales,
8658 &input,
8659 tokens,
8660 bank.in_features,
8661 bank.out_features,
8662 )
8663}
8664
8665fn run_resident_bank_expert_block_device(
8666 engine: &Engine,
8667 bank: &ResidentE4m3ExpertBankRank,
8668 local_expert: usize,
8669 block: usize,
8670 activations: &cudarc::driver::CudaView<'_, f32>,
8671) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8672 let _main = engine.gpu.enter_main()?;
8673 let local_count = bank.expert_range.end - bank.expert_range.start;
8674 if local_expert >= local_count {
8675 return Err(format!(
8676 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8677 bank.expert_range
8678 )
8679 .into());
8680 }
8681 let blocks = bank
8682 .k_blocks
8683 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
8684 if block >= blocks {
8685 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
8686 }
8687 let activation_device = activations.stream().context().ordinal();
8688 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
8689 return Err(format!(
8690 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
8691 activations.len(),
8692 activation_device,
8693 engine.ctx().ordinal()
8694 )
8695 .into());
8696 }
8697 let block_code_stride = bank.out_features * FP8_BLOCK;
8698 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8699 if bank.in_features != blocks * FP8_BLOCK
8700 || bank.code_stride != blocks * block_code_stride
8701 || bank.scale_stride != blocks * block_scale_stride
8702 {
8703 return Err("native TP row bank block-major geometry is inconsistent".into());
8704 }
8705 let expert_code_start = local_expert * bank.code_stride;
8706 let expert_scale_start = local_expert * bank.scale_stride;
8707 let weight = bank.codes.slice(
8708 expert_code_start + block * block_code_stride
8709 ..expert_code_start + (block + 1) * block_code_stride,
8710 );
8711 let scales = bank.scales.slice(
8712 expert_scale_start + block * block_scale_stride
8713 ..expert_scale_start + (block + 1) * block_scale_stride,
8714 );
8715 engine.qmatvec_mmq_fp8_blk_view(
8716 &weight,
8717 &scales,
8718 activations,
8719 1,
8720 FP8_BLOCK,
8721 bank.out_features,
8722 )
8723}
8724
8725fn configure_native_p2p(
8726 ranks: &[Engine],
8727 devices: &[usize],
8728) -> Result<(), Box<dyn std::error::Error>> {
8729 if ranks.len() != devices.len() || ranks.len() < 2 {
8730 return Err("native TP P2P setup requires matching multi-rank devices".into());
8731 }
8732 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8733 if engine.ctx().ordinal() != device {
8734 return Err(format!(
8735 "native TP rank {rank} context device {} != requested device {device}",
8736 engine.ctx().ordinal()
8737 )
8738 .into());
8739 }
8740 }
8741
8742 for src in 0..ranks.len() {
8743 for dst in 0..ranks.len() {
8744 if src == dst {
8745 continue;
8746 }
8747 let mut can_access = 0;
8748 unsafe {
8749 cudarc::driver::sys::cuDeviceCanAccessPeer(
8750 &mut can_access,
8751 ranks[src].ctx().cu_device(),
8752 ranks[dst].ctx().cu_device(),
8753 )
8754 .result()?;
8755 }
8756 if can_access == 0 {
8757 return Err(format!(
8758 "native TP requires P2P, but dev{} cannot access dev{}",
8759 devices[src], devices[dst]
8760 )
8761 .into());
8762 }
8763 ranks[src].ctx().bind_to_thread()?;
8764 let rc =
8765 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8766 use cudarc::driver::sys::cudaError_enum as E;
8767 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8768 return Err(format!(
8769 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8770 devices[src], devices[dst]
8771 )
8772 .into());
8773 }
8774 }
8775 }
8776
8777 for &owner in devices {
8778 for &accessor in devices {
8779 if owner == accessor {
8780 continue;
8781 }
8782 let device = cudarc::driver::result::device::get(owner as i32)?;
8783 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8784 unsafe {
8785 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8786 }
8787 let desc = cudarc::driver::sys::CUmemAccessDesc {
8788 location: cudarc::driver::sys::CUmemLocation {
8789 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8790 id: accessor as i32,
8791 },
8792 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8793 };
8794 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8795 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8796 return Err(format!(
8797 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8798 {rc:?}"
8799 )
8800 .into());
8801 }
8802 }
8803 }
8804
8805 for src in 0..ranks.len() {
8806 for dst in 0..ranks.len() {
8807 if src == dst {
8808 continue;
8809 }
8810 for &words in NATIVE_P2P_PROBE_WORDS {
8811 let expected = (0..words)
8812 .map(|index| {
8813 (index as u32)
8814 .wrapping_mul(0x9e37_79b9)
8815 .wrapping_add(((src as u32) << 16) | dst as u32)
8816 })
8817 .collect::<Vec<_>>();
8818 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8819 let source = ranks[src].htod_u32_v(&expected)?;
8820 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8821 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8822 let actual = ranks[dst].dtoh_u32(&destination)?;
8823 if actual != expected {
8824 let mismatches = actual
8825 .iter()
8826 .zip(&expected)
8827 .filter(|(actual, expected)| actual != expected)
8828 .count();
8829 return Err(format!(
8830 "native TP peer probe dev{}->dev{} failed at {} bytes: \
8831 {mismatches}/{} words differ",
8832 devices[src],
8833 devices[dst],
8834 words * std::mem::size_of::<u32>(),
8835 expected.len()
8836 )
8837 .into());
8838 }
8839 }
8840 }
8841 }
8842 ranks[0].ctx().bind_to_thread()?;
8843 eprintln!(
8844 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8845 directions={} byte_ladder={:?} mismatches=0",
8846 ranks.len() * (ranks.len() - 1),
8847 NATIVE_P2P_PROBE_WORDS
8848 .iter()
8849 .map(|words| words * std::mem::size_of::<u32>())
8850 .collect::<Vec<_>>(),
8851 );
8852 Ok(())
8853}
8854
8855fn validate_activations(
8856 activations: &[f32],
8857 tokens: usize,
8858 in_features: usize,
8859) -> Result<(), String> {
8860 let expected = tokens
8861 .checked_mul(in_features)
8862 .ok_or_else(|| "activation size overflow".to_string())?;
8863 if activations.len() != expected {
8864 return Err(format!(
8865 "activation count {} != {tokens}x{in_features} ({expected})",
8866 activations.len()
8867 ));
8868 }
8869 if !activations.iter().all(|value| value.is_finite()) {
8870 return Err("activations contain a non-finite value".to_string());
8871 }
8872 Ok(())
8873}
8874
8875fn column_shard(
8876 matrix: E4m3BlockMatrix<'_>,
8877 tp: usize,
8878 rank: usize,
8879) -> Result<E4m3BlockMatrix<'_>, String> {
8880 let local_out = matrix.out_features / tp;
8881 let row_start = rank * local_out;
8882 let code_start = row_start * matrix.in_features;
8883 let code_end = code_start + local_out * matrix.in_features;
8884 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8885 let local_scale_rows = local_out / FP8_BLOCK;
8886 let scale_start = rank * local_scale_rows * scale_cols;
8887 let scale_end = scale_start + local_scale_rows * scale_cols;
8888 Ok(E4m3BlockMatrix {
8889 codes: &matrix.codes[code_start..code_end],
8890 scales: &matrix.scales[scale_start..scale_end],
8891 out_features: local_out,
8892 in_features: matrix.in_features,
8893 })
8894}
8895
8896fn row_shard(
8897 matrix: E4m3BlockMatrix<'_>,
8898 tp: usize,
8899 rank: usize,
8900) -> Result<(Vec<u8>, Vec<f32>), String> {
8901 let local_in = matrix.in_features / tp;
8902 let col_start = rank * local_in;
8903 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8904 for row in 0..matrix.out_features {
8905 let start = row * matrix.in_features + col_start;
8906 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8907 }
8908
8909 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8910 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8911 let local_scale_cols = local_in / FP8_BLOCK;
8912 let scale_col_start = rank * local_scale_cols;
8913 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8914 for row in 0..scale_rows {
8915 let start = row * scale_cols + scale_col_start;
8916 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8917 }
8918 Ok((codes, scales))
8919}
8920
8921fn activation_shard(
8922 activations: &[f32],
8923 tokens: usize,
8924 in_features: usize,
8925 tp: usize,
8926 rank: usize,
8927) -> Vec<f32> {
8928 let local_in = in_features / tp;
8929 let col_start = rank * local_in;
8930 let mut shard = Vec::with_capacity(tokens * local_in);
8931 for token in 0..tokens {
8932 let start = token * in_features + col_start;
8933 shard.extend_from_slice(&activations[start..start + local_in]);
8934 }
8935 shard
8936}
8937
8938#[derive(Clone, Copy)]
8958pub struct Nvfp4BlockMatrix<'a> {
8959 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8963 pub in_features: usize,
8964}
8965
8966impl Nvfp4BlockMatrix<'_> {
8967 pub fn validate(&self) -> Result<(), String> {
8968 if self.in_features == 0 || self.out_features == 0 {
8969 return Err("NVFP4 matrix has a zero dimension".to_string());
8970 }
8971 if self.in_features % 64 != 0 {
8972 return Err(format!(
8973 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8974 self.in_features
8975 ));
8976 }
8977 if self.codes.len() != self.out_features * self.in_features / 2 {
8978 return Err(format!(
8979 "NVFP4 code bytes {} != {}x{}/2",
8980 self.codes.len(),
8981 self.out_features,
8982 self.in_features
8983 ));
8984 }
8985 if self.scales.len() != self.out_features * self.in_features / 16 {
8986 return Err(format!(
8987 "NVFP4 scale bytes {} != {}x{}/16",
8988 self.scales.len(),
8989 self.out_features,
8990 self.in_features
8991 ));
8992 }
8993 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8994 return Err(format!(
8995 "NVFP4 macro scale {} is not finite-positive",
8996 self.macro_scale
8997 ));
8998 }
8999 Ok(())
9000 }
9001}
9002
9003#[derive(Clone, Copy)]
9005pub struct Nvfp4ExpertBank<'a> {
9006 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9010 pub out_features: usize,
9011 pub in_features: usize,
9012}
9013
9014impl Nvfp4ExpertBank<'_> {
9015 pub fn validate(&self) -> Result<(), String> {
9016 if self.expert_count == 0 {
9017 return Err("NVFP4 expert bank is empty".to_string());
9018 }
9019 if self.macros.len() != self.expert_count {
9020 return Err(format!(
9021 "NVFP4 bank macros {} != expert count {}",
9022 self.macros.len(),
9023 self.expert_count
9024 ));
9025 }
9026 self.expert(0).map(|_| ())
9027 }
9028
9029 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9030 if expert >= self.expert_count {
9031 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9032 }
9033 let code_stride = self.out_features * self.in_features / 2;
9034 let scale_stride = self.out_features * self.in_features / 16;
9035 if self.codes.len() != self.expert_count * code_stride
9036 || self.scales.len() != self.expert_count * scale_stride
9037 {
9038 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9039 }
9040 let matrix = Nvfp4BlockMatrix {
9041 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9042 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9043 macro_scale: self.macros[expert],
9044 out_features: self.out_features,
9045 in_features: self.in_features,
9046 };
9047 matrix.validate()?;
9048 Ok(matrix)
9049 }
9050}
9051
9052pub struct ResidentNvfp4Rank {
9054 blocks: crate::CudaSlice<u8>,
9055 macro_scale: f32,
9056 out_features: usize,
9057 in_features: usize,
9058 row_bytes: usize,
9059}
9060
9061pub struct ResidentNvfp4ColumnParallel {
9062 ranks: Vec<ResidentNvfp4Rank>,
9063 pub out_features: usize,
9064 pub in_features: usize,
9065}
9066
9067pub struct ResidentNvfp4RowParallel {
9068 ranks: Vec<ResidentNvfp4Rank>,
9069 pub out_features: usize,
9070 pub in_features: usize,
9071}
9072
9073pub struct ResidentTpNvfp4Expert {
9074 gate: ResidentNvfp4ColumnParallel,
9075 up: ResidentNvfp4ColumnParallel,
9076 down: ResidentNvfp4RowParallel,
9077 pub input_width: usize,
9078 pub expert_width: usize,
9079}
9080
9081pub struct ResidentNvfp4ColumnBankRank {
9085 bank: crate::CudaSlice<u8>,
9089 expert_bytes: usize,
9090 local_out: usize,
9091 in_features: usize,
9092 row_bytes: usize,
9093}
9094
9095impl ResidentNvfp4ColumnBankRank {
9096 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9097 self.bank
9098 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9099 }
9100}
9101
9102pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9108
9109pub struct ResidentNvfp4RowBankRank {
9110 bank: crate::CudaSlice<u8>,
9112 expert_bytes: usize,
9113 device_rank: usize, out_features: usize,
9115 local_in: usize,
9116 row_bytes: usize,
9117}
9118
9119impl ResidentNvfp4RowBankRank {
9120 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9121 self.bank
9122 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9123 }
9124}
9125
9126impl ResidentNvfp4TensorParallel {
9127 pub(crate) fn device_workspace_handle(
9128 &self,
9129 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
9130 &self.device_workspace
9131 }
9132}
9133
9134pub struct ResidentNvfp4TensorParallel {
9135 gate: Vec<ResidentNvfp4ColumnBankRank>,
9136 up: Vec<ResidentNvfp4ColumnBankRank>,
9137 down: Vec<ResidentNvfp4RowBankRank>,
9138 macros_gate: Vec<f32>,
9139 macros_up: Vec<f32>,
9140 macros_down: Vec<f32>,
9141 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
9145 macros_up_dev: Vec<crate::CudaSlice<f32>>,
9146 macros_down_dev: Vec<crate::CudaSlice<f32>>,
9147 pub expert_count: usize,
9148 pub input_width: usize,
9149 pub expert_width: usize,
9150 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
9153 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
9157 pub(crate) ep2: bool,
9161}
9162
9163struct RoutesGraph {
9170 exec: cudarc::driver::sys::CUgraphExec,
9171 parent: cudarc::driver::sys::CUgraph,
9172 _children: Vec<cudarc::driver::CudaGraph>,
9173}
9174unsafe impl Send for RoutesGraph {}
9177
9178impl Drop for RoutesGraph {
9179 fn drop(&mut self) {
9180 unsafe {
9181 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
9182 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
9183 }
9184 }
9185}
9186
9187impl Nvfp4DeviceRoutesWorkspace {
9188 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
9189 self.in_stage_e.as_ref()
9190 }
9191 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9192 self.in_stage_e.as_mut()
9193 }
9194 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9195 self.out_stage_e.as_mut()
9196 }
9197 pub(crate) fn arm_stages(
9199 &mut self,
9200 e: &Engine,
9201 width: usize,
9202 n_sel: usize,
9203 ) -> Result<(), Box<dyn std::error::Error>> {
9204 let _main = e.gpu.enter_main()?;
9205 if self.in_stage_e.is_none() {
9206 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9207 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9208 }
9209 if self.dev_route_e.is_none() {
9210 self.dev_route_e = Some((
9211 e.htod_i32(&vec![0i32; n_sel])?,
9212 e.htod(&vec![0.0f32; n_sel])?,
9213 ));
9214 }
9215 Ok(())
9216 }
9217
9218 pub(crate) fn in_and_out_stages_mut(
9220 &mut self,
9221 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9222 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9223 (Some(input), Some(output)) => Some((input, output)),
9224 _ => None,
9225 }
9226 }
9227 pub(crate) fn dev_route_e_mut(
9228 &mut self,
9229 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9230 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9231 }
9232}
9233
9234pub struct Nvfp4DeviceRoutesWorkspace {
9235 gate_out: Vec<crate::CudaSlice<f32>>,
9238 up_out: Vec<crate::CudaSlice<f32>>,
9239 act_q: Vec<crate::CudaSlice<i8>>,
9240 act_d: Vec<crate::CudaSlice<f32>>,
9241 sel: Vec<crate::CudaSlice<i32>>,
9242 partial: Vec<crate::CudaSlice<f32>>,
9243 accumulator: Vec<crate::CudaSlice<f32>>,
9244 combine_w: Vec<crate::CudaSlice<f32>>,
9246 route_w: Vec<crate::CudaSlice<f32>>,
9249 in_q: Vec<crate::CudaSlice<i8>>,
9252 in_d: Vec<crate::CudaSlice<f32>>,
9253 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9257 prestaged: bool,
9260 rank1_routed: bool,
9263 fence_flags_raw: u64,
9267 fence_ticket: u32,
9268 ev_input: Option<(CudaEvent, usize)>,
9270 in_stage_e: Option<crate::CudaSlice<f32>>,
9273 out_stage_e: Option<crate::CudaSlice<f32>>,
9274 routes_graph: Option<RoutesGraph>,
9275 raw_dev_route_e: Option<(u64, u64)>,
9277 raw_combine: Option<(u64, u64, u64, u64)>,
9278 raw_input: Vec<u64>,
9279 raw_sel: Vec<u64>,
9280 raw_route_w: Vec<u64>,
9281 remote: crate::CudaSlice<f32>,
9282 combined: crate::CudaSlice<f32>,
9283 n_sel: usize,
9284 input: Vec<crate::CudaSlice<f32>>,
9288 ev_rank: Vec<CudaEvent>,
9289 ev_done: Option<CudaEvent>,
9290 ev_entry: Option<(CudaEvent, usize)>,
9291}
9292
9293struct ResidentNvfp4EpRank {
9295 gate: Vec<crate::CudaSlice<u8>>,
9296 up: Vec<crate::CudaSlice<u8>>,
9297 down: Vec<crate::CudaSlice<u8>>,
9298 #[allow(dead_code)]
9299 expert_range: Range<usize>,
9300}
9301
9302pub struct ResidentNvfp4ExpertParallel {
9303 ranks: Vec<ResidentNvfp4EpRank>,
9304 macros_gate: Vec<f32>,
9305 macros_up: Vec<f32>,
9306 macros_down: Vec<f32>,
9307 pub expert_count: usize,
9308 pub input_width: usize,
9309 pub expert_width: usize,
9310 gate_row_bytes: usize,
9311 down_row_bytes: usize,
9312}
9313
9314fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9315 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9316 matrix.codes,
9317 matrix.scales,
9318 matrix.out_features,
9319 matrix.in_features,
9320 )
9321}
9322
9323fn nvfp4_row_bytes(in_features: usize) -> usize {
9324 in_features / 64 * 36 }
9326
9327pub(crate) fn fuse_rope_append_on() -> bool {
9333 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9334 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9335}
9336
9337pub(crate) fn no_local_shadow_on() -> bool {
9338 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9339 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9340}
9341
9342fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9350 let row_bytes = nvfp4_row_bytes(in_features);
9351 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
9352 let n_slots = in_features / 32;
9353 let mut out = Vec::with_capacity(v1.len());
9354 for row in 0..out_features {
9355 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
9356 for g in 0..n_slots {
9357 let (sblk, h) = (g / 2, g % 2);
9358 let b = &r[sblk * 36..sblk * 36 + 36];
9359 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
9360 }
9361 for g in 0..n_slots {
9362 let (sblk, h) = (g / 2, g % 2);
9363 let b = &r[sblk * 36..sblk * 36 + 36];
9364 out.push(b[2 * h]);
9365 out.push(b[2 * h + 1]);
9366 }
9367 }
9368 out
9369}
9370
9371fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>, slot_major: bool) -> Vec<u8> {
9375 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9376 let v1 = nvfp4_repack_matrix(matrix);
9377 if slot_major {
9378 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9379 } else {
9380 v1
9381 }
9382}
9383
9384fn nvfp4_column_shard<'a>(
9387 matrix: Nvfp4BlockMatrix<'a>,
9388 tp: usize,
9389 rank: usize,
9390) -> Result<Nvfp4BlockMatrix<'a>, String> {
9391 if matrix.out_features % tp != 0 {
9392 return Err(format!(
9393 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
9394 matrix.out_features
9395 ));
9396 }
9397 let local_out = matrix.out_features / tp;
9398 let code_row = matrix.in_features / 2;
9399 let scale_row = matrix.in_features / 16;
9400 Ok(Nvfp4BlockMatrix {
9401 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
9402 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
9403 macro_scale: matrix.macro_scale,
9404 out_features: local_out,
9405 in_features: matrix.in_features,
9406 })
9407}
9408
9409fn nvfp4_row_shard(
9412 matrix: Nvfp4BlockMatrix<'_>,
9413 tp: usize,
9414 rank: usize,
9415) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
9416 if matrix.in_features % tp != 0 {
9417 return Err(format!(
9418 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
9419 matrix.in_features
9420 ));
9421 }
9422 let local_in = matrix.in_features / tp;
9423 if local_in % 64 != 0 {
9424 return Err(format!(
9425 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
9426 ));
9427 }
9428 let code_row = matrix.in_features / 2;
9429 let scale_row = matrix.in_features / 16;
9430 let local_code = local_in / 2;
9431 let local_scale = local_in / 16;
9432 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
9433 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
9434 for row in 0..matrix.out_features {
9435 let code_start = row * code_row + rank * local_code;
9436 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
9437 let scale_start = row * scale_row + rank * local_scale;
9438 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
9439 }
9440 Ok((codes, scales, local_in))
9441}
9442
9443fn run_rank_nvfp4(
9447 engine: &Engine,
9448 matrix: Nvfp4BlockMatrix<'_>,
9449 activations: &[f32],
9450 tokens: usize,
9451) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9452 matrix.validate()?;
9453 validate_activations(activations, tokens, matrix.in_features)?;
9454 let _main = engine.gpu.enter_main()?;
9455 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
9456 let activations = engine.htod(activations)?;
9457 let output = engine.qmatvec_nvfp4_fast(
9458 &blocks.slice(0..blocks.len()),
9459 &activations,
9460 tokens,
9461 matrix.in_features,
9462 matrix.out_features,
9463 nvfp4_row_bytes(matrix.in_features),
9464 )?;
9465 engine.dtoh(&output)
9466}
9467
9468fn upload_rank_nvfp4(
9469 engine: &Engine,
9470 matrix: Nvfp4BlockMatrix<'_>,
9471) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
9472 matrix.validate()?;
9473 let _main = engine.gpu.enter_main()?;
9474 Ok(ResidentNvfp4Rank {
9475 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
9476 macro_scale: matrix.macro_scale,
9477 out_features: matrix.out_features,
9478 in_features: matrix.in_features,
9479 row_bytes: nvfp4_row_bytes(matrix.in_features),
9480 })
9481}
9482
9483fn run_resident_rank_nvfp4(
9484 engine: &Engine,
9485 rank: &ResidentNvfp4Rank,
9486 activations: &[f32],
9487 tokens: usize,
9488) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9489 validate_activations(activations, tokens, rank.in_features)?;
9490 let _main = engine.gpu.enter_main()?;
9491 let activations = engine.htod(activations)?;
9492 let output = engine.qmatvec_nvfp4_fast(
9493 &rank.blocks.slice(0..rank.blocks.len()),
9494 &activations,
9495 tokens,
9496 rank.in_features,
9497 rank.out_features,
9498 rank.row_bytes,
9499 )?;
9500 engine.dtoh(&output)
9501}
9502
9503fn apply_macro(values: &mut [f32], macro_scale: f32) {
9504 for value in values.iter_mut() {
9505 *value *= macro_scale;
9506 }
9507}
9508
9509impl TpE4m3HostBounce {
9510 pub fn full_nvfp4(
9512 &self,
9513 matrix: Nvfp4BlockMatrix<'_>,
9514 activations: &[f32],
9515 tokens: usize,
9516 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9517 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
9518 apply_macro(&mut output, matrix.macro_scale);
9519 Ok(output)
9520 }
9521
9522 pub fn column_parallel_nvfp4(
9525 &self,
9526 matrix: Nvfp4BlockMatrix<'_>,
9527 activations: &[f32],
9528 tokens: usize,
9529 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
9530 matrix.validate()?;
9531 validate_activations(activations, tokens, matrix.in_features)?;
9532 let tp = self.ranks.len();
9533 let local_out = matrix.out_features / tp;
9534 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9535 let mut rank_outputs = Vec::with_capacity(tp);
9536 for (rank_index, rank) in self.ranks.iter().enumerate() {
9537 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
9538 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
9539 let row_start = rank_index * local_out;
9540 for token in 0..tokens {
9541 gathered[token * matrix.out_features + row_start
9542 ..token * matrix.out_features + row_start + local_out]
9543 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9544 }
9545 rank_outputs.push(output);
9546 }
9547 apply_macro(&mut gathered, matrix.macro_scale);
9548 Ok(ColumnParallelResult {
9549 gathered,
9550 rank_outputs,
9551 })
9552 }
9553
9554 pub fn row_parallel_nvfp4(
9557 &self,
9558 matrix: Nvfp4BlockMatrix<'_>,
9559 activations: &[f32],
9560 tokens: usize,
9561 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
9562 matrix.validate()?;
9563 validate_activations(activations, tokens, matrix.in_features)?;
9564 let tp = self.ranks.len();
9565 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9566 let mut rank_partials = Vec::with_capacity(tp);
9567 for (rank_index, rank) in self.ranks.iter().enumerate() {
9568 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
9569 let local_activations =
9570 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9571 let shard = Nvfp4BlockMatrix {
9572 codes: &codes,
9573 scales: &scales,
9574 macro_scale: matrix.macro_scale,
9575 out_features: matrix.out_features,
9576 in_features: local_in,
9577 };
9578 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
9579 for (sum, value) in reduced.iter_mut().zip(&partial) {
9580 *sum += *value;
9581 }
9582 rank_partials.push(partial);
9583 }
9584 apply_macro(&mut reduced, matrix.macro_scale);
9585 Ok(RowParallelResult {
9586 reduced,
9587 rank_partials,
9588 })
9589 }
9590
9591 pub fn upload_expert_nvfp4(
9592 &self,
9593 gate: Nvfp4BlockMatrix<'_>,
9594 up: Nvfp4BlockMatrix<'_>,
9595 down: Nvfp4BlockMatrix<'_>,
9596 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
9597 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9598 return Err("NVFP4 TP expert gate/up dimensions differ".into());
9599 }
9600 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9601 return Err(format!(
9602 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
9603 down.out_features, down.in_features, gate.out_features, gate.in_features
9604 )
9605 .into());
9606 }
9607 let tp = self.ranks.len();
9608 let mut gate_ranks = Vec::with_capacity(tp);
9609 let mut up_ranks = Vec::with_capacity(tp);
9610 let mut down_ranks = Vec::with_capacity(tp);
9611 for (rank_index, engine) in self.ranks.iter().enumerate() {
9612 gate_ranks.push(upload_rank_nvfp4(
9613 engine,
9614 nvfp4_column_shard(gate, tp, rank_index)?,
9615 )?);
9616 up_ranks.push(upload_rank_nvfp4(
9617 engine,
9618 nvfp4_column_shard(up, tp, rank_index)?,
9619 )?);
9620 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
9621 down_ranks.push(upload_rank_nvfp4(
9622 engine,
9623 Nvfp4BlockMatrix {
9624 codes: &codes,
9625 scales: &scales,
9626 macro_scale: down.macro_scale,
9627 out_features: down.out_features,
9628 in_features: local_in,
9629 },
9630 )?);
9631 }
9632 Ok(ResidentTpNvfp4Expert {
9633 gate: ResidentNvfp4ColumnParallel {
9634 ranks: gate_ranks,
9635 out_features: gate.out_features,
9636 in_features: gate.in_features,
9637 },
9638 up: ResidentNvfp4ColumnParallel {
9639 ranks: up_ranks,
9640 out_features: up.out_features,
9641 in_features: up.in_features,
9642 },
9643 down: ResidentNvfp4RowParallel {
9644 ranks: down_ranks,
9645 out_features: down.out_features,
9646 in_features: down.in_features,
9647 },
9648 input_width: gate.in_features,
9649 expert_width: gate.out_features,
9650 })
9651 }
9652
9653 fn column_parallel_resident_nvfp4(
9654 &self,
9655 matrix: &ResidentNvfp4ColumnParallel,
9656 activations: &[f32],
9657 tokens: usize,
9658 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9659 validate_activations(activations, tokens, matrix.in_features)?;
9660 let local_out = matrix.out_features / self.ranks.len();
9661 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9662 let mut macro_scale = None;
9663 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9664 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
9665 let row_start = rank_index * local_out;
9666 for token in 0..tokens {
9667 gathered[token * matrix.out_features + row_start
9668 ..token * matrix.out_features + row_start + local_out]
9669 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9670 }
9671 macro_scale = Some(shard.macro_scale);
9672 }
9673 apply_macro(
9674 &mut gathered,
9675 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
9676 );
9677 Ok(gathered)
9678 }
9679
9680 fn row_parallel_resident_nvfp4(
9681 &self,
9682 matrix: &ResidentNvfp4RowParallel,
9683 activations: &[f32],
9684 tokens: usize,
9685 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9686 validate_activations(activations, tokens, matrix.in_features)?;
9687 let tp = self.ranks.len();
9688 let local_in = matrix.in_features / tp;
9689 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9690 let mut macro_scale = None;
9691 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9692 if shard.in_features != local_in {
9693 return Err(format!(
9694 "NVFP4 resident row shard in_features {} != expected {local_in}",
9695 shard.in_features
9696 )
9697 .into());
9698 }
9699 let local_activations =
9700 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9701 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9702 for (sum, value) in reduced.iter_mut().zip(&partial) {
9703 *sum += *value;
9704 }
9705 macro_scale = Some(shard.macro_scale);
9706 }
9707 apply_macro(
9708 &mut reduced,
9709 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9710 );
9711 Ok(reduced)
9712 }
9713
9714 pub fn run_expert_nvfp4(
9715 &self,
9716 expert: &ResidentTpNvfp4Expert,
9717 input: &[f32],
9718 tokens: usize,
9719 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9720 validate_activations(input, tokens, expert.input_width)?;
9721 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9722 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9723 let activated: Vec<f32> = gate
9724 .iter()
9725 .zip(&up)
9726 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9727 .collect();
9728 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9729 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9730 }
9731
9732 pub fn upload_tensor_parallel_nvfp4(
9734 &self,
9735 gate: Nvfp4ExpertBank<'_>,
9736 up: Nvfp4ExpertBank<'_>,
9737 down: Nvfp4ExpertBank<'_>,
9738 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9739 gate.validate()?;
9740 up.validate()?;
9741 down.validate()?;
9742 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9743 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9744 }
9745 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9746 return Err("NVFP4 TP gate/up dimensions differ".into());
9747 }
9748 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9749 return Err(format!(
9750 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9751 down.out_features, down.in_features, gate.out_features, gate.in_features
9752 )
9753 .into());
9754 }
9755 let tp = self.ranks.len();
9756 if gate.out_features % tp != 0 {
9757 return Err(format!(
9758 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9759 gate.out_features
9760 )
9761 .into());
9762 }
9763 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9764 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9765 {
9766 return Err(format!(
9767 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9768 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9769 down.in_features
9770 )
9771 .into());
9772 }
9773 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9774 return Err(format!(
9775 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9776 ({NVFP4_CANONICAL_ROW_SHARDS})"
9777 )
9778 .into());
9779 }
9780
9781 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9782 let mut gate_ranks = Vec::with_capacity(tp);
9783 let mut up_ranks = Vec::with_capacity(tp);
9784 let mut macros_gate_dev = Vec::with_capacity(tp);
9785 let mut macros_up_dev = Vec::with_capacity(tp);
9786 let mut macros_down_dev = Vec::with_capacity(tp);
9787 for (rank_index, engine) in self.ranks.iter().enumerate() {
9788 let _main = engine.gpu.enter_main()?;
9789 let mut gate_host: Vec<u8> = Vec::new();
9795 let mut up_host: Vec<u8> = Vec::new();
9796 let mut owned = 0usize;
9797 for expert in 0..gate.expert_count {
9798 if ep2 {
9799 if expert % 2 != rank_index {
9800 continue;
9801 }
9802 owned += 1;
9803 gate_host
9804 .extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?, true));
9805 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?, true));
9806 } else {
9807 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9808 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard, false));
9809 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9810 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard, false));
9811 }
9812 }
9813 let bank_experts = if ep2 { owned } else { gate.expert_count };
9814 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9815 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9816 let local_out = if ep2 {
9817 gate.out_features
9818 } else {
9819 gate.out_features / tp
9820 };
9821 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9822 bank: engine.htod_bytes(&gate_host)?,
9823 expert_bytes: gate_expert_bytes,
9824 local_out,
9825 in_features: gate.in_features,
9826 row_bytes: nvfp4_row_bytes(gate.in_features),
9827 });
9828 up_ranks.push(ResidentNvfp4ColumnBankRank {
9829 bank: engine.htod_bytes(&up_host)?,
9830 expert_bytes: up_expert_bytes,
9831 local_out,
9832 in_features: up.in_features,
9833 row_bytes: nvfp4_row_bytes(up.in_features),
9834 });
9835 macros_gate_dev.push(engine.htod(gate.macros)?);
9836 macros_up_dev.push(engine.htod(up.macros)?);
9837 macros_down_dev.push(engine.htod(down.macros)?);
9838 }
9839 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9843 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9844 let device_rank = shard_index % tp;
9845 let engine = &self.ranks[device_rank];
9846 let _main = engine.gpu.enter_main()?;
9847 let mut down_host: Vec<u8> = Vec::new();
9848 let mut owned = 0usize;
9849 for expert in 0..down.expert_count {
9850 let down_matrix = down.expert(expert)?;
9851 if ep2 {
9852 if expert % 2 != device_rank {
9855 continue;
9856 }
9857 owned += 1;
9858 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix, true));
9859 } else {
9860 let (codes, scales, local_in) =
9861 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9862 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(
9863 Nvfp4BlockMatrix {
9864 codes: &codes,
9865 scales: &scales,
9866 macro_scale: down_matrix.macro_scale,
9867 out_features: down_matrix.out_features,
9868 in_features: local_in,
9869 },
9870 false,
9871 ));
9872 }
9873 }
9874 let bank_experts = if ep2 { owned } else { down.expert_count };
9875 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9876 let local_in = if ep2 {
9877 down.in_features
9878 } else {
9879 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9880 };
9881 down_ranks.push(ResidentNvfp4RowBankRank {
9882 bank: engine.htod_bytes(&down_host)?,
9883 expert_bytes: down_expert_bytes,
9884 device_rank,
9885 out_features: down.out_features,
9886 local_in,
9887 row_bytes: nvfp4_row_bytes(local_in),
9888 });
9889 }
9890 Ok(ResidentNvfp4TensorParallel {
9891 gate: gate_ranks,
9892 up: up_ranks,
9893 down: down_ranks,
9894 macros_gate: gate.macros.to_vec(),
9895 macros_up: up.macros.to_vec(),
9896 macros_down: down.macros.to_vec(),
9897 macros_gate_dev,
9898 macros_up_dev,
9899 macros_down_dev,
9900 expert_count: gate.expert_count,
9901 input_width: gate.in_features,
9902 expert_width: gate.out_features,
9903 device_workspace: std::sync::Mutex::new(None),
9904 prime_tables: std::sync::Mutex::new(Vec::new()),
9905 ep2,
9906 })
9907 }
9908
9909 fn run_full_bank_expert_nvfp4(
9913 &self,
9914 ranks: &[ResidentNvfp4ColumnBankRank],
9915 macros: &[f32],
9916 expert: usize,
9917 input: &[f32],
9918 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9919 let owner = expert & 1;
9920 let slot = expert >> 1;
9921 let bank = ranks
9922 .get(owner)
9923 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9924 let engine = &self.ranks[owner];
9925 let _main = engine.gpu.enter_main()?;
9926 let activations = engine.htod(input)?;
9927 let output = engine.qmatvec_nvfp4_fast_v2(
9930 &bank.expert(slot),
9931 &activations,
9932 1,
9933 bank.in_features,
9934 bank.local_out,
9935 bank.row_bytes,
9936 )?;
9937 let mut out = engine.dtoh(&output)?;
9938 apply_macro(&mut out, macros[expert]);
9939 Ok(out)
9940 }
9941
9942 fn run_full_down_expert_nvfp4(
9945 &self,
9946 shards: &[ResidentNvfp4RowBankRank],
9947 macros: &[f32],
9948 expert: usize,
9949 input: &[f32],
9950 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9951 let owner = expert & 1;
9952 let slot = expert >> 1;
9953 let shard = shards
9954 .get(owner)
9955 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9956 let engine = &self.ranks[owner];
9957 let _main = engine.gpu.enter_main()?;
9958 let activations = engine.htod(input)?;
9959 let output = engine.qmatvec_nvfp4_fast_v2(
9961 &shard.expert(slot),
9962 &activations,
9963 1,
9964 shard.local_in,
9965 shard.out_features,
9966 shard.row_bytes,
9967 )?;
9968 let mut out = engine.dtoh(&output)?;
9969 apply_macro(&mut out, macros[expert]);
9970 Ok(out)
9971 }
9972
9973 fn run_column_bank_expert_nvfp4(
9974 &self,
9975 ranks: &[ResidentNvfp4ColumnBankRank],
9976 macros: &[f32],
9977 expert: usize,
9978 input: &[f32],
9979 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9980 let local_out = ranks
9981 .first()
9982 .ok_or("NVFP4 TP column bank has no ranks")?
9983 .local_out;
9984 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9985 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9986 let _main = engine.gpu.enter_main()?;
9987 let activations = engine.htod(input)?;
9988 let output = engine.qmatvec_nvfp4_fast(
9989 &bank.expert(expert),
9990 &activations,
9991 1,
9992 bank.in_features,
9993 bank.local_out,
9994 bank.row_bytes,
9995 )?;
9996 let output = engine.dtoh(&output)?;
9997 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9998 }
9999 apply_macro(&mut gathered, macros[expert]);
10000 Ok(gathered)
10001 }
10002
10003 fn run_row_bank_expert_nvfp4(
10007 &self,
10008 shards: &[ResidentNvfp4RowBankRank],
10009 macros: &[f32],
10010 expert: usize,
10011 input: &[f32],
10012 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10013 let out_features = shards
10014 .first()
10015 .ok_or("NVFP4 TP row bank has no canonical shards")?
10016 .out_features;
10017 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
10018 let mut reduced = vec![0.0f32; out_features];
10019 for (shard_index, shard) in shards.iter().enumerate() {
10020 let engine = self
10021 .ranks
10022 .get(shard.device_rank)
10023 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
10024 let _main = engine.gpu.enter_main()?;
10025 let local_activations =
10026 activation_shard(input, 1, in_features, shards.len(), shard_index);
10027 let activations = engine.htod(&local_activations)?;
10028 let output = engine.qmatvec_nvfp4_fast(
10029 &shard.expert(expert),
10030 &activations,
10031 1,
10032 shard.local_in,
10033 shard.out_features,
10034 shard.row_bytes,
10035 )?;
10036 let partial = engine.dtoh(&output)?;
10037 for (sum, value) in reduced.iter_mut().zip(&partial) {
10038 *sum += *value;
10039 }
10040 }
10041 apply_macro(&mut reduced, macros[expert]);
10042 Ok(reduced)
10043 }
10044
10045 pub fn upload_expert_parallel_nvfp4(
10049 &self,
10050 gate: Nvfp4ExpertBank<'_>,
10051 up: Nvfp4ExpertBank<'_>,
10052 down: Nvfp4ExpertBank<'_>,
10053 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10054 gate.validate()?;
10055 up.validate()?;
10056 down.validate()?;
10057 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10058 return Err("NVFP4 EP gate/up/down expert counts differ".into());
10059 }
10060 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10061 return Err("NVFP4 EP gate/up dimensions differ".into());
10062 }
10063 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10064 return Err(format!(
10065 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
10066 down.out_features, down.in_features, gate.out_features, gate.in_features
10067 )
10068 .into());
10069 }
10070 let world = self.ranks.len();
10071 if gate.expert_count % world != 0 {
10072 return Err(format!(
10073 "NVFP4 EP expert count {} is not divisible by {world} ranks",
10074 gate.expert_count
10075 )
10076 .into());
10077 }
10078 let experts_per_rank = gate.expert_count / world;
10079 let mut ranks = Vec::with_capacity(world);
10080 for (rank_index, engine) in self.ranks.iter().enumerate() {
10081 let _main = engine.gpu.enter_main()?;
10082 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10083 let mut gate_experts = Vec::with_capacity(experts_per_rank);
10084 let mut up_experts = Vec::with_capacity(experts_per_rank);
10085 let mut down_experts = Vec::with_capacity(experts_per_rank);
10086 for expert in expert_range.clone() {
10087 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
10088 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
10089 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
10090 }
10091 ranks.push(ResidentNvfp4EpRank {
10092 gate: gate_experts,
10093 up: up_experts,
10094 down: down_experts,
10095 expert_range,
10096 });
10097 }
10098 Ok(ResidentNvfp4ExpertParallel {
10099 ranks,
10100 macros_gate: gate.macros.to_vec(),
10101 macros_up: up.macros.to_vec(),
10102 macros_down: down.macros.to_vec(),
10103 expert_count: gate.expert_count,
10104 input_width: gate.in_features,
10105 expert_width: gate.out_features,
10106 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
10107 down_row_bytes: nvfp4_row_bytes(down.in_features),
10108 })
10109 }
10110
10111 #[allow(clippy::too_many_arguments)]
10117 pub fn run_routed_experts_nvfp4(
10118 &self,
10119 experts: &ResidentNvfp4ExpertParallel,
10120 input: &[f32],
10121 tokens: usize,
10122 selected: &[usize],
10123 route_weights: &[f32],
10124 experts_per_token: usize,
10125 activation_limit: Option<f32>,
10126 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10127 validate_activations(input, tokens, experts.input_width)?;
10128 let pairs = tokens
10129 .checked_mul(experts_per_token)
10130 .ok_or("NVFP4 EP route count overflow")?;
10131 if selected.len() != pairs || route_weights.len() != pairs {
10132 return Err(format!(
10133 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
10134 {experts_per_token} ({pairs})",
10135 selected.len(),
10136 route_weights.len(),
10137 )
10138 .into());
10139 }
10140 if !route_weights.iter().all(|weight| weight.is_finite()) {
10141 return Err("NVFP4 EP route weights contain a non-finite value".into());
10142 }
10143 let experts_per_rank = experts.expert_count / experts.ranks.len();
10144 let mut output = vec![0.0f32; tokens * experts.input_width];
10145 for token in 0..tokens {
10146 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
10147 for slot in 0..experts_per_token {
10148 let pair = token * experts_per_token + slot;
10149 let expert = selected[pair];
10150 if expert >= experts.expert_count {
10151 return Err(format!(
10152 "NVFP4 EP selected expert {expert} outside 0..{}",
10153 experts.expert_count
10154 )
10155 .into());
10156 }
10157 let owner = expert / experts_per_rank;
10158 let local = expert - owner * experts_per_rank;
10159 let rank = &experts.ranks[owner];
10160 let engine = &self.ranks[owner];
10161 let _main = engine.gpu.enter_main()?;
10162 let device_input = engine.htod(input_row)?;
10163 let gate_out = engine.qmatvec_nvfp4_fast(
10164 &rank.gate[local].slice(0..rank.gate[local].len()),
10165 &device_input,
10166 1,
10167 experts.input_width,
10168 experts.expert_width,
10169 experts.gate_row_bytes,
10170 )?;
10171 let up_out = engine.qmatvec_nvfp4_fast(
10172 &rank.up[local].slice(0..rank.up[local].len()),
10173 &device_input,
10174 1,
10175 experts.input_width,
10176 experts.expert_width,
10177 experts.gate_row_bytes,
10178 )?;
10179 let mut gate_host = engine.dtoh(&gate_out)?;
10180 let mut up_host = engine.dtoh(&up_out)?;
10181 apply_macro(&mut gate_host, experts.macros_gate[expert]);
10182 apply_macro(&mut up_host, experts.macros_up[expert]);
10183 let activated: Vec<f32> = gate_host
10184 .iter()
10185 .zip(&up_host)
10186 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
10187 .collect();
10188 let device_activated = engine.htod(&activated)?;
10189 let down_out = engine.qmatvec_nvfp4_fast(
10190 &rank.down[local].slice(0..rank.down[local].len()),
10191 &device_activated,
10192 1,
10193 experts.expert_width,
10194 experts.input_width,
10195 experts.down_row_bytes,
10196 )?;
10197 let mut down_host = engine.dtoh(&down_out)?;
10198 apply_macro(&mut down_host, experts.macros_down[expert]);
10199 let weight = route_weights[pair];
10200 for (sum, value) in output
10201 [token * experts.input_width..(token + 1) * experts.input_width]
10202 .iter_mut()
10203 .zip(down_host)
10204 {
10205 *sum += weight * value;
10206 }
10207 }
10208 }
10209 Ok(output)
10210 }
10211
10212 pub fn run_tensor_parallel_routes_nvfp4_device(
10226 &self,
10227 experts: &ResidentNvfp4TensorParallel,
10228 input: &[f32],
10229 selected: &[usize],
10230 route_weights: &[f32],
10231 experts_per_token: usize,
10232 activation_limit: Option<f32>,
10233 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10234 validate_activations(input, 1, experts.input_width)?;
10235 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10236 return Err(format!(
10237 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
10238 selected.len(),
10239 route_weights.len(),
10240 )
10241 .into());
10242 }
10243 if !route_weights.iter().all(|weight| weight.is_finite()) {
10244 return Err("NVFP4 device route weights contain a non-finite value".into());
10245 }
10246 let world = self.ranks.len();
10247 if world != NVFP4_CANONICAL_ROW_SHARDS {
10248 return Err(format!(
10249 "NVFP4 device routes require world == canonical shard grid \
10250 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10251 )
10252 .into());
10253 }
10254 let local_out = if experts.ep2 {
10255 experts.expert_width
10256 } else {
10257 experts.expert_width / world
10258 };
10259
10260 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10264 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10265 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10266 let started = timing.then(std::time::Instant::now);
10267
10268 let n_sel = experts_per_token;
10269 let mut workspace_guard = experts
10270 .device_workspace
10271 .lock()
10272 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10273 if workspace_guard.is_none() {
10274 let mut gate_out = Vec::with_capacity(world);
10275 let mut up_out = Vec::with_capacity(world);
10276 let mut act_q = Vec::with_capacity(world);
10277 let mut act_d = Vec::with_capacity(world);
10278 let mut sel = Vec::with_capacity(world);
10279 let mut partial = Vec::with_capacity(world);
10280 let mut accumulator = Vec::with_capacity(world);
10281 let mut combine_w = Vec::with_capacity(world);
10282 let mut route_w = Vec::with_capacity(world);
10283 let mut in_q = Vec::with_capacity(world);
10284 let mut in_d = Vec::with_capacity(world);
10285 let mut input = Vec::with_capacity(world);
10286 let mut ev_rank = Vec::with_capacity(world);
10287 let moe_direct = moe_direct_on();
10288 for (rank, engine) in self.ranks.iter().enumerate() {
10289 let _main = engine.gpu.enter_main()?;
10290 gate_out.push(engine.uninit(n_sel * local_out)?);
10291 up_out.push(engine.uninit(n_sel * local_out)?);
10292 act_q.push(engine.uninit_i8(n_sel * local_out)?);
10293 act_d.push(engine.uninit(n_sel * local_out / 32)?);
10294 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
10295 partial.push(engine.uninit(n_sel * experts.input_width)?);
10296 if moe_direct && rank != 0 {
10298 let root = &self.ranks[0];
10299 let _root_main = root.gpu.enter_main()?;
10300 accumulator.push(root.zeros(experts.input_width)?);
10301 } else {
10302 accumulator.push(engine.zeros(experts.input_width)?);
10303 }
10304 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10305 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10306 in_q.push(engine.uninit_i8(experts.input_width)?);
10307 in_d.push(engine.uninit(experts.input_width / 32)?);
10308 input.push(engine.uninit(experts.input_width)?);
10309 ev_rank.push(engine.ctx().new_event(None)?);
10310 }
10311 let root = &self.ranks[0];
10312 let _main = root.gpu.enter_main()?;
10313 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
10314 prestaged: false,
10315 rank1_routed: false,
10316 ev_input: None,
10317 fence_flags_raw: 0,
10318 fence_ticket: 0,
10319 gate_out,
10320 up_out,
10321 act_q,
10322 act_d,
10323 sel,
10324 partial,
10325 accumulator,
10326 combine_w,
10327 route_w,
10328 in_q,
10329 in_d,
10330 dev_route_e: None,
10331 in_stage_e: None,
10332 out_stage_e: None,
10333 routes_graph: None,
10334 raw_dev_route_e: None,
10335 raw_combine: None,
10336 raw_input: Vec::new(),
10337 raw_sel: Vec::new(),
10338 raw_route_w: Vec::new(),
10339 remote: root.uninit(experts.input_width)?,
10340 combined: root.uninit(experts.input_width)?,
10341 n_sel,
10342 input,
10343 ev_rank,
10344 ev_done: Some(root.ctx().new_event(None)?),
10345 ev_entry: None,
10346 });
10347 }
10348 let workspace = workspace_guard
10349 .as_mut()
10350 .expect("NVFP4 device routes workspace initialized above");
10351 if experts.ep2 {
10354 return Ok(vec![0.0f32; experts.input_width]);
10355 }
10356 if workspace.n_sel != n_sel {
10357 return Err(format!(
10358 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10359 workspace.n_sel
10360 )
10361 .into());
10362 }
10363 for &expert in selected {
10364 if expert >= experts.expert_count {
10365 return Err(format!(
10366 "NVFP4 device selected expert {expert} outside 0..{}",
10367 experts.expert_count
10368 )
10369 .into());
10370 }
10371 }
10372 let sel_i32 = selected
10373 .iter()
10374 .map(|&expert| expert as i32)
10375 .collect::<Vec<_>>();
10376
10377 for (rank_index, engine) in self.ranks.iter().enumerate() {
10384 let _main = engine.gpu.enter_main()?;
10385 let device_input = engine.htod(input)?;
10386 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
10387 engine.quantize_q8_1_into(
10388 &device_input,
10389 1,
10390 experts.input_width,
10391 &mut in_q[rank_index],
10392 &mut in_d[rank_index],
10393 )?;
10394 }
10396 self.nvfp4_routes_batched_sweeps(
10397 experts,
10398 workspace,
10399 selected,
10400 route_weights,
10401 &sel_i32,
10402 local_out,
10403 n_sel,
10404 activation_limit,
10405 false,
10406 )?;
10407
10408 let root = &self.ranks[0];
10411 for engine in &self.ranks[1..] {
10412 let _main = engine.gpu.enter_main()?;
10413 engine.stream().synchronize()?;
10414 }
10415 let _main = root.gpu.enter_main()?;
10416 root.stream()
10417 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10418 root.add(
10419 &workspace.accumulator[0],
10420 &workspace.remote,
10421 &mut workspace.combined,
10422 experts.input_width,
10423 )?;
10424 let output = root.dtoh(&workspace.combined)?;
10425 if let Some(started) = started {
10426 use std::sync::atomic::Ordering;
10427 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10428 + started.elapsed().as_nanos() as u64;
10429 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10430 if calls % 430 == 0 {
10431 eprintln!(
10432 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10433 ns as f64 / 1.0e6,
10434 ns as f64 / calls as f64 / 1.0e3,
10435 );
10436 }
10437 }
10438 Ok(output)
10439 }
10440
10441 #[allow(clippy::too_many_arguments)]
10446 fn nvfp4_routes_batched_sweeps(
10447 &self,
10448 experts: &ResidentNvfp4TensorParallel,
10449 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10450 selected: &[usize],
10451 route_weights: &[f32],
10452 sel_i32: &[i32],
10453 local_out: usize,
10454 n_sel: usize,
10455 activation_limit: Option<f32>,
10456 device_routed: bool,
10457 ) -> Result<(), Box<dyn std::error::Error>> {
10458 for rank_index in 0..self.ranks.len() {
10459 self.nvfp4_routes_batched_sweeps_rank(
10460 experts,
10461 workspace,
10462 selected,
10463 route_weights,
10464 sel_i32,
10465 local_out,
10466 n_sel,
10467 activation_limit,
10468 device_routed,
10469 rank_index,
10470 )?;
10471 }
10472 Ok(())
10473 }
10474
10475 #[allow(clippy::too_many_arguments)]
10478 fn nvfp4_routes_batched_sweeps_rank(
10479 &self,
10480 experts: &ResidentNvfp4TensorParallel,
10481 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10482 selected: &[usize],
10483 route_weights: &[f32],
10484 sel_i32: &[i32],
10485 local_out: usize,
10486 n_sel: usize,
10487 activation_limit: Option<f32>,
10488 device_routed: bool,
10489 rank_index: usize,
10490 ) -> Result<(), Box<dyn std::error::Error>> {
10491 {
10492 let engine = &self.ranks[rank_index];
10493 let _main = engine.gpu.enter_main()?;
10494 if experts.ep2 {
10499 if !device_routed {
10500 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
10501 }
10502 let gate_bank = &experts.gate[rank_index];
10503 let up_bank = &experts.up[rank_index];
10504 if gate_bank.local_out != experts.expert_width
10505 || gate_bank.expert_bytes != up_bank.expert_bytes
10506 {
10507 return Err("NVFP4 EP2 bank geometry drifted".into());
10508 }
10509 {
10510 let Nvfp4DeviceRoutesWorkspace {
10511 sel,
10512 gate_out,
10513 up_out,
10514 in_q,
10515 in_d,
10516 ..
10517 } = &mut *workspace;
10518 engine.qmatvec_nvfp4_sel_gu_ep_into(
10519 &gate_bank.bank,
10520 &up_bank.bank,
10521 &sel[rank_index],
10522 &in_q[rank_index],
10523 &in_d[rank_index],
10524 &mut gate_out[rank_index],
10525 &mut up_out[rank_index],
10526 n_sel,
10527 gate_bank.in_features,
10528 gate_bank.local_out,
10529 gate_bank.row_bytes,
10530 gate_bank.expert_bytes,
10531 rank_index,
10532 )?;
10533 }
10534 {
10535 let Nvfp4DeviceRoutesWorkspace {
10536 gate_out,
10537 up_out,
10538 sel,
10539 act_q,
10540 act_d,
10541 ..
10542 } = &mut *workspace;
10543 engine.silu_mul_scaled_q8_1_sel_ep_into(
10544 &gate_out[rank_index],
10545 &up_out[rank_index],
10546 &experts.macros_gate_dev[rank_index],
10547 &experts.macros_up_dev[rank_index],
10548 &sel[rank_index],
10549 activation_limit,
10550 &mut act_q[rank_index],
10551 &mut act_d[rank_index],
10552 local_out,
10553 n_sel,
10554 rank_index,
10555 )?;
10556 }
10557 let shard = &experts.down[rank_index];
10558 if shard.device_rank != rank_index || shard.local_in != local_out {
10559 return Err("NVFP4 EP2 down bank placement drifted".into());
10560 }
10561 {
10562 let Nvfp4DeviceRoutesWorkspace {
10563 sel,
10564 act_q,
10565 act_d,
10566 route_w,
10567 accumulator,
10568 ..
10569 } = &mut *workspace;
10570 engine.qmatvec_nvfp4_sel_down8_ep_into(
10571 &shard.bank,
10572 &sel[rank_index],
10573 &act_q[rank_index],
10574 &act_d[rank_index],
10575 &route_w[rank_index],
10576 &experts.macros_down_dev[rank_index],
10577 &mut accumulator[rank_index],
10578 n_sel,
10579 shard.local_in,
10580 shard.out_features,
10581 shard.row_bytes,
10582 shard.expert_bytes,
10583 local_out,
10584 local_out / 32,
10585 rank_index,
10586 )?;
10587 }
10588 return Ok(());
10589 }
10590 if !device_routed {
10591 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
10592 let folded = (0..n_sel)
10595 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
10596 .collect::<Vec<_>>();
10597 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
10598 engine.stream().memcpy_htod(&folded, &mut view)?;
10599 }
10600 let gate_bank = &experts.gate[rank_index];
10601 let up_bank = &experts.up[rank_index];
10602 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
10603 engine.qmatvec_nvfp4_sel_into(
10604 &gate_bank.bank,
10605 &workspace.sel[rank_index],
10606 aq,
10607 ad,
10608 &mut workspace.gate_out[rank_index],
10609 n_sel,
10610 gate_bank.in_features,
10611 gate_bank.local_out,
10612 gate_bank.row_bytes,
10613 gate_bank.expert_bytes,
10614 0,
10615 0,
10616 )?;
10617 engine.qmatvec_nvfp4_sel_into(
10618 &up_bank.bank,
10619 &workspace.sel[rank_index],
10620 aq,
10621 ad,
10622 &mut workspace.up_out[rank_index],
10623 n_sel,
10624 up_bank.in_features,
10625 up_bank.local_out,
10626 up_bank.row_bytes,
10627 up_bank.expert_bytes,
10628 0,
10629 0,
10630 )?;
10631 {
10635 let Nvfp4DeviceRoutesWorkspace {
10636 gate_out,
10637 up_out,
10638 sel,
10639 act_q,
10640 act_d,
10641 ..
10642 } = &mut *workspace;
10643 engine.silu_mul_scaled_q8_1_sel_into(
10644 &gate_out[rank_index],
10645 &up_out[rank_index],
10646 &experts.macros_gate_dev[rank_index],
10647 &experts.macros_up_dev[rank_index],
10648 &sel[rank_index],
10649 activation_limit,
10650 &mut act_q[rank_index],
10651 &mut act_d[rank_index],
10652 local_out,
10653 n_sel,
10654 )?;
10655 }
10656 let shard = &experts.down[rank_index];
10657 if shard.device_rank != rank_index || shard.local_in != local_out {
10658 return Err(
10659 "NVFP4 device routes: down canonical shard placement drifted from \
10660 the gate/up column split"
10661 .into(),
10662 );
10663 }
10664 {
10665 let Nvfp4DeviceRoutesWorkspace {
10666 sel,
10667 act_q,
10668 act_d,
10669 partial,
10670 ..
10671 } = &mut *workspace;
10672 engine.qmatvec_nvfp4_sel_into(
10673 &shard.bank,
10674 &sel[rank_index],
10675 &act_q[rank_index],
10676 &act_d[rank_index],
10677 &mut partial[rank_index],
10678 n_sel,
10679 shard.local_in,
10680 shard.out_features,
10681 shard.row_bytes,
10682 shard.expert_bytes,
10683 local_out,
10684 local_out / 32,
10685 )?;
10686 }
10687 {
10691 let Nvfp4DeviceRoutesWorkspace {
10692 partial,
10693 combine_w,
10694 route_w,
10695 sel,
10696 accumulator,
10697 ..
10698 } = &mut *workspace;
10699 if device_routed {
10700 engine.axpy_rows_seq_md_into(
10701 &partial[rank_index],
10702 &route_w[rank_index],
10703 &experts.macros_down_dev[rank_index],
10704 &sel[rank_index],
10705 &mut accumulator[rank_index],
10706 experts.input_width,
10707 n_sel,
10708 )?;
10709 } else {
10710 engine.axpy_rows_seq_into(
10711 &partial[rank_index],
10712 &combine_w[rank_index],
10713 &mut accumulator[rank_index],
10714 experts.input_width,
10715 n_sel,
10716 )?;
10717 }
10718 }
10719 }
10720 Ok(())
10721 }
10722
10723 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10731 &self,
10732 experts: &ResidentNvfp4TensorParallel,
10733 e: &Engine,
10734 input_dev: &crate::CudaSlice<f32>,
10735 selected: &[usize],
10736 route_weights: &[f32],
10737 experts_per_token: usize,
10738 activation_limit: Option<f32>,
10739 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10740 if input_dev.len() != experts.input_width {
10741 return Err(format!(
10742 "NVFP4 device-io routes input {} != width {}",
10743 input_dev.len(),
10744 experts.input_width
10745 )
10746 .into());
10747 }
10748 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10749 return Err(format!(
10750 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10751 selected.len(),
10752 route_weights.len(),
10753 )
10754 .into());
10755 }
10756 if !route_weights.iter().all(|weight| weight.is_finite()) {
10757 return Err("NVFP4 device route weights contain a non-finite value".into());
10758 }
10759 let world = self.ranks.len();
10760 if world != NVFP4_CANONICAL_ROW_SHARDS {
10761 return Err(format!(
10762 "NVFP4 device routes require world == canonical shard grid \
10763 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10764 )
10765 .into());
10766 }
10767 let local_out = experts.expert_width / world;
10768 let n_sel = experts_per_token;
10769
10770 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10771 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10772 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10773 let started = timing.then(std::time::Instant::now);
10774
10775 let mut workspace_guard = experts
10776 .device_workspace
10777 .lock()
10778 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10779 if workspace_guard.is_none() {
10780 drop(workspace_guard);
10781 let zero = vec![0.0f32; experts.input_width];
10784 let zero_sel = vec![0usize; n_sel];
10785 let zero_w = vec![0.0f32; n_sel];
10786 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10787 experts,
10788 &zero,
10789 &zero_sel,
10790 &zero_w,
10791 n_sel,
10792 activation_limit,
10793 )?;
10794 workspace_guard = experts
10795 .device_workspace
10796 .lock()
10797 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10798 }
10799 let workspace = workspace_guard
10800 .as_mut()
10801 .expect("NVFP4 device routes workspace initialized above");
10802 if workspace.n_sel != n_sel {
10803 return Err(format!(
10804 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10805 workspace.n_sel
10806 )
10807 .into());
10808 }
10809 for &expert in selected {
10810 if expert >= experts.expert_count {
10811 return Err(format!(
10812 "NVFP4 device selected expert {expert} outside 0..{}",
10813 experts.expert_count
10814 )
10815 .into());
10816 }
10817 }
10818 let sel_i32 = selected
10819 .iter()
10820 .map(|&expert| expert as i32)
10821 .collect::<Vec<_>>();
10822
10823 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10827 if *device != e.ctx().ordinal() {
10828 return Err("NVFP4 device-io routes engine changed".into());
10829 }
10830 } else {
10831 let _main = e.gpu.enter_main()?;
10832 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10833 }
10834 {
10835 let _main = e.gpu.enter_main()?;
10836 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10837 ev_entry.record(&e.stream())?;
10838 }
10839 for (rank_index, engine) in self.ranks.iter().enumerate() {
10840 let _main = engine.gpu.enter_main()?;
10841 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10842 engine.stream().wait(ev_entry)?;
10843 {
10844 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10845 engine
10846 .stream()
10847 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10848 }
10849 {
10850 let Nvfp4DeviceRoutesWorkspace {
10851 input, in_q, in_d, ..
10852 } = &mut *workspace;
10853 engine.quantize_q8_1_into(
10854 &input[rank_index],
10855 1,
10856 experts.input_width,
10857 &mut in_q[rank_index],
10858 &mut in_d[rank_index],
10859 )?;
10860 }
10861 }
10862 self.nvfp4_routes_batched_sweeps(
10863 experts,
10864 workspace,
10865 selected,
10866 route_weights,
10867 &sel_i32,
10868 local_out,
10869 n_sel,
10870 activation_limit,
10871 false,
10872 )?;
10873
10874 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10880 let _main = engine.gpu.enter_main()?;
10881 workspace.ev_rank[rank_index].record(&engine.stream())?;
10882 }
10883 if moe_direct_on() && self.ranks.len() == 2 {
10884 {
10891 let root = &self.ranks[0];
10892 let _main = root.gpu.enter_main()?;
10893 workspace
10894 .ev_done
10895 .as_ref()
10896 .expect("device routes done event")
10897 .record(&root.stream())?;
10898 }
10899 let _main = e.gpu.enter_main()?;
10900 e.stream().wait(
10901 workspace
10902 .ev_done
10903 .as_ref()
10904 .expect("device routes done event"),
10905 )?;
10906 for ev in workspace.ev_rank.iter().skip(1) {
10907 e.stream().wait(ev)?;
10908 }
10909 let mut output = e.uninit(experts.input_width)?;
10910 e.add(
10911 &workspace.accumulator[0],
10912 &workspace.accumulator[1],
10913 &mut output,
10914 experts.input_width,
10915 )?;
10916 let output = output;
10917 if let Some(started) = started {
10918 use std::sync::atomic::Ordering;
10919 let ns = TIMING_NS
10920 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10921 + started.elapsed().as_nanos() as u64;
10922 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10923 if calls % 430 == 0 {
10924 eprintln!(
10925 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10926 ns as f64 / 1.0e6,
10927 ns as f64 / calls as f64 / 1.0e3,
10928 );
10929 }
10930 }
10931 return Ok(output);
10932 }
10933 {
10934 let root = &self.ranks[0];
10935 let _main = root.gpu.enter_main()?;
10936 for ev in workspace.ev_rank.iter().skip(1) {
10937 root.stream().wait(ev)?;
10938 }
10939 root.stream()
10940 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10941 {
10942 let Nvfp4DeviceRoutesWorkspace {
10943 accumulator,
10944 remote,
10945 combined,
10946 ..
10947 } = &mut *workspace;
10948 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10949 }
10950 workspace
10951 .ev_done
10952 .as_ref()
10953 .expect("device routes done event")
10954 .record(&root.stream())?;
10955 }
10956 let output = {
10957 let _main = e.gpu.enter_main()?;
10958 e.stream().wait(
10959 workspace
10960 .ev_done
10961 .as_ref()
10962 .expect("device routes done event"),
10963 )?;
10964 let mut output = e.uninit(experts.input_width)?;
10967 e.stream().memcpy_dtod(
10968 &workspace.combined.slice(0..experts.input_width),
10969 &mut output.slice_mut(0..experts.input_width),
10970 )?;
10971 output
10972 };
10973 if let Some(started) = started {
10974 use std::sync::atomic::Ordering;
10975 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10976 + started.elapsed().as_nanos() as u64;
10977 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10978 if calls % 430 == 0 {
10979 eprintln!(
10980 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10981 ns as f64 / 1.0e6,
10982 ns as f64 / calls as f64 / 1.0e3,
10983 );
10984 }
10985 }
10986 Ok(output)
10987 }
10988
10989 #[allow(clippy::too_many_arguments)]
10995 pub fn nvfp4_routes_prestage(
11000 &self,
11001 experts: &ResidentNvfp4TensorParallel,
11002 e: &Engine,
11003 input_dev: &crate::CudaSlice<f32>,
11004 ) -> Result<bool, Box<dyn std::error::Error>> {
11005 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
11006 }
11007
11008 pub fn nvfp4_routes_prestage_with(
11014 &self,
11015 experts: &ResidentNvfp4TensorParallel,
11016 e: &Engine,
11017 input_dev: &crate::CudaSlice<f32>,
11018 rank1_router: impl FnOnce(
11019 &Engine,
11020 &crate::CudaSlice<f32>,
11021 &mut crate::CudaSlice<i32>,
11022 &mut crate::CudaSlice<f32>,
11023 ) -> Result<bool, Box<dyn std::error::Error>>,
11024 ) -> Result<bool, Box<dyn std::error::Error>> {
11025 if !routes_prestage_on() || step_tp_graph_enabled()? {
11026 return Ok(false);
11027 }
11028 if input_dev.len() != experts.input_width {
11029 return Err("NVFP4 prestage input width mismatch".into());
11030 }
11031 let mut workspace_guard = experts
11032 .device_workspace
11033 .lock()
11034 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11035 let Some(workspace) = workspace_guard.as_mut() else {
11036 return Ok(false);
11037 };
11038 if workspace.ev_input.is_none() {
11039 let _main = e.gpu.enter_main()?;
11040 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11041 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
11042 return Err("NVFP4 prestage engine changed".into());
11043 }
11044 {
11045 let _main = e.gpu.enter_main()?;
11046 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
11047 ev.record(&e.stream())?;
11048 }
11049 for (rank_index, engine) in self.ranks.iter().enumerate() {
11050 let _main = engine.gpu.enter_main()?;
11051 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
11052 engine.stream().wait(ev)?;
11053 {
11054 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11055 engine
11056 .stream()
11057 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11058 }
11059 {
11060 let Nvfp4DeviceRoutesWorkspace {
11061 input, in_q, in_d, ..
11062 } = &mut *workspace;
11063 engine.quantize_q8_1_into(
11064 &input[rank_index],
11065 1,
11066 experts.input_width,
11067 &mut in_q[rank_index],
11068 &mut in_d[rank_index],
11069 )?;
11070 }
11071 }
11072 if self.ranks.len() == 2 {
11073 let rank1 = &self.ranks[1];
11074 let _r1 = rank1.gpu.enter_main()?;
11075 let Nvfp4DeviceRoutesWorkspace {
11076 input,
11077 sel,
11078 route_w,
11079 ..
11080 } = &mut *workspace;
11081 let (in1, rest_sel) = (&input[1], &mut sel[1]);
11082 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
11083 workspace.rank1_routed = true;
11084 }
11085 }
11086 workspace.prestaged = true;
11087 Ok(true)
11088 }
11089
11090 #[allow(clippy::too_many_arguments)]
11107 fn determ_stage_bytes(v: &[u8]) -> u64 {
11116 v.iter().fold(0u64, |a, b| {
11117 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
11118 })
11119 }
11120
11121 fn determ_stage_i32(v: &[i32]) -> u64 {
11126 v.iter().fold(0u64, |a, b| {
11127 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
11128 })
11129 }
11130
11131 fn determ_stage_sum(v: &[f32]) -> u64 {
11132 v.iter().fold(0u64, |a, x| {
11133 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
11134 })
11135 }
11136
11137 pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
11138 &self,
11139 experts: &ResidentNvfp4TensorParallel,
11140 e: &Engine,
11141 z_t: &crate::CudaSlice<f32>,
11142 t: usize,
11143 sel: &[i32],
11144 w: &[f32],
11145 n_used: usize,
11146 activation_limit: Option<f32>,
11147 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11148 let world = self.ranks.len();
11149 if world != NVFP4_CANONICAL_ROW_SHARDS {
11150 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
11151 }
11152 let bank_qt = if experts.ep2 {
11157 crate::QT_NVFP4_V2
11158 } else {
11159 crate::QT_NVFP4
11160 };
11161 let width = experts.input_width;
11162 let n_expert = experts.expert_count;
11163 let n_pairs = t * n_used;
11164 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
11165 return Err("NVFP4 grouped prime geometry".into());
11166 }
11167 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
11176 let g_t0 = std::time::Instant::now();
11177 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11180 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
11181 let s_id = s_id as usize;
11182 if s_id >= n_expert {
11183 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
11184 }
11185 buckets[s_id].push(p as i32);
11186 }
11187 let mut ex_ids: Vec<i32> = Vec::new();
11188 let mut ex_off: Vec<i32> = vec![0];
11189 let mut ex_pairs: Vec<i32> = Vec::new();
11190 for (e_id, b) in buckets.iter().enumerate() {
11191 if !b.is_empty() {
11192 ex_ids.push(e_id as i32);
11193 ex_pairs.extend_from_slice(b);
11194 ex_off.push(ex_pairs.len() as i32);
11195 }
11196 }
11197 let n_active = ex_ids.len();
11198 if n_active == 0 {
11199 return Ok(e.zeros(t * width)?);
11200 }
11201 if n_active > 512 {
11202 return Err("grouped prime n_active > 512 (direct lane cap)".into());
11203 }
11204 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11205 let mut inv = vec![0i32; n_pairs];
11208 for (row, &pair) in ex_pairs.iter().enumerate() {
11209 inv[pair as usize] = row as i32;
11210 }
11211 let mg: Vec<f32> = ex_pairs
11213 .iter()
11214 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
11215 .collect();
11216 let mu: Vec<f32> = ex_pairs
11217 .iter()
11218 .map(|&p| experts.macros_up[sel[p as usize] as usize])
11219 .collect();
11220 let wd: Vec<f32> = (0..n_pairs)
11221 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
11222 .collect();
11223 {
11227 let mut tabs = experts
11228 .prime_tables
11229 .lock()
11230 .map_err(|_| "grouped prime table cache is poisoned")?;
11231 if tabs.len() != world {
11232 tabs.clear();
11233 for rank in 0..world {
11234 let engine = &self.ranks[rank];
11235 let _main = engine.gpu.enter_main()?;
11236 let (gb, ub, db) =
11237 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
11238 let mut tab = vec![0u64; 3 * n_expert];
11239 {
11240 use cudarc::driver::DevicePtr;
11241 let stream = engine.stream();
11242 let (pg, _g0) = gb.bank.device_ptr(&stream);
11243 let (pu, _g1) = ub.bank.device_ptr(&stream);
11244 let (pd, _g2) = db.bank.device_ptr(&stream);
11245 for ex in 0..n_expert {
11246 tab[ex] = pg as u64 + (ex * gb.expert_bytes) as u64;
11247 tab[n_expert + ex] = pu as u64 + (ex * ub.expert_bytes) as u64;
11248 tab[2 * n_expert + ex] = pd as u64 + (ex * db.expert_bytes) as u64;
11249 }
11250 }
11251 tabs.push(engine.htod_u64(&tab)?);
11252 }
11253 }
11254 }
11255 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
11256 let g_t1 = std::time::Instant::now();
11257 {
11264 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
11265 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
11266 for rank in 0..world {
11267 let e_r = &self.ranks[rank];
11268 let _m = e_r.gpu.enter_main();
11269 eprintln!(
11270 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
11271 root_stream={:?}",
11272 e_r.ctx().ordinal(),
11273 std::sync::Arc::as_ptr(&e_r.ctx()),
11274 e_r.stream().cu_stream(),
11275 e.ctx().ordinal(),
11276 e.stream().cu_stream(),
11277 );
11278 }
11279 }
11280 }
11281
11282 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
11283 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
11284 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
11285 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
11286 for rank in 0..world {
11287 let engine = &self.ranks[rank];
11288 let _main = engine.gpu.enter_main()?;
11289 if gprof {
11290 let h = engine
11295 .ctx()
11296 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
11297 h.record(&engine.stream())?;
11298 ev_head.push(h);
11299 }
11300 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
11303 let gb = &experts.gate[rank];
11304 let ub = &experts.up[rank];
11305 let db = &experts.down[rank];
11306 if db.device_rank != rank {
11307 return Err("grouped prime: down shard placement drifted".into());
11308 }
11309 let local_ff = gb.local_out;
11310 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
11311 return Err("grouped prime: bank width mismatch".into());
11312 }
11313 let csr_tok_d = engine.htod_i32(&csr_tok)?;
11316 let exi_d = engine.htod_i32(&ex_ids)?;
11317 let exoff_d = engine.htod_i32(&ex_off)?;
11318 let mg_d = engine.htod(&mg)?;
11319 let mu_d = engine.htod(&mu)?;
11320 let tabs_guard = experts
11322 .prime_tables
11323 .lock()
11324 .map_err(|_| "grouped prime table cache is poisoned")?;
11325 let tab_d = &tabs_guard[rank];
11326 let mut z_r = engine.uninit(t * width)?;
11327 {
11328 let mut dst = z_r.slice_mut(0..t * width);
11329 engine
11330 .stream()
11331 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
11332 }
11333 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
11334 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
11335 if dstage {
11336 let zr = engine.dtoh(&z_r)?;
11340 let zsv = engine.dtoh(&zs)?;
11341 let z16v = engine.dtoh_u8(&z16)?;
11342 eprintln!(
11343 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
11344 Self::determ_stage_sum(&zr),
11345 Self::determ_stage_sum(&zsv),
11346 Self::determ_stage_bytes(&z16v)
11347 );
11348 }
11349 if dstage {
11350 engine.stream().synchronize()?;
11358 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
11359 let exi_v = engine.dtoh_i32(&exi_d)?;
11360 let exo_v = engine.dtoh_i32(&exoff_d)?;
11361 let mg_v = engine.dtoh(&mg_d)?;
11362 let mu_v = engine.dtoh(&mu_d)?;
11363 let tab_v = engine.dtoh_u64(tab_d)?;
11364 eprintln!(
11365 "[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={}",
11366 Self::determ_stage_i32(&csr_v),
11367 Self::determ_stage_i32(&exi_v),
11368 Self::determ_stage_i32(&exo_v),
11369 Self::determ_stage_i32(&ex_off),
11370 Self::determ_stage_sum(&mg_v),
11371 Self::determ_stage_sum(&mu_v),
11372 tab_v
11373 .iter()
11374 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
11375 gb.row_bytes
11376 );
11377 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
11380 let bank_v = engine.dtoh_u8(&gb.bank)?;
11381 eprintln!(
11382 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
11383 Self::determ_stage_bytes(&bank_v),
11384 bank_v.len()
11385 );
11386 }
11387 }
11388 let mut g = engine.moe_f16_grouped(
11389 tab_d,
11390 0,
11391 n_expert,
11392 &exi_d,
11393 &ex_off,
11394 &exoff_d,
11395 &z16,
11396 &zs,
11397 width,
11398 local_ff,
11399 n_active,
11400 n_pairs,
11401 bank_qt,
11402 gb.row_bytes,
11403 )?;
11404 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
11405 let mut u = engine.moe_f16_grouped(
11406 tab_d,
11407 1,
11408 n_expert,
11409 &exi_d,
11410 &ex_off,
11411 &exoff_d,
11412 &z16,
11413 &zs,
11414 width,
11415 local_ff,
11416 n_active,
11417 n_pairs,
11418 bank_qt,
11419 ub.row_bytes,
11420 )?;
11421 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
11422 let act = match activation_limit.filter(|l| *l > 1e-6) {
11426 Some(lim) => {
11427 let mut a = engine.uninit(n_pairs * local_ff)?;
11428 engine.swiglu_clamped_mul_scaled(
11429 &g,
11430 &u,
11431 1.0,
11432 1.0,
11433 lim,
11434 &mut a,
11435 n_pairs * local_ff,
11436 )?;
11437 a
11438 }
11439 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
11440 };
11441 if dstage {
11442 let gv = engine.dtoh(&g)?;
11443 let uv = engine.dtoh(&u)?;
11444 let av = engine.dtoh(&act)?;
11445 let key = (rank, t);
11450 let mut prev_map = DETERM_PREV
11451 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
11452 .lock()
11453 .map_err(|_| "determ prev map poisoned")?;
11454 let shape = match prev_map.get(&key) {
11455 Some(prev) if prev.len() == gv.len() => {
11456 let mut md = 0.0f32;
11457 let mut n_diff = 0usize;
11458 let mut n_big = 0usize;
11459 for (a, b) in prev.iter().zip(gv.iter()) {
11460 let d = (a - b).abs();
11461 if d > 0.0 {
11462 n_diff += 1;
11463 }
11464 if d > 1e-3 {
11465 n_big += 1;
11466 }
11467 if d > md {
11468 md = d;
11469 }
11470 }
11471 format!(
11472 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
11473 gv.len()
11474 )
11475 }
11476 _ => String::new(),
11477 };
11478 prev_map.insert(key, gv.clone());
11479 drop(prev_map);
11480 eprintln!(
11481 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
11482 Self::determ_stage_sum(&gv),
11483 Self::determ_stage_sum(&uv),
11484 Self::determ_stage_sum(&av)
11485 );
11486 }
11487 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
11488 let d_csr = engine.moe_f16_grouped(
11489 tab_d,
11490 2,
11491 n_expert,
11492 &exi_d,
11493 &ex_off,
11494 &exoff_d,
11495 &a16,
11496 &a_s,
11497 local_ff,
11498 width,
11499 n_active,
11500 n_pairs,
11501 bank_qt,
11502 db.row_bytes,
11503 )?;
11504
11505 if dstage {
11508 engine.stream().synchronize()?;
11509 let a16v = engine.dtoh_u8(&a16)?;
11510 let dv = engine.dtoh(&d_csr)?;
11511 eprintln!(
11512 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
11513 Self::determ_stage_bytes(&a16v),
11514 Self::determ_stage_sum(&dv)
11515 );
11516 }
11517 let ev = engine.ctx().new_event(None)?;
11518 ev.record(&engine.stream())?;
11519 if gprof {
11520 let tp = engine
11529 .ctx()
11530 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
11531 tp.record(&engine.stream())?;
11532 ev_tail_prof.push(tp);
11533 }
11534 ev_rank.push(ev);
11535 partials.push(d_csr);
11536 }
11537 let _main = e.gpu.enter_main()?;
11538 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
11539 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
11541 let g_t2 = std::time::Instant::now();
11542 for ev in &ev_rank {
11543 e.stream().wait(ev)?;
11544 }
11545 let mut y0 = e.uninit(n_pairs * width)?;
11548 {
11549 let mut dst = y0.slice_mut(0..n_pairs * width);
11550 e.stream()
11551 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
11552 }
11553 let mut y1 = e.uninit(n_pairs * width)?;
11554 {
11555 let mut dst = y1.slice_mut(0..n_pairs * width);
11556 e.stream()
11557 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
11558 }
11559 let inv_d = e.htod_i32(&inv)?;
11560 let wd_d = e.htod(&wd)?;
11561 let mut out = e.uninit(t * width)?;
11562 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
11563 if gprof {
11564 let _ = e.stream().synchronize();
11565 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
11566 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
11574 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
11575 let guard = self.ranks[rank].gpu.enter_main();
11576 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
11577 Ok(v) => span_ms.push(v),
11578 Err(err) => {
11579 static SAID: std::sync::atomic::AtomicBool =
11580 std::sync::atomic::AtomicBool::new(false);
11581 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
11582 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
11583 }
11584 span_ms.push(-1.0);
11585 }
11586 }
11587 }
11588 eprintln!(
11589 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
11590 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
11591 span_ms.iter().sum::<f32>(),
11592 span_ms.iter().cloned().fold(0.0f32, f32::max)
11593 );
11594 }
11595 Ok(out)
11596 }
11597
11598 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
11599 &self,
11600 experts: &ResidentNvfp4TensorParallel,
11601 e: &Engine,
11602 input_dev: &crate::CudaSlice<f32>,
11603 sel_d: &crate::CudaSlice<i32>,
11604 w_d: &crate::CudaSlice<f32>,
11605 experts_per_token: usize,
11606 activation_limit: Option<f32>,
11607 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11608 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11609 experts,
11610 e,
11611 input_dev,
11612 sel_d,
11613 w_d,
11614 experts_per_token,
11615 activation_limit,
11616 || Ok(()),
11617 )
11618 }
11619
11620 #[allow(clippy::too_many_arguments)]
11626 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11627 &self,
11628 experts: &ResidentNvfp4TensorParallel,
11629 e: &Engine,
11630 input_dev: &crate::CudaSlice<f32>,
11631 sel_d: &crate::CudaSlice<i32>,
11632 w_d: &crate::CudaSlice<f32>,
11633 experts_per_token: usize,
11634 activation_limit: Option<f32>,
11635 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11636 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11637 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11638 experts,
11639 e,
11640 input_dev,
11641 sel_d,
11642 w_d,
11643 experts_per_token,
11644 activation_limit,
11645 pre_join,
11646 None,
11647 )
11648 }
11649
11650 #[allow(clippy::too_many_arguments)]
11655 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11656 &self,
11657 experts: &ResidentNvfp4TensorParallel,
11658 e: &Engine,
11659 input_dev: &crate::CudaSlice<f32>,
11660 sel_d: &crate::CudaSlice<i32>,
11661 w_d: &crate::CudaSlice<f32>,
11662 experts_per_token: usize,
11663 activation_limit: Option<f32>,
11664 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11665 post_add: Option<(u64, u64)>,
11666 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11667 if input_dev.len() != experts.input_width {
11668 return Err(format!(
11669 "NVFP4 device-routed input {} != width {}",
11670 input_dev.len(),
11671 experts.input_width
11672 )
11673 .into());
11674 }
11675 let n_sel = experts_per_token;
11676 if sel_d.len() < n_sel || w_d.len() < n_sel {
11677 return Err(format!(
11678 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
11679 sel_d.len(),
11680 w_d.len()
11681 )
11682 .into());
11683 }
11684 let world = self.ranks.len();
11685 if world != NVFP4_CANONICAL_ROW_SHARDS {
11686 return Err(format!(
11687 "NVFP4 device routes require world == canonical shard grid \
11688 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
11689 )
11690 .into());
11691 }
11692 let local_out = if experts.ep2 {
11693 experts.expert_width
11694 } else {
11695 experts.expert_width / world
11696 };
11697
11698 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11699 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11700 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11701 let started = timing.then(std::time::Instant::now);
11702
11703 let mut workspace_guard = experts
11704 .device_workspace
11705 .lock()
11706 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11707 if workspace_guard.is_none() {
11708 drop(workspace_guard);
11709 let zero = vec![0.0f32; experts.input_width];
11710 let zero_sel = vec![0usize; n_sel];
11711 let zero_w = vec![0.0f32; n_sel];
11712 let _ = self.run_tensor_parallel_routes_nvfp4_device(
11713 experts,
11714 &zero,
11715 &zero_sel,
11716 &zero_w,
11717 n_sel,
11718 activation_limit,
11719 )?;
11720 workspace_guard = experts
11721 .device_workspace
11722 .lock()
11723 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11724 }
11725 let workspace = workspace_guard
11726 .as_mut()
11727 .expect("NVFP4 device routes workspace initialized above");
11728 if workspace.n_sel != n_sel {
11729 return Err(format!(
11730 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
11731 workspace.n_sel
11732 )
11733 .into());
11734 }
11735
11736 if step_tp_graph_enabled()? {
11741 if experts.ep2 {
11742 return Err(
11743 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
11744 co-gated; unset one"
11745 .into(),
11746 );
11747 }
11748 if workspace.dev_route_e.is_none() {
11749 let _main = e.gpu.enter_main()?;
11750 workspace.dev_route_e = Some((
11751 e.htod_i32(&vec![0i32; n_sel])?,
11752 e.htod(&vec![0.0f32; n_sel])?,
11753 ));
11754 }
11755 if workspace.in_stage_e.is_none() {
11756 let _main = e.gpu.enter_main()?;
11757 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11758 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11759 }
11760 if workspace.routes_graph.is_none() {
11761 let graph = self.nvfp4_routes_build_graph(
11762 experts,
11763 workspace,
11764 local_out,
11765 n_sel,
11766 activation_limit,
11767 )?;
11768 workspace.routes_graph = Some(graph);
11769 eprintln!(
11770 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
11771 children=3 updates=none performance_claim=false"
11772 );
11773 }
11774 let output = {
11775 let _main = e.gpu.enter_main()?;
11776 {
11777 let (sel_e, w_e) = workspace
11778 .dev_route_e
11779 .as_mut()
11780 .expect("device route staging set above");
11781 {
11782 let mut dst = sel_e.slice_mut(0..n_sel);
11783 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11784 }
11785 {
11786 let mut dst = w_e.slice_mut(0..n_sel);
11787 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11788 }
11789 }
11790 {
11791 let in_stage = workspace
11792 .in_stage_e
11793 .as_mut()
11794 .expect("graph staging set above");
11795 let mut dst = in_stage.slice_mut(0..experts.input_width);
11796 e.stream()
11797 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
11798 }
11799 unsafe {
11800 let r = cudarc::driver::sys::cuGraphLaunch(
11801 workspace
11802 .routes_graph
11803 .as_ref()
11804 .expect("routes graph built above")
11805 .exec,
11806 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11807 );
11808 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11809 return Err(format!("routes graph launch: {r:?}").into());
11810 }
11811 }
11812 let mut output = e.uninit(experts.input_width)?;
11813 {
11814 let out_stage = workspace
11815 .out_stage_e
11816 .as_ref()
11817 .expect("graph staging set above");
11818 e.stream().memcpy_dtod(
11819 &out_stage.slice(0..experts.input_width),
11820 &mut output.slice_mut(0..experts.input_width),
11821 )?;
11822 }
11823 output
11824 };
11825 if let Some(started) = started {
11826 use std::sync::atomic::Ordering;
11827 let ns = TIMING_NS
11828 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11829 + started.elapsed().as_nanos() as u64;
11830 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11831 if calls % 430 == 0 {
11832 eprintln!(
11833 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11834 ns as f64 / 1.0e6,
11835 ns as f64 / calls as f64 / 1.0e3,
11836 );
11837 }
11838 }
11839 return Ok(output);
11840 }
11841
11842 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11846 if *device != e.ctx().ordinal() {
11847 return Err("NVFP4 device-routed routes engine changed".into());
11848 }
11849 } else {
11850 let _main = e.gpu.enter_main()?;
11851 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11852 }
11853 if workspace.dev_route_e.is_none() {
11854 let _main = e.gpu.enter_main()?;
11855 workspace.dev_route_e = Some((
11856 e.htod_i32(&vec![0i32; n_sel])?,
11857 e.htod(&vec![0.0f32; n_sel])?,
11858 ));
11859 }
11860 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
11866 let e_device = e.ctx().ordinal();
11867 let rank1_routed_peek = workspace.rank1_routed;
11869 let stage_needed = !mirror
11870 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
11871 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
11872 });
11873 {
11874 let _main = e.gpu.enter_main()?;
11875 if stage_needed {
11876 let (sel_e, w_e) = workspace
11877 .dev_route_e
11878 .as_mut()
11879 .expect("device route staging set above");
11880 {
11881 let mut dst = sel_e.slice_mut(0..n_sel);
11882 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11883 }
11884 {
11885 let mut dst = w_e.slice_mut(0..n_sel);
11886 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11887 }
11888 }
11889 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11890 ev_entry.record(&e.stream())?;
11891 }
11892 let prestaged = std::mem::take(&mut workspace.prestaged);
11895 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
11896 for (rank_index, engine) in self.ranks.iter().enumerate() {
11897 let _main = engine.gpu.enter_main()?;
11898 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11899 engine.stream().wait(ev_entry)?;
11900 if !prestaged {
11901 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11902 engine
11903 .stream()
11904 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11905 }
11906 if !(rank1_routed && rank_index == 1) {
11907 let same_dev = engine.ctx().ordinal() == e_device;
11911 if mirror {
11912 let Nvfp4DeviceRoutesWorkspace {
11915 sel,
11916 route_w,
11917 dev_route_e,
11918 ..
11919 } = &mut *workspace;
11920 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
11921 if same_dev {
11922 (sel_d, w_d)
11923 } else {
11924 let (sel_e, w_e) = dev_route_e
11925 .as_ref()
11926 .expect("device route staging set above");
11927 (sel_e, w_e)
11928 };
11929 engine.moe_sel_w_mirror(
11930 src_sel,
11931 src_w,
11932 &mut sel[rank_index],
11933 &mut route_w[rank_index],
11934 n_sel,
11935 )?;
11936 } else {
11937 let (sel_e, w_e) = workspace
11938 .dev_route_e
11939 .as_ref()
11940 .expect("device route staging set above");
11941 {
11942 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
11943 engine
11944 .stream()
11945 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
11946 }
11947 {
11948 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
11949 engine
11950 .stream()
11951 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11952 }
11953 }
11954 }
11955 if !prestaged {
11956 let Nvfp4DeviceRoutesWorkspace {
11957 input, in_q, in_d, ..
11958 } = &mut *workspace;
11959 engine.quantize_q8_1_into(
11960 &input[rank_index],
11961 1,
11962 experts.input_width,
11963 &mut in_q[rank_index],
11964 &mut in_d[rank_index],
11965 )?;
11966 }
11967 }
11968 self.nvfp4_routes_batched_sweeps(
11969 experts,
11970 workspace,
11971 &[],
11972 &[],
11973 &[],
11974 local_out,
11975 n_sel,
11976 activation_limit,
11977 true,
11978 )?;
11979
11980 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11983 let _main = engine.gpu.enter_main()?;
11984 workspace.ev_rank[rank_index].record(&engine.stream())?;
11985 }
11986 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
11989 let mut ticket = 0u32;
11990 if memops {
11991 use cudarc::driver::sys;
11992 if workspace.fence_flags_raw == 0 {
11993 let root = &self.ranks[0];
11994 let _main = root.gpu.enter_main()?;
11995 let mut ptr: sys::CUdeviceptr = 0;
11996 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
11997 if r != sys::CUresult::CUDA_SUCCESS {
11998 return Err(format!("fence flag alloc: {r:?}").into());
11999 }
12000 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
12001 if r != sys::CUresult::CUDA_SUCCESS {
12002 return Err(format!("fence flag memset: {r:?}").into());
12003 }
12004 workspace.fence_flags_raw = ptr as u64;
12005 }
12006 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
12007 ticket = workspace.fence_ticket;
12008 let base = workspace.fence_flags_raw;
12009 if fence_rank1_on() {
12015 let peer = &self.ranks[1];
12016 let _pmain = peer.gpu.enter_main()?;
12017 peer.ring_flag_raw(base, ticket)?;
12018 }
12019 {
12020 let root = &self.ranks[0];
12021 let _main = root.gpu.enter_main()?;
12022 let r = unsafe {
12023 sys::cuStreamWriteValue32_v2(
12024 root.stream().cu_stream() as sys::CUstream,
12025 (base + 4) as sys::CUdeviceptr,
12026 ticket,
12027 0,
12028 )
12029 };
12030 if r != sys::CUresult::CUDA_SUCCESS {
12031 return Err(format!("fence write root: {r:?}").into());
12032 }
12033 }
12034 }
12035 pre_join()?;
12038
12039 if moe_direct_on() && self.ranks.len() == 2 {
12040 let _main = e.gpu.enter_main()?;
12047 if memops {
12048 use cudarc::driver::sys;
12049 let base = workspace.fence_flags_raw;
12050 let r = unsafe {
12051 sys::cuStreamWaitValue32_v2(
12052 e.stream().cu_stream() as sys::CUstream,
12053 (base + 4) as sys::CUdeviceptr,
12054 ticket,
12055 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
12056 )
12057 };
12058 if r != sys::CUresult::CUDA_SUCCESS {
12059 return Err(format!("fence wait: {r:?}").into());
12060 }
12061 if fence_rank1_on() {
12062 let r = unsafe {
12064 sys::cuStreamWaitValue32_v2(
12065 e.stream().cu_stream() as sys::CUstream,
12066 base as sys::CUdeviceptr,
12067 ticket,
12068 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
12069 )
12070 };
12071 if r != sys::CUresult::CUDA_SUCCESS {
12072 return Err(format!("fence wait rank1: {r:?}").into());
12073 }
12074 } else {
12075 for ev in workspace.ev_rank.iter().skip(1) {
12076 e.stream().wait(ev)?;
12077 }
12078 }
12079 } else {
12080 {
12081 let root = &self.ranks[0];
12082 let _rmain = root.gpu.enter_main()?;
12083 workspace
12084 .ev_done
12085 .as_ref()
12086 .expect("device routes done event")
12087 .record(&root.stream())?;
12088 }
12089 e.stream().wait(
12090 workspace
12091 .ev_done
12092 .as_ref()
12093 .expect("device routes done event"),
12094 )?;
12095 for ev in workspace.ev_rank.iter().skip(1) {
12096 e.stream().wait(ev)?;
12097 }
12098 }
12099 let mut output = e.uninit(experts.input_width)?;
12100 if let Some((sh_raw, scale_raw)) = post_add {
12101 e.add3_raw(
12104 &workspace.accumulator[0],
12105 &workspace.accumulator[1],
12106 sh_raw,
12107 scale_raw,
12108 &mut output,
12109 experts.input_width,
12110 )?;
12111 } else {
12112 e.add(
12113 &workspace.accumulator[0],
12114 &workspace.accumulator[1],
12115 &mut output,
12116 experts.input_width,
12117 )?;
12118 }
12119 let output = output;
12120 if let Some(started) = started {
12121 use std::sync::atomic::Ordering;
12122 let ns = TIMING_NS
12123 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12124 + started.elapsed().as_nanos() as u64;
12125 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12126 if calls % 430 == 0 {
12127 eprintln!(
12128 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12129 ns as f64 / 1.0e6,
12130 ns as f64 / calls as f64 / 1.0e3,
12131 );
12132 }
12133 }
12134 return Ok(output);
12135 }
12136 {
12137 let root = &self.ranks[0];
12138 let _main = root.gpu.enter_main()?;
12139 for ev in workspace.ev_rank.iter().skip(1) {
12140 root.stream().wait(ev)?;
12141 }
12142 root.stream()
12143 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12144 {
12145 let Nvfp4DeviceRoutesWorkspace {
12146 accumulator,
12147 remote,
12148 combined,
12149 ..
12150 } = &mut *workspace;
12151 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12152 }
12153 workspace
12154 .ev_done
12155 .as_ref()
12156 .expect("device routes done event")
12157 .record(&root.stream())?;
12158 }
12159 let output = {
12160 let _main = e.gpu.enter_main()?;
12161 e.stream().wait(
12162 workspace
12163 .ev_done
12164 .as_ref()
12165 .expect("device routes done event"),
12166 )?;
12167 let mut output = e.uninit(experts.input_width)?;
12170 e.stream().memcpy_dtod(
12171 &workspace.combined.slice(0..experts.input_width),
12172 &mut output.slice_mut(0..experts.input_width),
12173 )?;
12174 output
12175 };
12176 if let Some(started) = started {
12177 use std::sync::atomic::Ordering;
12178 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12179 + started.elapsed().as_nanos() as u64;
12180 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12181 if calls % 430 == 0 {
12182 eprintln!(
12183 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12184 ns as f64 / 1.0e6,
12185 ns as f64 / calls as f64 / 1.0e3,
12186 );
12187 }
12188 }
12189 Ok(output)
12190 }
12191
12192 pub(crate) fn decode_v2_finish_root_fused(
12196 &self,
12197 ws: &mut StepTpDecodeV2Ws,
12198 ) -> Result<(), Box<dyn std::error::Error>> {
12199 let root = &self.ranks[0];
12200 let _main = root.gpu.enter_main()?;
12201 if ws.raw_peer_partial != 0 {
12202 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
12204 } else {
12205 root.stream()
12206 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
12207 }
12208 {
12209 let StepTpDecodeV2Ws {
12210 o_partials,
12211 peer_partial,
12212 reduce_a,
12213 o_out,
12214 ..
12215 } = &mut *ws;
12216 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
12217 }
12218 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
12219 if shadows {
12220 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
12223 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
12224 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
12225 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
12226 }
12227 if shadows && ws.raw_peer_partial != 0 {
12228 raw_copy_bytes(
12229 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
12230 ws.raw_k1,
12231 ws.local_kv_dim * 4,
12232 root,
12233 )?;
12234 raw_copy_bytes(
12235 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
12236 ws.raw_v1,
12237 ws.local_kv_dim * 4,
12238 root,
12239 )?;
12240 } else if shadows {
12241 let start = ws.local_kv_dim;
12242 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
12243 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
12244 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
12245 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
12246 }
12247 if ws.raw_mixed_stage_e != 0 {
12248 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
12251 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
12252 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
12253 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
12254 }
12255 Ok(())
12256 }
12257
12258 pub(crate) fn decode_v2_arm_token_mirrors(
12261 &self,
12262 ws: &mut StepTpDecodeV2Ws,
12263 mixed_stage_e: u64,
12264 shadow_stage_e: (u64, u64),
12265 ) -> Result<(), Box<dyn std::error::Error>> {
12266 use cudarc::driver::DevicePtr;
12267 let root = &self.ranks[0];
12268 let _main = root.gpu.enter_main()?;
12269 let stream = root.stream();
12270 let (a, _g) = ws.reduce_a.device_ptr(&stream);
12271 ws.raw_reduce_a = a as u64;
12272 ws.raw_mixed_stage_e = mixed_stage_e;
12273 ws.raw_shadow_stage_e = shadow_stage_e;
12274 Ok(())
12275 }
12276
12277 fn nvfp4_routes_build_graph(
12283 &self,
12284 experts: &ResidentNvfp4TensorParallel,
12285 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12286 local_out: usize,
12287 n_sel: usize,
12288 activation_limit: Option<f32>,
12289 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12290 use cudarc::driver::DevicePtr;
12291 use cudarc::driver::sys;
12292 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12293 if r == sys::CUresult::CUDA_SUCCESS {
12294 Ok(())
12295 } else {
12296 Err(format!("{what}: {r:?}").into())
12297 }
12298 }
12299 let world = self.ranks.len();
12300 if world != 2 {
12301 return Err("routes graph door is built for the TP2 pair".into());
12302 }
12303 let width = experts.input_width;
12304
12305 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
12307 let stream = engine.stream();
12308 let (ptr, _g) = buf.device_ptr(&stream);
12309 ptr as u64
12310 };
12311 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
12312 let stream = engine.stream();
12313 let (ptr, _g) = buf.device_ptr(&stream);
12314 ptr as u64
12315 };
12316 let (sel_e, w_e) = workspace
12317 .dev_route_e
12318 .as_ref()
12319 .expect("device route staging set before graph build");
12320 let root_engine = &self.ranks[0];
12321 let p_in_stage = ptr_f32(
12322 workspace.in_stage_e.as_ref().expect("graph staging"),
12323 root_engine,
12324 );
12325 let p_out_stage = ptr_f32(
12326 workspace.out_stage_e.as_ref().expect("graph staging"),
12327 root_engine,
12328 );
12329 let p_sel_e = ptr_i32(sel_e, root_engine);
12330 let p_w_e = ptr_f32(w_e, root_engine);
12331 let p_input: Vec<u64> = (0..world)
12332 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
12333 .collect();
12334 let p_sel: Vec<u64> = (0..world)
12335 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
12336 .collect();
12337 let p_route_w: Vec<u64> = (0..world)
12338 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
12339 .collect();
12340 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
12341 let p_remote = ptr_f32(&workspace.remote, root_engine);
12342 let p_combined = ptr_f32(&workspace.combined, root_engine);
12343
12344 let raw_copy = |dst: u64,
12345 src: u64,
12346 bytes: usize,
12347 engine: &Engine|
12348 -> Result<(), Box<dyn std::error::Error>> {
12349 unsafe {
12350 cu_try(
12351 sys::cuMemcpyAsync(
12352 dst as sys::CUdeviceptr,
12353 src as sys::CUdeviceptr,
12354 bytes,
12355 engine.stream().cu_stream() as sys::CUstream,
12356 ),
12357 "routes graph cuMemcpyAsync",
12358 )
12359 }
12360 };
12361
12362 let mut children = Vec::with_capacity(3);
12363 for rank in 0..world {
12364 let engine = &self.ranks[rank];
12365 let _main = engine.gpu.enter_main()?;
12366 let (child, _retained) = engine.capture_graph_retained(|_| {
12367 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
12368 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
12369 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
12370 {
12371 let Nvfp4DeviceRoutesWorkspace {
12372 input, in_q, in_d, ..
12373 } = &mut *workspace;
12374 engine.quantize_q8_1_into(
12375 &input[rank],
12376 1,
12377 width,
12378 &mut in_q[rank],
12379 &mut in_d[rank],
12380 )?;
12381 }
12382 self.nvfp4_routes_batched_sweeps_rank(
12383 experts,
12384 workspace,
12385 &[],
12386 &[],
12387 &[],
12388 local_out,
12389 n_sel,
12390 activation_limit,
12391 true,
12392 rank,
12393 )?;
12394 Ok(())
12395 })?;
12396 children.push(child);
12397 }
12398 {
12399 let root = &self.ranks[0];
12400 let _main = root.gpu.enter_main()?;
12401 let (child, _retained) = root.capture_graph_retained(|_| {
12402 raw_copy(p_remote, p_acc1, width * 4, root)?;
12403 {
12404 let Nvfp4DeviceRoutesWorkspace {
12405 accumulator,
12406 remote,
12407 combined,
12408 ..
12409 } = &mut *workspace;
12410 root.add(&accumulator[0], remote, combined, width)?;
12411 }
12412 raw_copy(p_out_stage, p_combined, width * 4, root)?;
12413 Ok(())
12414 })?;
12415 children.push(child);
12416 }
12417
12418 let mut parent: sys::CUgraph = std::ptr::null_mut();
12419 unsafe {
12420 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
12421 }
12422 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
12423 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
12424 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
12425 unsafe {
12426 cu_try(
12427 sys::cuGraphAddChildGraphNode(
12428 &mut n0,
12429 parent,
12430 std::ptr::null(),
12431 0,
12432 children[0].cu_graph(),
12433 ),
12434 "routes child r0",
12435 )?;
12436 cu_try(
12437 sys::cuGraphAddChildGraphNode(
12438 &mut n1,
12439 parent,
12440 std::ptr::null(),
12441 0,
12442 children[1].cu_graph(),
12443 ),
12444 "routes child r1",
12445 )?;
12446 let deps = [n0, n1];
12447 cu_try(
12448 sys::cuGraphAddChildGraphNode(
12449 &mut n2,
12450 parent,
12451 deps.as_ptr(),
12452 2,
12453 children[2].cu_graph(),
12454 ),
12455 "routes child root",
12456 )?;
12457 }
12458 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12459 unsafe {
12460 cu_try(
12461 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12462 "routes instantiate",
12463 )?;
12464 }
12465 Ok(RoutesGraph {
12466 exec,
12467 parent,
12468 _children: children,
12469 })
12470 }
12471
12472 #[allow(clippy::too_many_arguments)]
12476 pub(crate) fn routes_rank_section(
12477 &self,
12478 experts: &ResidentNvfp4TensorParallel,
12479 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12480 raw_input_src: u64,
12481 local_out: usize,
12482 n_sel: usize,
12483 activation_limit: Option<f32>,
12484 rank_index: usize,
12485 ) -> Result<(), Box<dyn std::error::Error>> {
12486 let engine = &self.ranks[rank_index];
12487 {
12488 let _main = engine.gpu.enter_main()?;
12489 let (sel_e_ptr, w_e_ptr) = workspace
12491 .raw_dev_route_e
12492 .ok_or("routes rank section requires armed staging pointers")?;
12493 raw_copy_bytes(
12494 workspace.raw_input[rank_index],
12495 raw_input_src,
12496 experts.input_width * 4,
12497 engine,
12498 )?;
12499 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
12500 raw_copy_bytes(
12501 workspace.raw_route_w[rank_index],
12502 w_e_ptr,
12503 n_sel * 4,
12504 engine,
12505 )?;
12506 {
12507 let Nvfp4DeviceRoutesWorkspace {
12508 input, in_q, in_d, ..
12509 } = &mut *workspace;
12510 engine.quantize_q8_1_into(
12511 &input[rank_index],
12512 1,
12513 experts.input_width,
12514 &mut in_q[rank_index],
12515 &mut in_d[rank_index],
12516 )?;
12517 }
12518 }
12519 self.nvfp4_routes_batched_sweeps_rank(
12520 experts,
12521 workspace,
12522 &[],
12523 &[],
12524 &[],
12525 local_out,
12526 n_sel,
12527 activation_limit,
12528 true,
12529 rank_index,
12530 )
12531 }
12532
12533 pub(crate) fn routes_root_section(
12536 &self,
12537 experts: &ResidentNvfp4TensorParallel,
12538 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12539 ) -> Result<(), Box<dyn std::error::Error>> {
12540 let root = &self.ranks[0];
12541 let _main = root.gpu.enter_main()?;
12542 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
12543 .raw_combine
12544 .ok_or("routes root section requires armed combine pointers")?;
12545 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
12546 {
12547 let Nvfp4DeviceRoutesWorkspace {
12548 accumulator,
12549 remote,
12550 combined,
12551 ..
12552 } = &mut *workspace;
12553 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12554 }
12555 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
12556 Ok(())
12557 }
12558
12559 pub(crate) fn routes_arm_raw(
12562 &self,
12563 experts: &ResidentNvfp4TensorParallel,
12564 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12565 ) -> Result<(), Box<dyn std::error::Error>> {
12566 use cudarc::driver::DevicePtr;
12567 if workspace.raw_dev_route_e.is_some() {
12568 return Ok(());
12569 }
12570 let _ = experts;
12571 let (sel_e, w_e) = workspace
12572 .dev_route_e
12573 .as_ref()
12574 .ok_or("routes staging not armed")?;
12575 let root = &self.ranks[0];
12576 {
12577 let _main = root.gpu.enter_main()?;
12578 let stream = root.stream();
12579 let (a, _g) = sel_e.device_ptr(&stream);
12580 let (b, _g) = w_e.device_ptr(&stream);
12581 workspace.raw_dev_route_e = Some((a as u64, b as u64));
12582 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
12583 let (d, _g) = workspace.remote.device_ptr(&stream);
12584 let (f, _g) = workspace.combined.device_ptr(&stream);
12585 let out_stage = workspace
12586 .out_stage_e
12587 .as_ref()
12588 .ok_or("routes out stage not armed")?;
12589 let (g_, _g) = out_stage.device_ptr(&stream);
12590 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
12591 }
12592 for rank in 0..self.ranks.len() {
12593 let engine = &self.ranks[rank];
12594 let _main = engine.gpu.enter_main()?;
12595 let stream = engine.stream();
12596 let (a, _g) = workspace.input[rank].device_ptr(&stream);
12597 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
12598 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
12599 workspace.raw_input.push(a as u64);
12600 workspace.raw_sel.push(b as u64);
12601 workspace.raw_route_w.push(c as u64);
12602 }
12603 Ok(())
12604 }
12605
12606 pub fn run_tensor_parallel_routes_nvfp4(
12610 &self,
12611 experts: &ResidentNvfp4TensorParallel,
12612 input: &[f32],
12613 tokens: usize,
12614 selected: &[usize],
12615 route_weights: &[f32],
12616 experts_per_token: usize,
12617 activation_limit: Option<f32>,
12618 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12619 validate_activations(input, tokens, experts.input_width)?;
12620 let pairs = tokens
12621 .checked_mul(experts_per_token)
12622 .ok_or("NVFP4 TP route count overflow")?;
12623 if selected.len() != pairs || route_weights.len() != pairs {
12624 return Err(format!(
12625 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
12626 {experts_per_token} ({pairs})",
12627 selected.len(),
12628 route_weights.len(),
12629 )
12630 .into());
12631 }
12632 if !route_weights.iter().all(|weight| weight.is_finite()) {
12633 return Err("NVFP4 TP route weights contain a non-finite value".into());
12634 }
12635
12636 let mut output = vec![0.0f32; tokens * experts.input_width];
12637 for token in 0..tokens {
12638 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
12639 for slot in 0..experts_per_token {
12640 let pair = token * experts_per_token + slot;
12641 let expert = selected[pair];
12642 if expert >= experts.expert_count {
12643 return Err(format!(
12644 "NVFP4 TP selected expert {expert} outside 0..{}",
12645 experts.expert_count
12646 )
12647 .into());
12648 }
12649 let gate = if experts.ep2 {
12655 self.run_full_bank_expert_nvfp4(
12656 &experts.gate,
12657 &experts.macros_gate,
12658 expert,
12659 input_row,
12660 )?
12661 } else {
12662 self.run_column_bank_expert_nvfp4(
12663 &experts.gate,
12664 &experts.macros_gate,
12665 expert,
12666 input_row,
12667 )?
12668 };
12669 let up = if experts.ep2 {
12670 self.run_full_bank_expert_nvfp4(
12671 &experts.up,
12672 &experts.macros_up,
12673 expert,
12674 input_row,
12675 )?
12676 } else {
12677 self.run_column_bank_expert_nvfp4(
12678 &experts.up,
12679 &experts.macros_up,
12680 expert,
12681 input_row,
12682 )?
12683 };
12684 let activated: Vec<f32> = gate
12685 .iter()
12686 .zip(&up)
12687 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
12688 .collect();
12689 debug_assert_eq!(activated.len(), experts.expert_width);
12690 let down = if experts.ep2 {
12691 self.run_full_down_expert_nvfp4(
12692 &experts.down,
12693 &experts.macros_down,
12694 expert,
12695 &activated,
12696 )?
12697 } else {
12698 self.run_row_bank_expert_nvfp4(
12699 &experts.down,
12700 &experts.macros_down,
12701 expert,
12702 &activated,
12703 )?
12704 };
12705 let weight = route_weights[pair];
12706 for (sum, value) in output
12707 [token * experts.input_width..(token + 1) * experts.input_width]
12708 .iter_mut()
12709 .zip(down)
12710 {
12711 *sum += weight * value;
12712 }
12713 }
12714 }
12715 Ok(output)
12716 }
12717}
12718
12719#[cfg(test)]
12720mod bank_v2_layout_tests {
12721 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
12722
12723 #[test]
12733 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
12734 let (out_f, in_f) = (2usize, 128usize);
12736 let row_bytes = nvfp4_row_bytes(in_f);
12737 assert_eq!(row_bytes, 72);
12738 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
12739 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
12740 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
12741 let n_slots = in_f / 32;
12742 for row in 0..out_f {
12743 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
12744 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
12745 for g in 0..n_slots {
12746 let (sblk, h) = (g / 2, g % 2);
12747 let sb = &src[sblk * 36..sblk * 36 + 36];
12748 assert_eq!(
12749 &dst[g * 16..g * 16 + 16],
12750 &sb[4 + 16 * h..4 + 16 * h + 16],
12751 "row {row} slot {g} codes"
12752 );
12753 assert_eq!(
12754 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
12755 &sb[2 * h..2 * h + 2],
12756 "row {row} slot {g} scales"
12757 );
12758 }
12759 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
12761 a.sort_unstable();
12762 b.sort_unstable();
12763 assert_eq!(a, b, "row {row} is not a byte permutation");
12764 }
12765 }
12766}
12767
12768#[cfg(test)]
12769mod tests {
12770
12771 #[test]
12778 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
12779 let (kp, bp) = (0xdead_0000u64, 0u64);
12780 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
12781 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
12782 assert_eq!(
12783 super::retired_rows_tab_key(kp, bp, 20, 2),
12784 super::retired_rows_tab_key(kp, bp, 20, 2),
12785 "same layer and t must hash the same, or the test proves nothing"
12786 );
12787 let a = super::rows_tab_host(&live, 0x9000, true, 1);
12788 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
12789 assert_ne!(a, b, "the two generations write DIFFERENT tables");
12790 assert_eq!(
12792 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
12793 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
12794 "the retired key collides across allocation generations"
12795 );
12796 }
12797
12798 #[test]
12801 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
12802 let parts = [
12803 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
12804 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
12805 ];
12806 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
12807 assert_eq!(
12808 same,
12809 vec![
12810 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
12811 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
12814 "same-session rows share one counter cell and step back t-1-r"
12815 );
12816 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
12817 assert_eq!(
12818 cross,
12819 vec![
12820 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
12821 0x00c1u64, 0x00d1u64, 0x7004, 0,
12822 ],
12823 "cross-session rows get their own counter cell and no step back"
12824 );
12825 }
12826 use super::*;
12827
12828 #[test]
12829 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
12830 let limit = Some(7.0);
12831 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
12832 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
12833 assert!(
12834 step_expert_activation_host(-20.0, 9.0, limit).abs()
12835 < step_expert_activation_host(-20.0, 9.0, None).abs()
12836 );
12837 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
12838 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
12839 assert!(validate_step_expert_activation_limit(limit).is_ok());
12840 }
12841
12842 #[test]
12843 fn moe_residual_host_preserves_official_add_order() {
12844 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
12845 assert_eq!(output, [0.0]);
12846 assert_eq!(
12847 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
12848 "MoE residual lengths residual=1 routed=2 shared=1"
12849 );
12850 }
12851
12852 #[test]
12853 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
12854 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
12855 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
12856 assert_eq!(owners.len(), 4);
12857 for (rank, owner) in owners.iter().enumerate() {
12858 assert_eq!(owner.rank, rank);
12859 assert_eq!(owner.selected, vec![0, 36]);
12860 assert_eq!(owner.token_rows, vec![0, 0]);
12861 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
12862 }
12863 }
12864
12865 #[test]
12866 fn expert_owner_routes_validate_geometry_and_selected_experts() {
12867 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
12868 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
12869 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
12870 assert!(error.contains("outside 0..288"));
12871 }
12872
12873 #[test]
12874 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
12875 let selected = [
12876 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
12877 ];
12878 assert_eq!(
12879 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
12880 16
12881 );
12882 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
12883 assert_eq!(
12884 owners
12885 .iter()
12886 .map(|owner| owner.selected.len())
12887 .collect::<Vec<_>>(),
12888 vec![2, 4, 6, 4]
12889 );
12890 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
12891 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
12892 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
12893 }
12894
12895 #[test]
12896 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
12897 let owner0 = [0usize, 3];
12898 let owner1 = [1usize, 2];
12899 let owners = [owner0.as_slice(), owner1.as_slice()];
12900 assert_eq!(
12901 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
12902 .unwrap(),
12903 WeightedRouteCombineShape {
12904 pairs: 4,
12905 max_pairs: 12,
12906 }
12907 );
12908 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
12909 assert!(
12910 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
12911 .is_err()
12912 );
12913 assert!(
12914 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
12915 .is_err()
12916 );
12917 assert!(
12918 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
12919 .is_err()
12920 );
12921 }
12922
12923 #[test]
12924 fn native_p2p_door_is_strict_and_default_off() {
12925 assert!(!parse_step_tp_native_p2p(None).unwrap());
12926 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
12927 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
12928 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
12929 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
12930 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
12931 }
12932
12933 #[test]
12934 fn bulk_p2p_door_is_strict_and_default_off() {
12935 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
12936 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
12937 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
12938 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
12939 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
12940 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
12941 }
12942
12943 #[test]
12944 fn ep_device_arithmetic_door_is_strict_and_default_off() {
12945 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
12946 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
12947 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
12948 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
12949 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
12950 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
12951 }
12952
12953 #[test]
12954 fn f32_mirror_door_is_strict_and_default_off() {
12955 assert!(!parse_step_tp_f32_mirror(None).unwrap());
12956 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
12957 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
12958 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
12959 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
12960 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
12961 }
12962
12963 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
12964 let codes = (0..out_features * in_features)
12965 .map(|index| (index % 251) as u8)
12966 .collect();
12967 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
12968 .map(|index| index as f32 + 1.0)
12969 .collect();
12970 (codes, scales)
12971 }
12972
12973 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
12974 (0..out_features * in_features)
12975 .flat_map(|value| (value as u16).to_le_bytes())
12976 .collect()
12977 }
12978
12979 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
12980 bytes
12981 .chunks_exact(2)
12982 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
12983 .collect()
12984 }
12985
12986 #[test]
12987 fn bf16_matrix_rejects_wrong_byte_count() {
12988 let bytes = vec![0u8; 4 * 4 * 2 - 1];
12989 let matrix = Bf16Matrix {
12990 bytes: &bytes,
12991 out_features: 4,
12992 in_features: 4,
12993 };
12994 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
12995 }
12996
12997 #[test]
12998 fn replicated_device_rows_require_exact_rank_local_shapes() {
12999 assert_eq!(
13000 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
13001 12_288
13002 );
13003 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
13004 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
13005 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
13006 assert!(
13007 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
13008 );
13009 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
13010 }
13011
13012 #[test]
13013 fn replicated_device_row_refresh_requires_exact_root_source() {
13014 assert_eq!(
13015 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
13016 12_288
13017 );
13018 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
13019 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
13020 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
13021 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
13022 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
13023 }
13024
13025 #[test]
13026 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
13027 for tp in [1, 2, 4, 8] {
13028 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
13029 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
13030 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
13031 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
13032 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
13033 }
13034 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
13035 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
13036 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
13037 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
13038 }
13039
13040 #[test]
13041 fn cache_rows_split_by_token_then_rank() {
13042 let rows = (0u8..24).collect::<Vec<_>>();
13043 assert_eq!(
13044 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
13045 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
13046 );
13047 assert_eq!(
13048 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
13049 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
13050 );
13051 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
13052 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
13053 }
13054
13055 #[test]
13056 fn bf16_column_shard_preserves_contiguous_output_rows() {
13057 let bytes = bf16_matrix_bytes(4, 4);
13058 let matrix = Bf16Matrix {
13059 bytes: &bytes,
13060 out_features: 4,
13061 in_features: 4,
13062 };
13063 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
13064 assert_eq!(shard.out_features, 2);
13065 assert_eq!(shard.in_features, 4);
13066 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
13067 }
13068
13069 #[test]
13070 fn bf16_row_shard_preserves_each_input_column_window() {
13071 let bytes = bf16_matrix_bytes(3, 4);
13072 let matrix = Bf16Matrix {
13073 bytes: &bytes,
13074 out_features: 3,
13075 in_features: 4,
13076 };
13077 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
13078 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
13079 }
13080
13081 #[test]
13082 fn bf16_row_block_preserves_global_column_order() {
13083 let bytes = bf16_matrix_bytes(3, 8);
13084 let matrix = Bf16Matrix {
13085 bytes: &bytes,
13086 out_features: 3,
13087 in_features: 8,
13088 };
13089 let block = bf16_row_block(matrix, 2, 3).unwrap();
13090 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
13091 }
13092
13093 #[test]
13094 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
13095 let (codes, scales) = matrix(1280, 4096);
13096 let matrix = E4m3BlockMatrix {
13097 codes: &codes,
13098 scales: &scales,
13099 out_features: 1280,
13100 in_features: 4096,
13101 };
13102 let shard = column_shard(matrix, 2, 1).unwrap();
13103 assert_eq!(shard.out_features, 640);
13104 assert_eq!(shard.codes, &codes[640 * 4096..]);
13105 assert_eq!(shard.scales, &scales[5 * 32..]);
13106 }
13107
13108 #[test]
13109 fn row_shard_preserves_each_weight_and_scale_column_window() {
13110 let (codes, scales) = matrix(4096, 1280);
13111 let matrix = E4m3BlockMatrix {
13112 codes: &codes,
13113 scales: &scales,
13114 out_features: 4096,
13115 in_features: 1280,
13116 };
13117 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
13118 assert_eq!(shard_codes.len(), 4096 * 640);
13119 assert_eq!(&shard_codes[..640], &codes[640..1280]);
13120 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
13121 assert_eq!(shard_scales.len(), 32 * 5);
13122 assert_eq!(&shard_scales[..5], &scales[5..10]);
13123 assert_eq!(&shard_scales[5..10], &scales[15..20]);
13124 }
13125
13126 #[test]
13127 fn activation_shards_keep_token_rows_separate() {
13128 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
13129 assert_eq!(
13130 activation_shard(&activations, 2, 8, 2, 1),
13131 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
13132 );
13133 }
13134
13135 #[test]
13136 fn expert_bank_selects_expert_major_code_and_scale_planes() {
13137 let expert_count = 2;
13138 let out_features = 128;
13139 let in_features = 128;
13140 let code_stride = out_features * in_features;
13141 let codes: Vec<u8> = (0..expert_count * code_stride)
13142 .map(|index| (index % 251) as u8)
13143 .collect();
13144 let scales = vec![1.0f32, 2.0];
13145 let bank = E4m3ExpertBank {
13146 codes: &codes,
13147 scales: &scales,
13148 expert_count,
13149 out_features,
13150 in_features,
13151 };
13152 bank.validate().unwrap();
13153 let expert = bank.expert(1).unwrap();
13154 assert_eq!(expert.codes, &codes[code_stride..]);
13155 assert_eq!(expert.scales, &[2.0]);
13156 }
13157
13158 #[test]
13159 fn expert_bank_rejects_non_positive_scale() {
13160 let codes = vec![0u8; 128 * 128];
13161 let scales = vec![0.0f32];
13162 let bank = E4m3ExpertBank {
13163 codes: &codes,
13164 scales: &scales,
13165 expert_count: 1,
13166 out_features: 128,
13167 in_features: 128,
13168 };
13169 assert!(bank.validate().unwrap_err().contains("non-positive"));
13170 }
13171
13172 #[test]
13173 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
13174 let expert_count = 2;
13175 let out_features = 256;
13176 let in_features = 128;
13177 let code_stride = out_features * in_features;
13178 let scale_stride = 2;
13179 let codes = (0..expert_count * code_stride)
13180 .map(|index| (index % 251) as u8)
13181 .collect::<Vec<_>>();
13182 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
13183 let bank = E4m3ExpertBank {
13184 codes: &codes,
13185 scales: &scales,
13186 expert_count,
13187 out_features,
13188 in_features,
13189 };
13190
13191 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
13192 assert_eq!(rank.out_features, 128);
13193 assert_eq!(rank.in_features, 128);
13194 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
13195 assert_eq!(rank.scales, vec![11.0, 21.0]);
13196 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
13197 assert_eq!(
13198 &rank.codes[128 * 128..],
13199 &codes[code_stride + 128 * 128..2 * code_stride]
13200 );
13201 assert_eq!(scale_stride, scales.len() / expert_count);
13202 }
13203
13204 #[test]
13205 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
13206 let expert_count = 2;
13207 let out_features = 128;
13208 let in_features = 256;
13209 let code_stride = out_features * in_features;
13210 let codes = (0..expert_count * code_stride)
13211 .map(|index| (index % 251) as u8)
13212 .collect::<Vec<_>>();
13213 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
13214 let bank = E4m3ExpertBank {
13215 codes: &codes,
13216 scales: &scales,
13217 expert_count,
13218 out_features,
13219 in_features,
13220 };
13221
13222 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
13223 assert_eq!(rank.out_features, 128);
13224 assert_eq!(rank.in_features, 128);
13225 assert_eq!(rank.k_blocks, Some(1));
13226 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
13227 assert_eq!(rank.scales, vec![11.0, 21.0]);
13228 assert_eq!(&rank.codes[..128], &codes[128..256]);
13229 assert_eq!(
13230 &rank.codes[128 * 128..128 * 128 + 128],
13231 &codes[code_stride + 128..code_stride + 256]
13232 );
13233 }
13234
13235 #[test]
13236 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
13237 let expert_count = 2;
13238 let out_features = 256;
13239 let in_features = 512;
13240 let code_stride = out_features * in_features;
13241 let mut codes = vec![0u8; expert_count * code_stride];
13242 for expert in 0..expert_count {
13243 for row in 0..out_features {
13244 for block in 0..4 {
13245 let value = (expert * 80 + block * 16 + row % 16) as u8;
13246 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
13247 codes[start..start + FP8_BLOCK].fill(value);
13248 }
13249 }
13250 }
13251 let scales = vec![
13252 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,
13253 112.0, 113.0, 114.0,
13254 ];
13255 let bank = E4m3ExpertBank {
13256 codes: &codes,
13257 scales: &scales,
13258 expert_count,
13259 out_features,
13260 in_features,
13261 };
13262
13263 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
13264 assert_eq!(rank.out_features, out_features);
13265 assert_eq!(rank.in_features, 256);
13266 assert_eq!(rank.k_blocks, Some(2));
13267 assert_eq!(rank.code_stride, out_features * 256);
13268 assert_eq!(rank.scale_stride, 4);
13269 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
13270 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
13271
13272 let block_stride = out_features * FP8_BLOCK;
13273 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
13274 assert!(
13275 rank.codes[block_stride..block_stride + FP8_BLOCK]
13276 .iter()
13277 .all(|&code| code == 48)
13278 );
13279 assert!(
13280 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
13281 .iter()
13282 .all(|&code| code == 112)
13283 );
13284 assert!(
13285 rank.codes
13286 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
13287 .iter()
13288 .all(|&code| code == 128)
13289 );
13290 }
13291
13292 #[test]
13293 fn step_ep_layer_specs_are_literal_and_fail_closed() {
13294 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
13295 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
13296 assert_eq!(
13297 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
13298 vec![StepEpLayerSpec {
13299 layer: 24,
13300 devices: vec![1, 2],
13301 }]
13302 );
13303 assert_eq!(
13304 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
13305 vec![
13306 StepEpLayerSpec {
13307 layer: 24,
13308 devices: vec![1, 2],
13309 },
13310 StepEpLayerSpec {
13311 layer: 25,
13312 devices: vec![1, 2],
13313 },
13314 StepEpLayerSpec {
13315 layer: 31,
13316 devices: vec![0, 2],
13317 },
13318 ]
13319 );
13320 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
13321 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
13322 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
13323 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
13324 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
13325 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13326 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
13327 }
13328
13329 #[test]
13330 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
13331 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
13332 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
13333 assert_eq!(
13334 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
13335 vec![
13336 StepTpLayerSpec {
13337 layer: 24,
13338 devices: vec![1, 2],
13339 },
13340 StepTpLayerSpec {
13341 layer: 25,
13342 devices: vec![1, 2],
13343 },
13344 ]
13345 );
13346 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
13347 assert!(error.contains("MEMRA_STEP_TP"));
13348 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
13349 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13350
13351 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
13352 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
13353 assert_eq!(all.first().unwrap().layer, 0);
13354 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
13355 let devices = (0..8).collect::<Vec<_>>();
13356 assert!(all.iter().all(|spec| spec.devices == devices));
13357 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
13358 }
13359}
13360
13361struct TokenGraphChild {
13373 graph: cudarc::driver::CudaGraph,
13374 node: cudarc::driver::sys::CUgraphNode,
13375 ctx: cudarc::driver::sys::CUcontext,
13376}
13377
13378struct TokenGraphFaSite {
13382 ctx: cudarc::driver::sys::CUcontext,
13383 memset_o: cudarc::driver::sys::CUgraphNode,
13384 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
13385 fa: cudarc::driver::sys::CUgraphNode,
13386 combine: cudarc::driver::sys::CUgraphNode,
13387 window: usize,
13388 n_head: usize,
13389 n_head_kv: usize,
13390 head_dim: usize,
13391}
13392
13393pub struct TokenGraphBuilder {
13394 parent: cudarc::driver::sys::CUgraph,
13395 children: Vec<TokenGraphChild>,
13396 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
13399 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
13402 group: Option<(
13405 u32,
13406 Vec<cudarc::driver::sys::CUgraphNode>,
13407 Vec<cudarc::driver::sys::CUgraphNode>,
13408 )>,
13409}
13410
13411unsafe impl Send for TokenGraphBuilder {}
13413
13414impl TokenGraphBuilder {
13415 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
13416 use cudarc::driver::sys;
13417 let mut parent: sys::CUgraph = std::ptr::null_mut();
13418 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
13419 if r != sys::CUresult::CUDA_SUCCESS {
13420 return Err(format!("token graph create: {r:?}").into());
13421 }
13422 Ok(Self {
13423 parent,
13424 children: Vec::new(),
13425 frontier: Vec::new(),
13426 pending_detached: Vec::new(),
13427 group: None,
13428 })
13429 }
13430
13431 fn push_child(
13432 &mut self,
13433 graph: cudarc::driver::CudaGraph,
13434 parallel_group: Option<u32>,
13435 detached: bool,
13436 absorb: bool,
13437 ctx: cudarc::driver::sys::CUcontext,
13438 ) -> Result<(), Box<dyn std::error::Error>> {
13439 use cudarc::driver::sys;
13440 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
13444 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
13445 (state, Some(group)) => {
13446 if let Some((_, _, members)) = state.take() {
13448 self.frontier = members;
13449 }
13450 let base = self.frontier.clone();
13451 *state = Some((group, base.clone(), Vec::new()));
13452 base
13453 }
13454 (state, None) if detached => match state.as_ref() {
13455 Some((_, base, _)) => base.clone(),
13456 None => self.frontier.clone(),
13457 },
13458 (state, None) => {
13459 if let Some((_, _, members)) = state.take() {
13460 self.frontier = members;
13461 }
13462 let mut deps = self.frontier.clone();
13463 if absorb {
13464 deps.append(&mut self.pending_detached);
13465 }
13466 deps
13467 }
13468 };
13469 let mut node: sys::CUgraphNode = std::ptr::null_mut();
13470 let r = unsafe {
13471 sys::cuGraphAddChildGraphNode(
13472 &mut node,
13473 self.parent,
13474 if deps.is_empty() {
13475 std::ptr::null()
13476 } else {
13477 deps.as_ptr()
13478 },
13479 deps.len(),
13480 graph.cu_graph(),
13481 )
13482 };
13483 if r != sys::CUresult::CUDA_SUCCESS {
13484 return Err(format!("token graph child: {r:?}").into());
13485 }
13486 match (&mut self.group, parallel_group, detached) {
13487 (_, None, true) => self.pending_detached.push(node),
13488 (Some((_, _, members)), Some(_), _) => members.push(node),
13489 _ => self.frontier = vec![node],
13490 }
13491 self.children.push(TokenGraphChild { graph, node, ctx });
13492 Ok(())
13493 }
13494
13495 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
13496 use cudarc::driver::sys;
13497 if let Some((_, _, members)) = self.group.take() {
13498 self.frontier = members;
13499 }
13500 let mut fa_sites = Vec::new();
13503 for child in &self.children {
13504 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
13505 fa_sites.push(site);
13506 }
13507 }
13508 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
13509 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
13510 if r != sys::CUresult::CUDA_SUCCESS {
13511 return Err(format!("token graph instantiate: {r:?}").into());
13512 }
13513 Ok(TokenGraph {
13514 exec,
13515 parent: self.parent,
13516 _children: self.children,
13517 fa_sites,
13518 })
13519 }
13520}
13521
13522fn discover_fa_site(
13525 child_node: cudarc::driver::sys::CUgraphNode,
13526 ctx: cudarc::driver::sys::CUcontext,
13527) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
13528 use cudarc::driver::sys;
13529 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13530 if r == sys::CUresult::CUDA_SUCCESS {
13531 Ok(())
13532 } else {
13533 Err(format!("{what}: {r:?}").into())
13534 }
13535 }
13536 let mut graph: sys::CUgraph = std::ptr::null_mut();
13537 unsafe {
13538 cu_try(
13539 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
13540 "fa-site child GetGraph",
13541 )?;
13542 }
13543 let mut count: usize = 0;
13544 unsafe {
13545 cu_try(
13546 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
13547 "fa-site GetNodes(count)",
13548 )?;
13549 }
13550 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
13551 unsafe {
13552 cu_try(
13553 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
13554 "fa-site GetNodes",
13555 )?;
13556 }
13557 nodes.truncate(count);
13558 let node_type =
13559 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
13560 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
13561 unsafe {
13562 cu_try(
13563 sys::cuGraphNodeGetType(node, &mut ty),
13564 "fa-site NodeGetType",
13565 )?;
13566 }
13567 Ok(ty)
13568 };
13569 let memsets: Vec<sys::CUgraphNode> = {
13570 let mut v = Vec::new();
13571 for &node in &nodes {
13572 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
13573 v.push(node);
13574 }
13575 }
13576 v
13577 };
13578 if memsets.len() != 3 {
13579 return Ok(None);
13580 }
13581 let dependents =
13583 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
13584 let mut n: usize = 0;
13585 unsafe {
13586 cu_try(
13587 sys::cuGraphNodeGetDependentNodes_v2(
13588 node,
13589 std::ptr::null_mut(),
13590 std::ptr::null_mut(),
13591 &mut n,
13592 ),
13593 "fa-site GetDependentNodes(count)",
13594 )?;
13595 }
13596 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
13597 unsafe {
13598 cu_try(
13599 sys::cuGraphNodeGetDependentNodes_v2(
13600 node,
13601 v.as_mut_ptr(),
13602 std::ptr::null_mut(),
13603 &mut n,
13604 ),
13605 "fa-site GetDependentNodes",
13606 )?;
13607 }
13608 v.truncate(n);
13609 Ok(v)
13610 };
13611 let mut fa: Option<sys::CUgraphNode> = None;
13614 let mut last_memset: Option<sys::CUgraphNode> = None;
13615 for &ms in &memsets {
13616 for dep in dependents(ms)? {
13617 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13618 fa = Some(dep);
13619 last_memset = Some(ms);
13620 }
13621 }
13622 }
13623 let (Some(fa), Some(_last)) = (fa, last_memset) else {
13624 return Ok(None);
13625 };
13626 let mut combine: Option<sys::CUgraphNode> = None;
13627 for dep in dependents(fa)? {
13628 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13629 combine = Some(dep);
13630 }
13631 }
13632 let Some(combine) = combine else {
13633 return Ok(None);
13634 };
13635 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13638 unsafe {
13639 cu_try(
13640 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
13641 "fa-site KernelNodeGetParams",
13642 )?;
13643 }
13644 let arg_i32 =
13645 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
13646 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
13647 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
13649 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13650 unsafe {
13651 cu_try(
13652 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13653 "fa-site MemsetNodeGetParams",
13654 )?;
13655 }
13656 Ok(mp.width)
13657 };
13658 let mut widest = memsets[0];
13659 for &ms in &memsets[1..] {
13660 if width_of(ms)? > width_of(widest)? {
13661 widest = ms;
13662 }
13663 }
13664 let memset_m: Vec<sys::CUgraphNode> =
13665 memsets.iter().copied().filter(|&m| m != widest).collect();
13666 Ok(Some(TokenGraphFaSite {
13667 ctx,
13668 memset_o: widest,
13669 memset_m: [memset_m[0], memset_m[1]],
13670 fa,
13671 combine,
13672 window: win as usize,
13673 n_head: nh as usize,
13674 n_head_kv: nhkv as usize,
13675 head_dim: hd as usize,
13676 }))
13677}
13678
13679pub struct TokenGraph {
13680 exec: cudarc::driver::sys::CUgraphExec,
13681 parent: cudarc::driver::sys::CUgraph,
13682 _children: Vec<TokenGraphChild>,
13683 fa_sites: Vec<TokenGraphFaSite>,
13684}
13685
13686unsafe impl Send for TokenGraph {}
13687
13688impl TokenGraph {
13689 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
13694 use cudarc::driver::sys;
13695 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13696 if r == sys::CUresult::CUDA_SUCCESS {
13697 Ok(())
13698 } else {
13699 Err(format!("{what}: {r:?}").into())
13700 }
13701 }
13702 for site in &self.fa_sites {
13703 let layer_bucket = if site.window > 0 {
13704 bucket.min(site.window)
13705 } else {
13706 bucket
13707 };
13708 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
13709 let nsp = layer_bucket.div_ceil(sp).max(1);
13710 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13712 unsafe {
13713 cu_try(
13714 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
13715 "retarget fa GetParams",
13716 )?;
13717 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
13718 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
13719 params.gridDimY = nsp as u32;
13720 cu_try(
13721 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
13722 "retarget fa SetParams",
13723 )?;
13724 }
13725 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13727 unsafe {
13728 cu_try(
13729 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
13730 "retarget combine GetParams",
13731 )?;
13732 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
13733 cu_try(
13734 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
13735 "retarget combine SetParams",
13736 )?;
13737 }
13738 let set_width =
13740 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
13741 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13742 unsafe {
13743 cu_try(
13744 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13745 "retarget memset GetParams",
13746 )?;
13747 }
13748 mp.width = width;
13749 unsafe {
13750 cu_try(
13751 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
13752 "retarget memset SetParams",
13753 )?;
13754 }
13755 Ok(())
13756 };
13757 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
13758 set_width(site.memset_m[0], site.n_head * nsp)?;
13759 set_width(site.memset_m[1], site.n_head * nsp)?;
13760 }
13761 Ok(())
13762 }
13763
13764 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
13765 use cudarc::driver::sys;
13766 let _main = e.gpu.enter_main()?;
13767 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
13768 if r != sys::CUresult::CUDA_SUCCESS {
13769 return Err(format!("token graph launch: {r:?}").into());
13770 }
13771 Ok(())
13772 }
13773}
13774
13775impl Drop for TokenGraph {
13776 fn drop(&mut self) {
13777 unsafe {
13778 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
13779 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
13780 }
13781 }
13782}
13783
13784std::thread_local! {
13785 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
13786 const { std::cell::RefCell::new(None) };
13787}
13788
13789pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
13791 let builder = TokenGraphBuilder::new()?;
13792 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
13793 Ok(())
13794}
13795
13796pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
13798 let builder = TOKEN_GRAPH_BUILDER
13799 .with(|cell| cell.borrow_mut().take())
13800 .ok_or("token graph build was not begun")?;
13801 builder.finish()
13802}
13803
13804pub fn token_graph_building() -> bool {
13806 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
13807}
13808
13809pub fn graph_section<F>(
13814 engine: &Engine,
13815 parallel_group: Option<u32>,
13816 f: F,
13817) -> Result<(), Box<dyn std::error::Error>>
13818where
13819 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13820{
13821 graph_section_opts(engine, parallel_group, false, false, f)
13822}
13823
13824pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13826where
13827 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13828{
13829 graph_section_opts(engine, None, false, true, f)
13830}
13831
13832pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13835where
13836 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13837{
13838 graph_section_opts(engine, None, true, false, f)
13839}
13840
13841pub fn graph_section_opts<F>(
13842 engine: &Engine,
13843 parallel_group: Option<u32>,
13844 detached: bool,
13845 absorb: bool,
13846 f: F,
13847) -> Result<(), Box<dyn std::error::Error>>
13848where
13849 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13850{
13851 let building = token_graph_building();
13852 if !building {
13853 let mut f = f;
13854 return f();
13855 }
13856 let (child, ctx) = {
13857 let _main = engine.gpu.enter_main()?;
13858 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
13859 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
13860 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13861 return Err(format!("graph section ctx query: {r:?}").into());
13862 }
13863 let mut f = f;
13864 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
13867 (child, ctx)
13868 };
13869 TOKEN_GRAPH_BUILDER.with(|cell| {
13870 cell.borrow_mut()
13871 .as_mut()
13872 .expect("builder checked above")
13873 .push_child(child, parallel_group, detached, absorb, ctx)
13874 })
13875}