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 {
196 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
197 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
198}
199
200pub(crate) fn rows_tab_host(
224 parts_rank: &[[u64; 4]],
225 ctr_base: u64,
226 same_session: bool,
227 t: usize,
228) -> Vec<u64> {
229 let mut host = Vec::with_capacity(t * 6);
230 for (r, parts) in parts_rank.iter().enumerate().take(t) {
231 host.extend_from_slice(&[
232 parts[0],
233 parts[1],
234 parts[2],
235 parts[3],
236 if same_session {
237 ctr_base
238 } else {
239 ctr_base + (r as u64) * 4
240 },
241 if same_session {
242 (t - 1 - r) as u64
243 } else {
244 0u64
245 },
246 ]);
247 }
248 host
249}
250
251#[cfg(test)]
255pub(crate) fn retired_rows_tab_key(kp: u64, bp: u64, il: usize, t: usize) -> u64 {
256 kp.rotate_left(17)
257 .wrapping_add(bp)
258 .wrapping_add((il as u64) << 32)
259 .wrapping_add(t as u64)
260 .wrapping_add(1 << 63)
261}
262
263pub(crate) fn rows_tab_restage_on() -> bool {
264 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
265 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_RESTAGE").as_deref() != Ok("0"))
266}
267
268pub(crate) fn rows_tab_stale_scan() -> bool {
277 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
278 *ON.get_or_init(|| std::env::var("MEMRA_ROWS_TAB_STALE_SCAN").as_deref() == Ok("1"))
279}
280
281pub(crate) static ROWS_TAB_ENGAGED: std::sync::atomic::AtomicU64 =
282 std::sync::atomic::AtomicU64::new(0);
283pub(crate) static ROWS_TAB_STALE: std::sync::atomic::AtomicU64 =
284 std::sync::atomic::AtomicU64::new(0);
285
286pub(crate) fn spec_fa2_on() -> bool {
287 static ENV: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
288 crate::step37_door(&ENV, "MEMRA_SPEC_FA2")
289}
290thread_local! {
291 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
294 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
295}
296pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
297 SPEC_FA2_DEFER.with(|x| x.set(c));
298}
299pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
300 SPEC_FA2_DEFER.with(|x| x.take())
301}
302pub(crate) fn set_spec_fa2_stashed() {
303 SPEC_FA2_STASHED.with(|x| x.set(true));
304}
305pub(crate) fn take_spec_fa2_stashed() -> bool {
306 SPEC_FA2_STASHED.with(|x| x.replace(false))
307}
308
309pub(crate) fn sel_mirror_on() -> bool {
310 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
311 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
312}
313
314pub(crate) fn step_nvfp4_ep2_on() -> bool {
321 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
322 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
323}
324
325pub(crate) fn sel_down8_on() -> bool {
326 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
327 *ON.get_or_init(|| std::env::var("MEMRA_SEL_DOWN8").as_deref() == Ok("1"))
328}
329
330pub(crate) fn oproj_direct_on() -> bool {
331 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
332 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
333}
334
335pub(crate) fn raw_copy_bytes(
336 dst: u64,
337 src: u64,
338 bytes: usize,
339 engine: &Engine,
340) -> Result<(), Box<dyn std::error::Error>> {
341 use cudarc::driver::sys;
342 let r = unsafe {
343 sys::cuMemcpyAsync(
344 dst as sys::CUdeviceptr,
345 src as sys::CUdeviceptr,
346 bytes,
347 engine.stream().cu_stream() as sys::CUstream,
348 )
349 };
350 if r == sys::CUresult::CUDA_SUCCESS {
351 Ok(())
352 } else {
353 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
356 eprintln!(
357 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
358 std::backtrace::Backtrace::force_capture()
359 );
360 }
361 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
362 }
363}
364
365pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
366 let silu = gate / (1.0 + (-gate).exp());
367 match limit {
368 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
369 None => silu * up,
370 }
371}
372
373#[derive(Debug, Clone, PartialEq, Eq)]
374struct ExpertOwnerRoutes {
375 rank: usize,
376 selected: Vec<usize>,
377 token_rows: Vec<usize>,
378 global_pairs: Vec<usize>,
379}
380
381fn partition_expert_owner_routes(
382 expert_count: usize,
383 ranks: usize,
384 tokens: usize,
385 experts_per_token: usize,
386 selected: &[usize],
387) -> Result<Vec<ExpertOwnerRoutes>, String> {
388 if expert_count == 0
389 || ranks == 0
390 || tokens == 0
391 || experts_per_token == 0
392 || expert_count % ranks != 0
393 {
394 return Err(format!(
395 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
396 tokens={tokens} experts_per_token={experts_per_token}"
397 ));
398 }
399 let pairs = tokens
400 .checked_mul(experts_per_token)
401 .ok_or("expert-owner route count overflow")?;
402 if selected.len() != pairs {
403 return Err(format!(
404 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
405 selected.len()
406 ));
407 }
408 let per_rank = expert_count / ranks;
409 let mut owners = (0..ranks)
410 .map(|rank| ExpertOwnerRoutes {
411 rank,
412 selected: Vec::new(),
413 token_rows: Vec::new(),
414 global_pairs: Vec::new(),
415 })
416 .collect::<Vec<_>>();
417 for (pair, &expert) in selected.iter().enumerate() {
418 if expert >= expert_count {
419 return Err(format!(
420 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
421 ));
422 }
423 let rank = expert / per_rank;
424 owners[rank].selected.push(expert - rank * per_rank);
425 owners[rank].token_rows.push(pair / experts_per_token);
426 owners[rank].global_pairs.push(pair);
427 }
428 Ok(owners)
429}
430
431fn validate_step_grouped_owner_routes(
432 expert_count: usize,
433 tokens: usize,
434 selected: &[usize],
435) -> Result<usize, String> {
436 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
437 return Err(format!(
438 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
439 experts={expert_count} tokens={tokens}",
440 STEP_GROUPED_FP8_EXPERTS
441 ));
442 }
443 let pairs = tokens
444 .checked_mul(STEP_GROUPED_FP8_TOP_K)
445 .ok_or("official Step owner-grouped FP8 route count overflow")?;
446 if selected.len() != pairs {
447 return Err(format!(
448 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
449 selected.len(),
450 STEP_GROUPED_FP8_TOP_K,
451 ));
452 }
453 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
454 let mut unique = routes.to_vec();
455 unique.sort_unstable();
456 unique.dedup();
457 if unique.len() != STEP_GROUPED_FP8_TOP_K {
458 return Err(format!(
459 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
460 {routes:?}"
461 ));
462 }
463 }
464 Ok(pairs)
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
468struct WeightedRouteCombineShape {
469 pairs: usize,
470 max_pairs: usize,
471}
472
473fn validate_weighted_route_combine(
474 width: usize,
475 experts_per_token: usize,
476 max_tokens: usize,
477 tokens: usize,
478 owner_global_pairs: &[&[usize]],
479 route_weights: &[f32],
480) -> Result<WeightedRouteCombineShape, String> {
481 if width == 0
482 || experts_per_token == 0
483 || max_tokens == 0
484 || tokens == 0
485 || tokens > max_tokens
486 || width > i32::MAX as usize
487 || experts_per_token > i32::MAX as usize
488 || tokens > i32::MAX as usize
489 {
490 return Err(format!(
491 "invalid weighted route combine geometry width={width} experts_per_token=\
492 {experts_per_token} tokens={tokens}/{max_tokens}"
493 ));
494 }
495 let pairs = tokens
496 .checked_mul(experts_per_token)
497 .ok_or("weighted route combine pair count overflow")?;
498 let max_pairs = max_tokens
499 .checked_mul(experts_per_token)
500 .ok_or("weighted route combine capacity overflow")?;
501 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
502 return Err(format!(
503 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
504 route_weights.len()
505 ));
506 }
507 let mut seen = vec![false; pairs];
508 let mut observed = 0usize;
509 for pairs_for_owner in owner_global_pairs {
510 observed = observed
511 .checked_add(pairs_for_owner.len())
512 .ok_or("weighted route combine observed pair count overflow")?;
513 for &pair in *pairs_for_owner {
514 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
515 return Err(format!(
516 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
517 ));
518 }
519 }
520 }
521 if observed != pairs || seen.iter().any(|present| !present) {
522 return Err(format!(
523 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
524 ));
525 }
526 Ok(WeightedRouteCombineShape { pairs, max_pairs })
527}
528
529fn cache_rank_rows(
530 rows: &[u8],
531 tokens: usize,
532 local_token_bytes: usize,
533 ranks: usize,
534 rank: usize,
535) -> Result<Vec<u8>, String> {
536 if ranks == 0 || rank >= ranks {
537 return Err(format!(
538 "TP cache rank {rank} is outside a {ranks}-rank layout"
539 ));
540 }
541 let global_token_bytes = local_token_bytes
542 .checked_mul(ranks)
543 .ok_or("TP cache global token-byte overflow")?;
544 let expected = tokens
545 .checked_mul(global_token_bytes)
546 .ok_or("TP cache row-byte overflow")?;
547 if rows.len() != expected {
548 return Err(format!(
549 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
550 rows.len()
551 ));
552 }
553 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
554 for token in 0..tokens {
555 let start = token * global_token_bytes + rank * local_token_bytes;
556 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
557 }
558 Ok(shard)
559}
560
561fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
562 match value {
563 None | Some("") | Some("0") => Ok(false),
564 Some("1") => Ok(true),
565 Some(value) => Err(format!(
566 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
567 )),
568 }
569}
570
571pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
572 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
573}
574
575fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
576 match value {
577 None | Some("") | Some("0") => Ok(false),
578 Some("1") => Ok(true),
579 Some(value) => Err(format!(
580 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
581 )),
582 }
583}
584
585pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
586 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
587}
588
589fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
590 match value {
591 None | Some("") | Some("0") => Ok(false),
592 Some("1") => Ok(true),
593 Some(value) => Err(format!(
594 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
595 )),
596 }
597}
598
599fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
600 match value {
601 None | Some("") | Some("0") => Ok(false),
602 Some("1") => Ok(true),
603 Some(value) => Err(format!(
604 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
605 )),
606 }
607}
608
609pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
612 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
613}
614
615pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
616 parse_step_ep_device_arithmetic(
617 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
618 .ok()
619 .as_deref(),
620 )
621}
622
623fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
624 match value {
625 None | Some("") | Some("0") => Ok(false),
626 Some("1") => Ok(true),
627 Some(value) => Err(format!(
628 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
629 )),
630 }
631}
632
633pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
634 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
635}
636
637fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
638 match value {
639 None | Some("") | Some("0") => Ok(false),
640 Some("1") => Ok(true),
641 Some(value) => Err(format!(
642 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
643 )),
644 }
645}
646
647pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
652 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
653}
654
655fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
656 match value {
657 None | Some("") | Some("0") => Ok(false),
658 Some("1") => Ok(true),
659 Some(value) => Err(format!(
660 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
661 )),
662 }
663}
664
665fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
666 match value {
667 None | Some("") | Some("0") => Ok(false),
668 Some("1") => Ok(true),
669 Some(value) => Err(format!(
670 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
671 )),
672 }
673}
674
675pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
679 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
680}
681
682fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
683 match value {
684 None | Some("") | Some("0") => Ok(false),
685 Some("1") => Ok(true),
686 Some(value) => Err(format!(
687 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
688 )),
689 }
690}
691
692fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
693 match value {
694 None | Some("") | Some("0") => Ok(false),
695 Some("1") => Ok(true),
696 Some(value) => Err(format!(
697 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
698 )),
699 }
700}
701
702pub fn step_tp_dcw_enabled() -> Result<bool, String> {
707 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
708}
709
710pub fn step_tp_graph_enabled() -> Result<bool, String> {
715 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
716}
717
718pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
722 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
723}
724
725#[derive(Debug, Clone, PartialEq, Eq)]
726pub struct StepEpLayerSpec {
727 pub layer: usize,
728 pub devices: Vec<usize>,
729}
730
731pub type StepTpLayerSpec = StepEpLayerSpec;
732
733fn parse_step_layer_specs(
734 flag: &str,
735 value: Option<&str>,
736 allow_full_model: bool,
737) -> Result<Vec<StepEpLayerSpec>, String> {
738 let Some(value) = value else {
739 return Ok(Vec::new());
740 };
741 if value.is_empty() || value == "0" {
742 return Ok(Vec::new());
743 }
744
745 let mut specs = Vec::new();
746 for item in value.split(';') {
747 let (layers, devices) = item.split_once('@').ok_or_else(|| {
748 let layers = if allow_full_model {
749 "LAYER[-LAYER] or all"
750 } else {
751 "LAYER[-LAYER]"
752 };
753 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
754 })?;
755 let (first, last) = if layers == "all" {
756 if !allow_full_model {
757 return Err(format!(
758 "{flag} does not support the full-model shorthand; assign routed layers \
759 explicitly"
760 ));
761 }
762 (0, STEP37_TRUNK_LAYERS - 1)
763 } else {
764 match layers.split_once('-') {
765 Some((first, last)) => {
766 let first = first
767 .parse::<usize>()
768 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
769 let last = last
770 .parse::<usize>()
771 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
772 if first > last {
773 return Err(format!("{flag} layer range {first}-{last} is reversed"));
774 }
775 if last - first + 1 > 128 {
776 return Err(format!(
777 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
778 ));
779 }
780 (first, last)
781 }
782 None => {
783 let layer = layers
784 .parse::<usize>()
785 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
786 (layer, layer)
787 }
788 }
789 };
790 let devices = devices
791 .split(',')
792 .map(|device| {
793 device
794 .parse::<usize>()
795 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
796 })
797 .collect::<Result<Vec<_>, _>>()?;
798 if !(2..=8).contains(&devices.len()) {
799 return Err(format!(
800 "{flag} requires 2..=8 devices, got {}",
801 devices.len()
802 ));
803 }
804 let mut unique = devices.clone();
805 unique.sort_unstable();
806 unique.dedup();
807 if unique.len() != devices.len() {
808 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
809 }
810 for layer in first..=last {
811 if specs
812 .iter()
813 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
814 {
815 return Err(format!("{flag} assigns layer {layer} more than once"));
816 }
817 specs.push(StepEpLayerSpec {
818 layer,
819 devices: devices.clone(),
820 });
821 }
822 }
823 Ok(specs)
824}
825
826pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
827 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
828}
829
830pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
831 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
832}
833
834pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
835 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
836}
837
838pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
839 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
840}
841
842#[derive(Clone, Copy)]
843pub struct E4m3BlockMatrix<'a> {
844 pub codes: &'a [u8],
845 pub scales: &'a [f32],
846 pub out_features: usize,
847 pub in_features: usize,
848}
849
850impl E4m3BlockMatrix<'_> {
851 fn validate(&self) -> Result<(), String> {
852 let code_count = self
853 .out_features
854 .checked_mul(self.in_features)
855 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
856 if self.codes.len() != code_count {
857 return Err(format!(
858 "E4M3 code count {} != {}x{} ({code_count})",
859 self.codes.len(),
860 self.out_features,
861 self.in_features,
862 ));
863 }
864 let scale_count =
865 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
866 if self.scales.len() != scale_count {
867 return Err(format!(
868 "E4M3 scale count {} != {scale_count} for {}x{}",
869 self.scales.len(),
870 self.out_features,
871 self.in_features,
872 ));
873 }
874 if !self
875 .scales
876 .iter()
877 .all(|scale| scale.is_finite() && *scale > 0.0)
878 {
879 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
880 }
881 Ok(())
882 }
883}
884
885#[derive(Clone, Copy)]
886pub struct E4m3ExpertBank<'a> {
887 pub codes: &'a [u8],
888 pub scales: &'a [f32],
889 pub expert_count: usize,
890 pub out_features: usize,
891 pub in_features: usize,
892}
893
894impl E4m3ExpertBank<'_> {
895 fn validate(&self) -> Result<(), String> {
896 if self.expert_count == 0 {
897 return Err("E4M3 expert bank is empty".to_string());
898 }
899 let code_stride = self
900 .out_features
901 .checked_mul(self.in_features)
902 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
903 let code_count = self
904 .expert_count
905 .checked_mul(code_stride)
906 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
907 if self.codes.len() != code_count {
908 return Err(format!(
909 "E4M3 expert code count {} != {}x{} ({code_count})",
910 self.codes.len(),
911 self.expert_count,
912 code_stride,
913 ));
914 }
915 let scale_stride =
916 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
917 let scale_count = self
918 .expert_count
919 .checked_mul(scale_stride)
920 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
921 if self.scales.len() != scale_count {
922 return Err(format!(
923 "E4M3 expert scale count {} != {}x{} ({scale_count})",
924 self.scales.len(),
925 self.expert_count,
926 scale_stride,
927 ));
928 }
929 if !self
930 .scales
931 .iter()
932 .all(|scale| scale.is_finite() && *scale > 0.0)
933 {
934 return Err(
935 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
936 );
937 }
938 Ok(())
939 }
940
941 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
942 if expert >= self.expert_count {
943 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
944 }
945 let code_stride = self.out_features * self.in_features;
946 let scale_stride =
947 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
948 Ok(E4m3BlockMatrix {
949 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
950 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
951 out_features: self.out_features,
952 in_features: self.in_features,
953 })
954 }
955}
956
957pub struct ColumnParallelResult {
958 pub gathered: Vec<f32>,
959 pub rank_outputs: Vec<Vec<f32>>,
960}
961
962pub struct RowParallelResult {
963 pub reduced: Vec<f32>,
964 pub rank_partials: Vec<Vec<f32>>,
965}
966
967#[derive(Clone, Copy)]
968pub struct Bf16Matrix<'a> {
969 pub bytes: &'a [u8],
970 pub out_features: usize,
971 pub in_features: usize,
972}
973
974impl Bf16Matrix<'_> {
975 pub fn validate(&self) -> Result<(), String> {
976 if self.out_features == 0 || self.in_features == 0 {
977 return Err("BF16 matrix dimensions must be nonzero".into());
978 }
979 let expected = self
980 .out_features
981 .checked_mul(self.in_features)
982 .and_then(|values| values.checked_mul(2))
983 .ok_or("BF16 matrix byte count overflow")?;
984 if self.bytes.len() != expected {
985 return Err(format!(
986 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
987 self.bytes.len(),
988 self.out_features,
989 self.in_features,
990 ));
991 }
992 Ok(())
993 }
994}
995
996struct ResidentE4m3Rank {
997 codes: CudaSlice<u8>,
998 scales: CudaSlice<f32>,
999 out_features: usize,
1000 in_features: usize,
1001}
1002
1003enum ResidentBf16Weight {
1004 Bf16(CudaSlice<u8>),
1005 F32(CudaSlice<f32>),
1006}
1007
1008impl ResidentBf16Weight {
1009 fn ordinal(&self) -> usize {
1010 match self {
1011 Self::Bf16(bytes) => bytes.ordinal(),
1012 Self::F32(values) => values.ordinal(),
1013 }
1014 }
1015}
1016
1017struct ResidentBf16Rank {
1018 weight: ResidentBf16Weight,
1019 out_features: usize,
1020 in_features: usize,
1021 q8: Option<CudaSlice<u8>>,
1024}
1025
1026pub struct ResidentColumnParallel {
1027 ranks: Vec<ResidentE4m3Rank>,
1028 out_features: usize,
1029 in_features: usize,
1030}
1031
1032pub struct ResidentRowParallel {
1033 ranks: Vec<ResidentE4m3Rank>,
1034 out_features: usize,
1035 in_features: usize,
1036}
1037
1038pub struct ResidentBf16ColumnParallel {
1039 ranks: Vec<ResidentBf16Rank>,
1040 out_features: usize,
1041 in_features: usize,
1042 canonical_chunk_rows: Option<usize>,
1043}
1044
1045pub struct ResidentBf16RowParallel {
1046 ranks: Vec<ResidentBf16Rank>,
1047 out_features: usize,
1048 in_features: usize,
1049}
1050
1051pub struct ResidentStepBf16RowParallel {
1052 ranks: Vec<Vec<ResidentBf16Rank>>,
1053 out_features: usize,
1054 in_features: usize,
1055 canonical_chunk_cols: usize,
1056}
1057
1058pub struct ResidentSigmoidTopKRouter {
1060 weight: CudaSlice<f32>,
1061 correction_bias: CudaSlice<f32>,
1062 active: CudaSlice<u8>,
1063 root_device: usize,
1064 input_width: usize,
1065 expert_count: usize,
1066 experts_per_token: usize,
1067 active_count: usize,
1068 scaling_factor: f32,
1069 route_norm: bool,
1070}
1071
1072pub struct SigmoidTopKHostOutput {
1073 pub logits: Vec<f32>,
1074 pub selected: Vec<u32>,
1075 pub weights: Vec<f32>,
1076}
1077
1078pub struct ResidentReplicatedBf16SwiGlu {
1080 gate: Vec<ResidentBf16Rank>,
1081 up: Vec<ResidentBf16Rank>,
1082 down: Vec<ResidentBf16Rank>,
1083 input_width: usize,
1084 intermediate_width: usize,
1085}
1086
1087pub struct ResidentReplicatedDeviceRows {
1092 ranks: Vec<CudaSlice<f32>>,
1093 tokens: usize,
1094 width: usize,
1095}
1096
1097impl ResidentReplicatedDeviceRows {
1098 pub fn tokens(&self) -> usize {
1099 self.tokens
1100 }
1101
1102 pub fn width(&self) -> usize {
1103 self.width
1104 }
1105
1106 pub fn ranks(&self) -> usize {
1107 self.ranks.len()
1108 }
1109}
1110
1111pub fn moe_residual_host(
1113 residual: &[f32],
1114 routed: &[f32],
1115 shared: &[f32],
1116) -> Result<Vec<f32>, String> {
1117 if residual.len() != routed.len() || residual.len() != shared.len() {
1118 return Err(format!(
1119 "MoE residual lengths residual={} routed={} shared={}",
1120 residual.len(),
1121 routed.len(),
1122 shared.len()
1123 ));
1124 }
1125 let ffn = routed
1126 .iter()
1127 .zip(shared)
1128 .map(|(&routed, &shared)| routed + shared)
1129 .collect::<Vec<_>>();
1130 Ok(residual
1131 .iter()
1132 .zip(ffn)
1133 .map(|(&residual, ffn)| residual + ffn)
1134 .collect())
1135}
1136
1137pub use memra_kv::{
1138 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1139};
1140
1141pub struct ResidentTpExpert {
1147 gate: ResidentColumnParallel,
1148 up: ResidentColumnParallel,
1149 down: ResidentRowParallel,
1150 input_width: usize,
1151 expert_width: usize,
1152}
1153
1154struct ResidentE4m3ExpertBankRank {
1155 codes: CudaSlice<u8>,
1156 scales: CudaSlice<f32>,
1157 expert_range: Range<usize>,
1158 out_features: usize,
1159 in_features: usize,
1160 code_stride: usize,
1161 scale_stride: usize,
1162 k_blocks: Option<usize>,
1165}
1166
1167struct PackedE4m3ExpertBankRank {
1168 codes: Vec<u8>,
1169 scales: Vec<f32>,
1170 expert_range: Range<usize>,
1171 out_features: usize,
1172 in_features: usize,
1173 code_stride: usize,
1174 scale_stride: usize,
1175 k_blocks: Option<usize>,
1176}
1177
1178struct ResidentEpRank {
1179 gate: ResidentE4m3ExpertBankRank,
1180 up: ResidentE4m3ExpertBankRank,
1181 down: ResidentE4m3ExpertBankRank,
1182}
1183
1184pub struct ResidentExpertParallel {
1191 ranks: Vec<ResidentEpRank>,
1192 expert_count: usize,
1193 input_width: usize,
1194 expert_width: usize,
1195}
1196
1197pub struct StepGroupedFp8ProjectionOutput {
1202 pub gate: Vec<f32>,
1203 pub up: Vec<f32>,
1204 pub down: Vec<f32>,
1205}
1206
1207pub struct PreparedStepGroupedFp8Gate {
1212 device: usize,
1213 gate: ResidentE4m3ExpertBankRank,
1214 up: ResidentE4m3ExpertBankRank,
1215 down: ResidentE4m3ExpertBankRank,
1216 input: CudaSlice<f32>,
1217 route_csr: DeviceExpertCsr,
1218 down_csr: DeviceExpertCsr,
1219 gate_workspace: Fp8GroupedWorkspace,
1220 up_workspace: Fp8GroupedWorkspace,
1221 down_workspace: Fp8GroupedWorkspace,
1222 activation: CudaSlice<f32>,
1223 activation_limit: Option<f32>,
1224 tokens: usize,
1225 pairs: usize,
1226}
1227
1228impl PreparedStepGroupedFp8Gate {
1229 pub fn tokens(&self) -> usize {
1230 self.tokens
1231 }
1232
1233 pub fn pairs(&self) -> usize {
1234 self.pairs
1235 }
1236}
1237
1238struct PreparedStepGroupedExpertOwner {
1239 rank: usize,
1240 global_pairs: Vec<usize>,
1241 route_csr: DeviceExpertCsr,
1242 down_csr: DeviceExpertCsr,
1243 gate_workspace: Fp8GroupedWorkspace,
1244 up_workspace: Fp8GroupedWorkspace,
1245 down_workspace: Fp8GroupedWorkspace,
1246 activation: CudaSlice<f32>,
1247}
1248
1249struct StepGroupedExpertOwnerSchedule {
1250 global_pairs: Vec<usize>,
1251 route_csr: ExpertCsr,
1252 down_csr: ExpertCsr,
1253}
1254
1255pub struct PreparedStepGroupedExpertParallelGate {
1261 rank_inputs: Vec<CudaSlice<f32>>,
1262 owners: Vec<PreparedStepGroupedExpertOwner>,
1263 activation_limit: Option<f32>,
1264 tokens: usize,
1265 pairs: usize,
1266 max_tokens: usize,
1267 max_pairs: usize,
1268 input_width: usize,
1269 expert_width: usize,
1270 generation: u64,
1271 executed_generation: Option<u64>,
1272 ready: bool,
1273}
1274
1275impl PreparedStepGroupedExpertParallelGate {
1276 pub fn tokens(&self) -> usize {
1277 self.tokens
1278 }
1279
1280 pub fn pairs(&self) -> usize {
1281 self.pairs
1282 }
1283
1284 pub fn max_tokens(&self) -> usize {
1285 self.max_tokens
1286 }
1287
1288 pub fn input_width(&self) -> usize {
1289 self.input_width
1290 }
1291
1292 pub fn expert_width(&self) -> usize {
1293 self.expert_width
1294 }
1295
1296 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1297 validate_step_expert_activation_limit(limit)?;
1298 self.activation_limit = limit;
1299 self.executed_generation = None;
1300 Ok(())
1301 }
1302
1303 pub fn active_owners(&self) -> usize {
1304 self.owners
1305 .iter()
1306 .filter(|owner| !owner.global_pairs.is_empty())
1307 .count()
1308 }
1309
1310 pub fn owner_pair_counts(&self) -> Vec<usize> {
1311 self.owners
1312 .iter()
1313 .map(|owner| owner.global_pairs.len())
1314 .collect()
1315 }
1316
1317 pub fn generation(&self) -> u64 {
1318 self.generation
1319 }
1320}
1321
1322struct PreparedPeerWeightedRouteOwner {
1323 token_rows: CudaSlice<i32>,
1324 slots: CudaSlice<i32>,
1325 weights: CudaSlice<f32>,
1326 active_pairs: usize,
1327}
1328
1329pub struct PreparedPeerWeightedRouteCombine {
1335 root_device: usize,
1336 owners: Vec<PreparedPeerWeightedRouteOwner>,
1337 peer_staging: CudaSlice<f32>,
1338 slots: CudaSlice<f32>,
1339 weights: CudaSlice<f32>,
1340 output: CudaSlice<f32>,
1341 peer_devices: Vec<usize>,
1342 peer_outputs: Vec<CudaSlice<f32>>,
1343 width: usize,
1344 experts_per_token: usize,
1345 max_tokens: usize,
1346 max_pairs: usize,
1347 tokens: usize,
1348 pairs: usize,
1349 projection_generation: u64,
1350 output_generation: Option<u64>,
1351 broadcast_generation: Option<u64>,
1352 ready: bool,
1353}
1354
1355impl PreparedPeerWeightedRouteCombine {
1356 pub fn tokens(&self) -> usize {
1357 self.tokens
1358 }
1359
1360 pub fn pairs(&self) -> usize {
1361 self.pairs
1362 }
1363
1364 pub fn owner_pair_counts(&self) -> Vec<usize> {
1365 self.owners.iter().map(|owner| owner.active_pairs).collect()
1366 }
1367
1368 pub fn distributed_ranks(&self) -> usize {
1369 1 + self.peer_outputs.len()
1370 }
1371}
1372
1373struct ResidentTpExpertBank {
1374 gate: Vec<ResidentE4m3ExpertBankRank>,
1375 up: Vec<ResidentE4m3ExpertBankRank>,
1376 down: Vec<ResidentE4m3ExpertBankRank>,
1377 expert_count: usize,
1378 input_width: usize,
1379 expert_width: usize,
1380}
1381
1382pub struct ResidentTensorParallel {
1388 bank: ResidentTpExpertBank,
1389}
1390
1391pub struct TpE4m3HostBounce {
1397 devices: Vec<usize>,
1398 ranks: Vec<Engine>,
1399 native_p2p: bool,
1400 ep_device_arithmetic: bool,
1401 bulk_p2p: bool,
1402 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1405}
1406
1407pub enum StepTpGateShards<'a> {
1416 F32(&'a [crate::CudaSlice<f32>]),
1417 Bf16(&'a [crate::CudaSlice<u8>]),
1418}
1419
1420pub struct StepTpDecodeV2Ws {
1421 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1425 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1426 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1427 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1428 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1429 pub(crate) tcol_cap: usize,
1430 w8_aq: Vec<CudaSlice<i8>>,
1434 w8_ad: Vec<CudaSlice<f32>>,
1435 w8_in: usize,
1436 w8o_aq: Vec<CudaSlice<i8>>,
1439 w8o_ad: Vec<CudaSlice<f32>>,
1440 w8o_in: usize,
1441 w8t_aq: Vec<CudaSlice<i8>>,
1445 w8t_ad: Vec<CudaSlice<f32>>,
1446 w8t_in: usize,
1447 w8t_oaq: Vec<CudaSlice<i8>>,
1448 w8t_oad: Vec<CudaSlice<f32>>,
1449 w8t_oin: usize,
1450 w8t_cap: usize,
1451 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1458 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1459 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1460 pub(crate) fa2_cap: usize,
1461 rope_k_t: Vec<CudaSlice<f32>>,
1465 rope_ctr_t: Vec<CudaSlice<u32>>,
1466 rope_pos_t: Vec<CudaSlice<i32>>,
1467 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1471 rows_tab_t: Vec<CudaSlice<u64>>,
1481 rows_tab_shadow: Vec<std::collections::HashMap<u64, Vec<u64>>>,
1485 tcol_gated: Vec<CudaSlice<f32>>,
1486 tcol_opart: Vec<CudaSlice<f32>>,
1487 tcol_opeer: Option<CudaSlice<f32>>,
1488 tcol_omix: Option<CudaSlice<f32>>,
1489 tcol_ocap: usize,
1490 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1493 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1494 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1495 pub(crate) q: Vec<CudaSlice<f32>>,
1496 pub(crate) k: Vec<CudaSlice<f32>>,
1497 pub(crate) pos: Vec<CudaSlice<i32>>,
1498 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1500 pub(crate) gate: Vec<CudaSlice<f32>>,
1501 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1502 pub(crate) gated: Vec<CudaSlice<f32>>,
1503 o_partials: Vec<Vec<CudaSlice<f32>>>,
1505 ev_rank: Vec<CudaEvent>,
1507 peer_partial: CudaSlice<f32>,
1509 reduce_a: CudaSlice<f32>,
1510 reduce_b: CudaSlice<f32>,
1511 zeros: CudaSlice<f32>,
1513 pub(crate) k_shadow: CudaSlice<f32>,
1514 pub(crate) v_shadow: CudaSlice<f32>,
1515 ev_refresh: CudaEvent,
1516 ev_oproj: CudaEvent,
1517 gate_e: CudaSlice<f32>,
1519 pub(crate) h_stage: Option<CudaSlice<f32>>,
1522 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1523 attn_in: Vec<CudaSlice<f32>>,
1527 raw_h_stage: u64,
1529 raw_pos_stage: u64,
1530 raw_attn_in: Vec<u64>,
1531 raw_pos: Vec<u64>,
1532 raw_o_partial1: u64,
1533 raw_peer_partial: u64,
1534 raw_k1: u64,
1535 raw_v1: u64,
1536 raw_k_shadow: u64,
1537 raw_v_shadow: u64,
1538 raw_mixed_stage_e: u64,
1542 raw_reduce_a: u64,
1543 raw_shadow_stage_e: (u64, u64),
1544 ev_entry: CudaEvent,
1545 e_device: usize,
1546 local_q_dim: usize,
1548 local_kv_dim: usize,
1549 heads: usize,
1550 pub(crate) o_out: usize,
1551 o_block_cols: usize,
1552 blocks_per_rank: usize,
1553}
1554
1555impl TpE4m3HostBounce {
1556 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1557 Self::new_inner(devices, false, false, false, false)
1558 }
1559
1560 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1561 Self::new_inner(devices, false, true, false, false)
1562 }
1563
1564 pub fn new_native_p2p_device_arithmetic(
1565 devices: &[usize],
1566 ) -> Result<Self, Box<dyn std::error::Error>> {
1567 Self::new_inner(devices, false, true, true, false)
1568 }
1569
1570 pub(crate) fn new_configured(
1571 devices: &[usize],
1572 native_p2p: bool,
1573 ep_device_arithmetic: bool,
1574 bulk_p2p: bool,
1575 ) -> Result<Self, Box<dyn std::error::Error>> {
1576 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1577 }
1578
1579 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1584 Self::new_inner(&[device], true, false, false, false)
1585 }
1586
1587 fn new_inner(
1588 devices: &[usize],
1589 allow_single_rank: bool,
1590 native_p2p: bool,
1591 ep_device_arithmetic: bool,
1592 bulk_p2p: bool,
1593 ) -> Result<Self, Box<dyn std::error::Error>> {
1594 if ep_device_arithmetic && !native_p2p {
1595 return Err("device-resident EP arithmetic requires native P2P".into());
1596 }
1597 if bulk_p2p && !native_p2p {
1598 return Err("bulk TP transport requires native P2P".into());
1599 }
1600 let minimum = if allow_single_rank { 1 } else { 2 };
1601 if !(minimum..=8).contains(&devices.len()) {
1602 return Err(format!(
1603 "TP reference requires {minimum}..=8 devices, got {}",
1604 devices.len()
1605 )
1606 .into());
1607 }
1608 let mut unique = devices.to_vec();
1609 unique.sort_unstable();
1610 unique.dedup();
1611 if unique.len() != devices.len() {
1612 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1613 }
1614 let ranks = devices
1615 .iter()
1616 .map(|&device| Engine::new(device))
1617 .collect::<Result<Vec<_>, _>>()?;
1618 if native_p2p {
1619 configure_native_p2p(&ranks, devices)?;
1620 }
1621 if allow_single_rank {
1622 eprintln!(
1623 "[tp] canonical oracle transport=local device={} performance_claim=false",
1624 devices[0]
1625 );
1626 } else if native_p2p {
1627 if ep_device_arithmetic {
1628 eprintln!(
1629 "[tp] correctness transport=native-p2p devices={devices:?} \
1630 native_p2p=true activation=device-host-exact \
1631 accumulation=device-host-exact output=root-readback \
1632 bulk_p2p={bulk_p2p} performance_claim=false"
1633 );
1634 } else {
1635 eprintln!(
1636 "[tp] correctness transport=native-p2p devices={devices:?} \
1637 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1638 performance_claim=false"
1639 );
1640 }
1641 } else {
1642 eprintln!(
1643 "[tp] correctness transport=host-bounce devices={devices:?} \
1644 native_p2p=false performance_claim=false"
1645 );
1646 }
1647 Ok(Self {
1648 devices: devices.to_vec(),
1649 ranks,
1650 native_p2p,
1651 ep_device_arithmetic,
1652 bulk_p2p,
1653 decode_v2: std::sync::Mutex::new(Vec::new()),
1654 })
1655 }
1656
1657 pub fn devices(&self) -> &[usize] {
1658 &self.devices
1659 }
1660
1661 pub fn native_p2p(&self) -> bool {
1662 self.native_p2p
1663 }
1664
1665 pub fn bulk_p2p(&self) -> bool {
1666 self.bulk_p2p
1667 }
1668
1669 pub fn expert_activation_label(&self) -> &'static str {
1670 if self.ep_device_arithmetic {
1671 "device-host-exact"
1672 } else {
1673 "host-canonical"
1674 }
1675 }
1676
1677 pub fn expert_accumulation_label(&self) -> &'static str {
1678 self.expert_activation_label()
1679 }
1680
1681 pub fn expert_output_label(&self) -> &'static str {
1682 if self.ep_device_arithmetic {
1683 "root-readback"
1684 } else {
1685 "host-accumulated"
1686 }
1687 }
1688
1689 pub fn transport_label(&self) -> &'static str {
1690 if self.devices.len() == 1 {
1691 "local"
1692 } else if self.native_p2p {
1693 "native-p2p"
1694 } else {
1695 "host-bounce"
1696 }
1697 }
1698
1699 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1700 self.ranks
1701 .iter()
1702 .map(|rank| rank.ctx().name().map_err(Into::into))
1703 .collect()
1704 }
1705
1706 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1712 self.ranks.get(rank)
1713 }
1714
1715 pub fn allocate_tp_kv_cache(
1716 &self,
1717 kv_dim_k: usize,
1718 kv_dim_v: usize,
1719 capacity: usize,
1720 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1721 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1722 }
1723
1724 pub fn allocate_tp_swa_kv_cache(
1725 &self,
1726 kv_dim_k: usize,
1727 kv_dim_v: usize,
1728 capacity: usize,
1729 window: usize,
1730 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1731 if window == 0 {
1732 return Err("TP SWA KV window must be nonzero".into());
1733 }
1734 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1735 }
1736
1737 fn allocate_tp_kv_cache_inner(
1738 &self,
1739 kv_dim_k: usize,
1740 kv_dim_v: usize,
1741 capacity: usize,
1742 window: Option<usize>,
1743 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1744 if capacity == 0 || capacity > i32::MAX as usize {
1745 return Err(
1746 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1747 );
1748 }
1749 let tp = self.ranks.len();
1750 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1751 let physical_rows = window
1752 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1753 .unwrap_or(capacity);
1754 let k_plane_bytes = physical_rows
1755 .checked_mul(shape.k_token_bytes)
1756 .and_then(|bytes| bytes.checked_add(8))
1757 .ok_or("TP KV K plane-byte overflow")?;
1758 let v_plane_bytes = physical_rows
1759 .checked_mul(shape.v_token_bytes)
1760 .and_then(|bytes| bytes.checked_add(8))
1761 .ok_or("TP KV V plane-byte overflow")?;
1762 let mut ranks = Vec::with_capacity(tp);
1763 for engine in &self.ranks {
1764 let _main = engine.gpu.enter_main()?;
1765 ranks.push(ResidentTpKvCacheRank::new(
1766 engine.alloc_u8(k_plane_bytes)?,
1767 engine.alloc_u8(v_plane_bytes)?,
1768 engine.htod_i32(&[0])?,
1769 ));
1770 }
1771 Ok(match window {
1772 Some(window) => ResidentTpKvCache::new_swa(
1773 ranks,
1774 shape.kv_dim_k,
1775 shape.kv_dim_v,
1776 shape.k_token_bytes,
1777 shape.v_token_bytes,
1778 capacity,
1779 window,
1780 ),
1781 None => ResidentTpKvCache::new(
1782 ranks,
1783 shape.kv_dim_k,
1784 shape.kv_dim_v,
1785 shape.k_token_bytes,
1786 shape.v_token_bytes,
1787 capacity,
1788 ),
1789 })
1790 }
1791
1792 pub fn grow_tp_kv_cache(
1793 &self,
1794 source: &ResidentTpKvCache,
1795 target_capacity: usize,
1796 rows: usize,
1797 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1798 self.validate_tp_kv_cache(source)?;
1799 let plan = source.prepare_grow(target_capacity, rows)?;
1800 let ranks = self.ranks.len();
1801 let global_k = source
1802 .kv_dim_k()
1803 .checked_mul(ranks)
1804 .ok_or("TP KV grow global K dimension overflow")?;
1805 let global_v = source
1806 .kv_dim_v()
1807 .checked_mul(ranks)
1808 .ok_or("TP KV grow global V dimension overflow")?;
1809 let mut target = match source.ring_window() {
1810 Some(window) => {
1811 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1812 }
1813 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1814 };
1815 self.validate_tp_kv_cache(&target)?;
1816
1817 for (rank, engine) in self.ranks.iter().enumerate() {
1818 let _main = engine.gpu.enter_main()?;
1819 let src = source
1820 .rank(rank)
1821 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1822 let dst = target
1823 .rank_mut(rank)
1824 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1825 if plan.k_bytes() > 0 {
1826 engine.copy_u8_range_into(
1827 dst.k_mut(),
1828 0,
1829 src.k(),
1830 plan.source_row() * source.k_tok_bytes(),
1831 plan.k_bytes(),
1832 )?;
1833 }
1834 if plan.v_bytes() > 0 {
1835 engine.copy_u8_range_into(
1836 dst.v_mut(),
1837 0,
1838 src.v(),
1839 plan.source_row() * source.v_tok_bytes(),
1840 plan.v_bytes(),
1841 )?;
1842 }
1843 }
1844 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1845
1846 for engine in &self.ranks {
1849 let _main = engine.gpu.enter_main()?;
1850 engine.stream().synchronize()?;
1851 }
1852 let physical_copy_rows = plan.copy_rows();
1853 target.publish_grow(plan)?;
1854 eprintln!(
1855 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1856 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1857 rank_streams_synchronized=true generation_preserved=true",
1858 rows,
1859 source.capacity(),
1860 target_capacity,
1861 ranks,
1862 physical_copy_rows,
1863 source.ring_window(),
1864 );
1865 Ok(target)
1866 }
1867
1868 pub fn hydrate_tp_kv_cache(
1869 &self,
1870 cache: &mut ResidentTpKvCache,
1871 rows: usize,
1872 k_rows: &[u8],
1873 v_rows: &[u8],
1874 ) -> Result<(), Box<dyn std::error::Error>> {
1875 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1876 }
1877
1878 pub fn hydrate_tp_kv_cache_from(
1879 &self,
1880 cache: &mut ResidentTpKvCache,
1881 logical_len: usize,
1882 resident_start: usize,
1883 k_rows: &[u8],
1884 v_rows: &[u8],
1885 ) -> Result<(), Box<dyn std::error::Error>> {
1886 self.validate_tp_kv_cache(cache)?;
1887 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1888 return Err(format!(
1889 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1890 cache.committed_len(),
1891 cache.staged_len()
1892 )
1893 .into());
1894 }
1895 if resident_start > logical_len || logical_len > cache.capacity() {
1896 return Err(format!(
1897 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1898 cache.capacity(),
1899 )
1900 .into());
1901 }
1902 let rows = logical_len - resident_start;
1903 if rows > cache.physical_capacity() {
1904 return Err(format!(
1905 "TP KV hydration rows {rows} exceed physical capacity {}",
1906 cache.physical_capacity()
1907 )
1908 .into());
1909 }
1910 for rank in 0..self.ranks.len() {
1911 let k_rank =
1912 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1913 let v_rank =
1914 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1915 let engine = &self.ranks[rank];
1916 let _main = engine.gpu.enter_main()?;
1917 let rank_cache = cache
1918 .rank_mut(rank)
1919 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1920 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1921 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1922 }
1923 cache.publish_hydration(logical_len, resident_start)?;
1924 Ok(())
1925 }
1926
1927 pub fn append_tp_kv_transaction(
1928 &self,
1929 cache: &mut ResidentTpKvCache,
1930 transaction: TpKvTransaction,
1931 k_shards: &[CudaSlice<f32>],
1932 v_shards: &[CudaSlice<f32>],
1933 rows: usize,
1934 ) -> Result<(), Box<dyn std::error::Error>> {
1935 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1936 }
1937
1938 #[allow(clippy::too_many_arguments)]
1943 pub fn append_tp_kv_transaction_inner(
1944 &self,
1945 cache: &mut ResidentTpKvCache,
1946 transaction: TpKvTransaction,
1947 k_shards: &[CudaSlice<f32>],
1948 v_shards: &[CudaSlice<f32>],
1949 rows: usize,
1950 external_rank_appends: bool,
1951 ) -> Result<(), Box<dyn std::error::Error>> {
1952 self.validate_tp_kv_cache(cache)?;
1953 let plan = cache.prepare_append(transaction, rows)?;
1954 let target = plan.target();
1955 let expected_k = rows
1956 .checked_mul(cache.kv_dim_k())
1957 .ok_or("TP KV K append size overflow")?;
1958 let expected_v = rows
1959 .checked_mul(cache.kv_dim_v())
1960 .ok_or("TP KV V append size overflow")?;
1961 if !external_rank_appends
1964 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1965 {
1966 return Err(format!(
1967 "TP KV append shard counts k={} v={} != ranks {}",
1968 k_shards.len(),
1969 v_shards.len(),
1970 self.ranks.len()
1971 )
1972 .into());
1973 }
1974 let kv_dim_k = cache.kv_dim_k();
1975 let kv_dim_v = cache.kv_dim_v();
1976 let k_tok_bytes = cache.k_tok_bytes();
1977 let v_tok_bytes = cache.v_tok_bytes();
1978 if let Some(KvRingAppend::Rebase {
1979 src_row,
1980 keep_rows,
1981 new_base,
1982 ..
1983 }) = plan.ring_append()
1984 {
1985 for rank in 0..self.ranks.len() {
1986 let engine = &self.ranks[rank];
1987 let _main = engine.gpu.enter_main()?;
1988 let rank_cache = cache
1989 .rank_mut(rank)
1990 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1991 if keep_rows > 0 {
1992 let k_len = keep_rows
1993 .checked_mul(k_tok_bytes)
1994 .ok_or("TP KV K rebase-byte overflow")?;
1995 let v_len = keep_rows
1996 .checked_mul(v_tok_bytes)
1997 .ok_or("TP KV V rebase-byte overflow")?;
1998 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1999 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
2000 engine.copy_u8_range_into(
2001 &mut k_tmp,
2002 0,
2003 rank_cache.k(),
2004 src_row * k_tok_bytes,
2005 k_len,
2006 )?;
2007 engine.copy_u8_range_into(
2008 &mut v_tmp,
2009 0,
2010 rank_cache.v(),
2011 src_row * v_tok_bytes,
2012 v_len,
2013 )?;
2014 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
2015 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
2016 }
2017 if rank_cache.base_d().is_some() {
2021 let value = new_base as i32;
2022 let rank_cache = cache
2023 .rank_mut(rank)
2024 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2025 if let Some(base_d) = rank_cache.base_d_mut() {
2026 engine.set_i32_one(base_d, value)?;
2027 }
2028 }
2029 }
2030 }
2031 cache.publish_append_rebase(plan)?;
2032 let write_row = plan.write_row();
2033 for rank in 0..self.ranks.len() {
2034 if external_rank_appends {
2035 break;
2036 }
2037 let engine = &self.ranks[rank];
2038 let _main = engine.gpu.enter_main()?;
2039 if k_shards[rank].len() != expected_k
2040 || v_shards[rank].len() != expected_v
2041 || k_shards[rank].ordinal() != engine.ctx().ordinal()
2042 || v_shards[rank].ordinal() != engine.ctx().ordinal()
2043 {
2044 return Err(format!(
2045 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
2046 != expected {expected_k}/{expected_v} on device {}",
2047 k_shards[rank].len(),
2048 k_shards[rank].ordinal(),
2049 v_shards[rank].len(),
2050 v_shards[rank].ordinal(),
2051 engine.ctx().ordinal(),
2052 )
2053 .into());
2054 }
2055 let rank_cache = cache
2056 .rank_mut(rank)
2057 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
2058 let (rank_k, rank_v) = rank_cache.planes_mut();
2059 engine.append_kv_quantized_rows(
2060 &k_shards[rank],
2061 &v_shards[rank],
2062 rank_k,
2063 rank_v,
2064 write_row,
2065 rows,
2066 kv_dim_k,
2067 kv_dim_v,
2068 k_tok_bytes,
2069 v_tok_bytes,
2070 Engine::kv_fp8_on(),
2071 )?;
2072 }
2073 if !external_rank_appends {
2074 self.set_tp_kv_len_mirrors(cache, target)?;
2077 }
2078 cache.publish_append_plan(plan)?;
2079 Ok(())
2080 }
2081
2082 pub fn commit_tp_kv_transaction(
2083 &self,
2084 cache: &mut ResidentTpKvCache,
2085 transaction: TpKvTransaction,
2086 accepted_rows: usize,
2087 ) -> Result<(), Box<dyn std::error::Error>> {
2088 self.validate_tp_kv_cache(cache)?;
2089 let target = cache.commit_target(transaction, accepted_rows)?;
2090 self.set_tp_kv_len_mirrors(cache, target)?;
2091 cache.publish_finalize(transaction, target)?;
2092 Ok(())
2093 }
2094
2095 pub fn commit_tp_kv_transaction_external(
2101 &self,
2102 cache: &mut ResidentTpKvCache,
2103 transaction: TpKvTransaction,
2104 accepted_rows: usize,
2105 ) -> Result<(), Box<dyn std::error::Error>> {
2106 self.validate_tp_kv_cache(cache)?;
2107 let target = cache.commit_target(transaction, accepted_rows)?;
2108 cache.publish_finalize(transaction, target)?;
2109 Ok(())
2110 }
2111
2112 pub fn rollback_tp_kv_transaction(
2113 &self,
2114 cache: &mut ResidentTpKvCache,
2115 transaction: TpKvTransaction,
2116 ) -> Result<(), Box<dyn std::error::Error>> {
2117 self.validate_tp_kv_cache(cache)?;
2118 cache.validate_transaction(transaction)?;
2119 let target = transaction.base_len();
2120 self.set_tp_kv_len_mirrors(cache, target)?;
2121 cache.publish_finalize(transaction, target)?;
2122 Ok(())
2123 }
2124
2125 pub fn tp_kv_device_lengths(
2126 &self,
2127 cache: &ResidentTpKvCache,
2128 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2129 self.validate_tp_kv_cache(cache)?;
2130 let mut lengths = Vec::with_capacity(self.ranks.len());
2131 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2132 let _main = engine.gpu.enter_main()?;
2133 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2134 }
2135 Ok(lengths)
2136 }
2137
2138 fn set_tp_kv_len_mirrors(
2139 &self,
2140 cache: &mut ResidentTpKvCache,
2141 len: usize,
2142 ) -> Result<(), Box<dyn std::error::Error>> {
2143 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2144 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2145 let _main = engine.gpu.enter_main()?;
2146 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2147 }
2148 Ok(())
2149 }
2150
2151 fn validate_tp_kv_cache(
2152 &self,
2153 cache: &ResidentTpKvCache,
2154 ) -> Result<(), Box<dyn std::error::Error>> {
2155 if cache.ranks_len() != self.ranks.len() {
2156 return Err(format!(
2157 "TP KV cache ranks {} != runtime ranks {}",
2158 cache.ranks_len(),
2159 self.ranks.len()
2160 )
2161 .into());
2162 }
2163 let expected_k = cache
2164 .physical_capacity()
2165 .checked_mul(cache.k_tok_bytes())
2166 .and_then(|bytes| bytes.checked_add(8))
2167 .ok_or("TP KV K plane validation overflow")?;
2168 let expected_v = cache
2169 .physical_capacity()
2170 .checked_mul(cache.v_tok_bytes())
2171 .and_then(|bytes| bytes.checked_add(8))
2172 .ok_or("TP KV V plane validation overflow")?;
2173 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2174 let device = engine.ctx().ordinal();
2175 if rank_cache.k().len() != expected_k
2176 || rank_cache.v().len() != expected_v
2177 || rank_cache.len_d().len() != 1
2178 || rank_cache.k().ordinal() != device
2179 || rank_cache.v().ordinal() != device
2180 || rank_cache.len_d().ordinal() != device
2181 {
2182 return Err(format!(
2183 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2184 )
2185 .into());
2186 }
2187 }
2188 Ok(())
2189 }
2190
2191 pub fn full(
2192 &self,
2193 matrix: E4m3BlockMatrix<'_>,
2194 activations: &[f32],
2195 tokens: usize,
2196 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2197 matrix.validate()?;
2198 validate_activations(activations, tokens, matrix.in_features)?;
2199 run_rank(&self.ranks[0], matrix, activations, tokens)
2200 }
2201
2202 pub fn column_parallel(
2206 &self,
2207 matrix: E4m3BlockMatrix<'_>,
2208 activations: &[f32],
2209 tokens: usize,
2210 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2211 matrix.validate()?;
2212 validate_activations(activations, tokens, matrix.in_features)?;
2213 let tp = self.ranks.len();
2214 if matrix.out_features % tp != 0 {
2215 return Err(format!(
2216 "column-parallel out_features {} is not divisible by TP={tp}",
2217 matrix.out_features
2218 )
2219 .into());
2220 }
2221 let local_out = matrix.out_features / tp;
2222 if local_out % FP8_BLOCK != 0 {
2223 return Err(format!(
2224 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2225 E4M3 scale block"
2226 )
2227 .into());
2228 }
2229
2230 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2231 let mut rank_outputs = Vec::with_capacity(tp);
2232 for (rank_index, rank) in self.ranks.iter().enumerate() {
2233 let shard = column_shard(matrix, tp, rank_index)?;
2234 let output = run_rank(rank, shard, activations, tokens)?;
2235 let row_start = rank_index * local_out;
2236 for token in 0..tokens {
2237 gathered[token * matrix.out_features + row_start
2238 ..token * matrix.out_features + row_start + local_out]
2239 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2240 }
2241 rank_outputs.push(output);
2242 }
2243 Ok(ColumnParallelResult {
2244 gathered,
2245 rank_outputs,
2246 })
2247 }
2248
2249 pub fn upload_column_parallel(
2250 &self,
2251 matrix: E4m3BlockMatrix<'_>,
2252 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2253 matrix.validate()?;
2254 let tp = self.ranks.len();
2255 validate_column_shape(matrix, tp)?;
2256 let mut ranks = Vec::with_capacity(tp);
2257 for (rank_index, engine) in self.ranks.iter().enumerate() {
2258 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2259 }
2260 Ok(ResidentColumnParallel {
2261 ranks,
2262 out_features: matrix.out_features,
2263 in_features: matrix.in_features,
2264 })
2265 }
2266
2267 pub fn column_parallel_resident(
2268 &self,
2269 matrix: &ResidentColumnParallel,
2270 activations: &[f32],
2271 tokens: usize,
2272 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2273 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2274 validate_activations(activations, tokens, matrix.in_features)?;
2275 let local_out = matrix.out_features / self.ranks.len();
2276 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2277 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2278 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2279 let output = run_resident_rank(engine, shard, activations, tokens)?;
2280 let row_start = rank_index * local_out;
2281 for token in 0..tokens {
2282 gathered[token * matrix.out_features + row_start
2283 ..token * matrix.out_features + row_start + local_out]
2284 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2285 }
2286 rank_outputs.push(output);
2287 }
2288 Ok(ColumnParallelResult {
2289 gathered,
2290 rank_outputs,
2291 })
2292 }
2293
2294 pub fn row_parallel(
2298 &self,
2299 matrix: E4m3BlockMatrix<'_>,
2300 activations: &[f32],
2301 tokens: usize,
2302 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2303 matrix.validate()?;
2304 validate_activations(activations, tokens, matrix.in_features)?;
2305 let tp = self.ranks.len();
2306 if matrix.in_features % tp != 0 {
2307 return Err(format!(
2308 "row-parallel in_features {} is not divisible by TP={tp}",
2309 matrix.in_features
2310 )
2311 .into());
2312 }
2313 let local_in = matrix.in_features / tp;
2314 if local_in % FP8_BLOCK != 0 {
2315 return Err(format!(
2316 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2317 E4M3 scale block"
2318 )
2319 .into());
2320 }
2321
2322 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2323 let mut rank_partials = Vec::with_capacity(tp);
2324 for (rank_index, rank) in self.ranks.iter().enumerate() {
2325 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2326 let local_activations =
2327 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2328 let shard = E4m3BlockMatrix {
2329 codes: &codes,
2330 scales: &scales,
2331 out_features: matrix.out_features,
2332 in_features: local_in,
2333 };
2334 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2335 for (sum, value) in reduced.iter_mut().zip(&partial) {
2336 *sum += *value;
2337 }
2338 rank_partials.push(partial);
2339 }
2340 Ok(RowParallelResult {
2341 reduced,
2342 rank_partials,
2343 })
2344 }
2345
2346 pub fn upload_row_parallel(
2347 &self,
2348 matrix: E4m3BlockMatrix<'_>,
2349 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2350 matrix.validate()?;
2351 let tp = self.ranks.len();
2352 validate_row_shape(matrix, tp)?;
2353 let local_in = matrix.in_features / tp;
2354 let mut ranks = Vec::with_capacity(tp);
2355 for (rank_index, engine) in self.ranks.iter().enumerate() {
2356 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2357 ranks.push(upload_rank(
2358 engine,
2359 E4m3BlockMatrix {
2360 codes: &codes,
2361 scales: &scales,
2362 out_features: matrix.out_features,
2363 in_features: local_in,
2364 },
2365 )?);
2366 }
2367 Ok(ResidentRowParallel {
2368 ranks,
2369 out_features: matrix.out_features,
2370 in_features: matrix.in_features,
2371 })
2372 }
2373
2374 pub fn row_parallel_resident(
2375 &self,
2376 matrix: &ResidentRowParallel,
2377 activations: &[f32],
2378 tokens: usize,
2379 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2380 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2381 validate_activations(activations, tokens, matrix.in_features)?;
2382 let tp = self.ranks.len();
2383 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2384 let mut rank_partials = Vec::with_capacity(tp);
2385 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2386 let local_activations =
2387 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2388 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2389 for (sum, value) in reduced.iter_mut().zip(&partial) {
2390 *sum += *value;
2391 }
2392 rank_partials.push(partial);
2393 }
2394 Ok(RowParallelResult {
2395 reduced,
2396 rank_partials,
2397 })
2398 }
2399
2400 pub fn upload_bf16_column_parallel(
2401 &self,
2402 matrix: Bf16Matrix<'_>,
2403 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2404 self.upload_bf16_column_parallel_inner(matrix, None, false)
2405 }
2406
2407 pub fn upload_step_bf16_column_parallel(
2409 &self,
2410 matrix: Bf16Matrix<'_>,
2411 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2412 self.upload_step_bf16_column_parallel_inner(matrix, false)
2413 }
2414
2415 pub fn upload_step_bf16_column_parallel_f32_mirror(
2420 &self,
2421 matrix: Bf16Matrix<'_>,
2422 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2423 self.upload_step_bf16_column_parallel_inner(matrix, true)
2424 }
2425
2426 fn upload_step_bf16_column_parallel_inner(
2427 &self,
2428 matrix: Bf16Matrix<'_>,
2429 f32_mirror: bool,
2430 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2431 let canonical_chunk_rows =
2432 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2433 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2434 }
2435
2436 fn upload_bf16_column_parallel_inner(
2437 &self,
2438 matrix: Bf16Matrix<'_>,
2439 canonical_chunk_rows: Option<usize>,
2440 f32_mirror: bool,
2441 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2442 matrix.validate()?;
2443 let tp = self.ranks.len();
2444 if matrix.out_features % tp != 0 {
2445 return Err(format!(
2446 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2447 matrix.out_features
2448 )
2449 .into());
2450 }
2451 let mut ranks = Vec::with_capacity(tp);
2452 for (rank, engine) in self.ranks.iter().enumerate() {
2453 ranks.push(upload_bf16_rank(
2454 engine,
2455 bf16_column_shard(matrix, tp, rank)?,
2456 f32_mirror,
2457 )?);
2458 }
2459 Ok(ResidentBf16ColumnParallel {
2460 ranks,
2461 out_features: matrix.out_features,
2462 in_features: matrix.in_features,
2463 canonical_chunk_rows,
2464 })
2465 }
2466
2467 pub fn bf16_column_parallel_resident(
2468 &self,
2469 matrix: &ResidentBf16ColumnParallel,
2470 activations: &[f32],
2471 tokens: usize,
2472 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2473 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2474 validate_activations(activations, tokens, matrix.in_features)?;
2475 let local_out = matrix.out_features / self.ranks.len();
2476 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2477 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2478 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2479 let output = run_resident_bf16_rank(
2480 engine,
2481 shard,
2482 activations,
2483 tokens,
2484 matrix.canonical_chunk_rows,
2485 )?;
2486 for token in 0..tokens {
2487 let src = &output[token * local_out..(token + 1) * local_out];
2488 let dst_start = token * matrix.out_features + rank * local_out;
2489 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2490 }
2491 rank_outputs.push(output);
2492 }
2493 Ok(ColumnParallelResult {
2494 gathered,
2495 rank_outputs,
2496 })
2497 }
2498
2499 pub fn bf16_column_parallel_resident_native(
2506 &self,
2507 matrix: &ResidentBf16ColumnParallel,
2508 activations: &[f32],
2509 tokens: usize,
2510 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2511 let rank_outputs =
2512 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2513 let local_out = matrix.out_features / self.ranks.len();
2514 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2515 }
2516
2517 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2522 self.ranks
2523 .first()
2524 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2525 }
2526
2527 pub fn bf16_column_parallel_resident_native_device(
2540 &self,
2541 matrix: &ResidentBf16ColumnParallel,
2542 root_activation: &CudaSlice<f32>,
2543 tokens: usize,
2544 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2545 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2546 matrix,
2547 root_activation,
2548 tokens,
2549 )?;
2550 let local_out = matrix.out_features / self.ranks.len();
2551 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2552 let root = &self.ranks[0];
2553 let _main = root.gpu.enter_main()?;
2554 root.stream().synchronize()?;
2555 Ok(gathered)
2556 }
2557
2558 pub fn bf16_column_parallel_resident_device_shards_from_root(
2563 &self,
2564 matrix: &ResidentBf16ColumnParallel,
2565 root_activation: &CudaSlice<f32>,
2566 tokens: usize,
2567 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2568 if self.ranks.len() > 1 && !self.native_p2p {
2569 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2570 }
2571 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2572 let values = tokens
2573 .checked_mul(matrix.in_features)
2574 .ok_or("device BF16 column activation size overflow")?;
2575 let root = &self.ranks[0];
2576 if tokens == 0
2577 || root_activation.len() < values
2578 || root_activation.ordinal() != root.ctx().ordinal()
2579 {
2580 return Err("device BF16 column root activation geometry mismatch".into());
2581 }
2582
2583 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2584 let root_input = {
2585 let _main = root.gpu.enter_main()?;
2586 let mut root_input = root.uninit(values)?;
2587 root.stream()
2588 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2589 root_input
2590 };
2591 {
2595 let _main = root.gpu.enter_main()?;
2596 root.stream().synchronize()?;
2597 }
2598 rank_inputs.push(root_input);
2599 for engine in &self.ranks[1..] {
2600 let peer_input = {
2601 let _main = engine.gpu.enter_main()?;
2602 let mut peer_input = engine.uninit(values)?;
2603 engine
2604 .stream()
2605 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2606 peer_input
2607 };
2608 rank_inputs.push(peer_input);
2609 }
2610
2611 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2612 for rank in 0..self.ranks.len() {
2613 rank_outputs.push(run_resident_bf16_rank_device(
2614 &self.ranks[rank],
2615 &matrix.ranks[rank],
2616 &rank_inputs[rank],
2617 tokens,
2618 matrix.canonical_chunk_rows,
2619 self.bulk_p2p,
2620 )?);
2621 }
2622 Ok(rank_outputs)
2623 }
2624
2625 pub fn bf16_column_parallel_resident_device_shards(
2632 &self,
2633 matrix: &ResidentBf16ColumnParallel,
2634 activations: &[f32],
2635 tokens: usize,
2636 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2637 if self.ranks.len() > 1 && !self.native_p2p {
2638 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2639 }
2640 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2641 validate_activations(activations, tokens, matrix.in_features)?;
2642
2643 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2644 let root_input = {
2645 let root = &self.ranks[0];
2646 let _main = root.gpu.enter_main()?;
2647 root.htod(activations)?
2648 };
2649 {
2655 let root = &self.ranks[0];
2656 let _main = root.gpu.enter_main()?;
2657 root.stream().synchronize()?;
2658 }
2659 rank_inputs.push(root_input);
2660 for engine in &self.ranks[1..] {
2661 let peer_input = {
2662 let _main = engine.gpu.enter_main()?;
2663 let mut peer_input = engine.uninit(activations.len())?;
2664 engine
2665 .stream()
2666 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2667 peer_input
2668 };
2669 rank_inputs.push(peer_input);
2670 }
2671
2672 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2673 for rank in 0..self.ranks.len() {
2674 rank_outputs.push(run_resident_bf16_rank_device(
2675 &self.ranks[rank],
2676 &matrix.ranks[rank],
2677 &rank_inputs[rank],
2678 tokens,
2679 matrix.canonical_chunk_rows,
2680 self.bulk_p2p,
2681 )?);
2682 }
2683 Ok(rank_outputs)
2684 }
2685
2686 pub fn allocate_replicated_device_rows(
2690 &self,
2691 tokens: usize,
2692 width: usize,
2693 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2694 if self.ranks.len() > 1 && !self.native_p2p {
2695 return Err("replicated device rows require native P2P ranks".into());
2696 }
2697 let values = tokens
2698 .checked_mul(width)
2699 .ok_or("replicated device row size overflow")?;
2700 let rank_lengths = vec![values; self.ranks.len()];
2701 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2702 let mut ranks = Vec::with_capacity(self.ranks.len());
2703 for engine in &self.ranks {
2704 let _main = engine.gpu.enter_main()?;
2705 ranks.push(engine.uninit(values)?);
2706 }
2707 Ok(ResidentReplicatedDeviceRows {
2708 ranks,
2709 tokens,
2710 width,
2711 })
2712 }
2713
2714 pub fn refresh_replicated_device_rows_from_root(
2716 &self,
2717 rows: &mut ResidentReplicatedDeviceRows,
2718 source: &CudaSlice<f32>,
2719 ) -> Result<(), Box<dyn std::error::Error>> {
2720 if self.ranks.len() > 1 && !self.native_p2p {
2721 return Err("replicated device rows require native P2P ranks".into());
2722 }
2723 validate_replicated_device_rows(&self.ranks, rows)?;
2724 let root = self
2725 .ranks
2726 .first()
2727 .ok_or("replicated rows have no root rank")?;
2728 let values = replicated_device_row_source_values(
2729 rows.tokens,
2730 rows.width,
2731 source.len(),
2732 source.ordinal(),
2733 root.ctx().ordinal(),
2734 )?;
2735 let (root_rows, peer_rows) = rows
2736 .ranks
2737 .split_first_mut()
2738 .ok_or("replicated rows have no root allocation")?;
2739 {
2740 let _main = root.gpu.enter_main()?;
2741 let mut destination = root_rows.slice_mut(0..values);
2742 root.stream()
2743 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2744 root.stream().synchronize()?;
2745 }
2746 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2747 let _main = engine.gpu.enter_main()?;
2748 let mut destination = peer_rows.slice_mut(0..values);
2749 engine
2750 .stream()
2751 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2752 }
2753 Ok(())
2754 }
2755
2756 pub fn upload_replicated_device_rows(
2758 &self,
2759 rows: &[f32],
2760 tokens: usize,
2761 width: usize,
2762 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2763 if self.ranks.len() > 1 && !self.native_p2p {
2764 return Err("replicated device rows require native P2P ranks".into());
2765 }
2766 validate_activations(rows, tokens, width)?;
2767 let root = self
2768 .ranks
2769 .first()
2770 .ok_or("replicated rows have no root rank")?;
2771 let root_rows = {
2772 let _main = root.gpu.enter_main()?;
2773 root.htod(rows)?
2774 };
2775 {
2776 let _main = root.gpu.enter_main()?;
2777 root.stream().synchronize()?;
2778 }
2779 let mut ranks = Vec::with_capacity(self.ranks.len());
2780 ranks.push(root_rows);
2781 for engine in self.ranks.iter().skip(1) {
2782 let _main = engine.gpu.enter_main()?;
2783 let mut peer_rows = engine.uninit(rows.len())?;
2784 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2785 ranks.push(peer_rows);
2786 }
2787 Ok(ResidentReplicatedDeviceRows {
2788 ranks,
2789 tokens,
2790 width,
2791 })
2792 }
2793
2794 pub fn bf16_column_parallel_resident_replicated_device_shards(
2796 &self,
2797 matrix: &ResidentBf16ColumnParallel,
2798 activations: &ResidentReplicatedDeviceRows,
2799 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2800 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2801 validate_replicated_device_rows(&self.ranks, activations)?;
2802 if activations.width != matrix.in_features {
2803 return Err(format!(
2804 "replicated BF16 column input width {} != matrix width {}",
2805 activations.width, matrix.in_features
2806 )
2807 .into());
2808 }
2809 let mut outputs = Vec::with_capacity(self.ranks.len());
2810 for rank in 0..self.ranks.len() {
2811 outputs.push(run_resident_bf16_rank_device(
2812 &self.ranks[rank],
2813 &matrix.ranks[rank],
2814 &activations.ranks[rank],
2815 activations.tokens,
2816 matrix.canonical_chunk_rows,
2817 self.bulk_p2p,
2818 )?);
2819 }
2820 Ok(outputs)
2821 }
2822
2823 #[allow(clippy::too_many_arguments)]
2825 pub fn upload_sigmoid_topk_router(
2826 &self,
2827 weight: Bf16Matrix<'_>,
2828 correction_bias: &[f32],
2829 active: Option<&[bool]>,
2830 experts_per_token: usize,
2831 scaling_factor: f32,
2832 route_norm: bool,
2833 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2834 weight.validate()?;
2835 if correction_bias.len() != weight.out_features
2836 || experts_per_token == 0
2837 || experts_per_token > weight.out_features
2838 || !correction_bias.iter().all(|value| value.is_finite())
2839 || !scaling_factor.is_finite()
2840 || scaling_factor <= 0.0
2841 {
2842 return Err(format!(
2843 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2844 weight.out_features,
2845 weight.in_features,
2846 correction_bias.len(),
2847 experts_per_token,
2848 )
2849 .into());
2850 }
2851 let active_row = active
2852 .map(|mask| {
2853 if mask.len() != weight.out_features {
2854 return Err(format!(
2855 "sigmoid router active mask {} != experts {}",
2856 mask.len(),
2857 weight.out_features
2858 ));
2859 }
2860 Ok(mask
2861 .iter()
2862 .map(|&enabled| u8::from(enabled))
2863 .collect::<Vec<_>>())
2864 })
2865 .transpose()?
2866 .unwrap_or_else(|| vec![1; weight.out_features]);
2867 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2868 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2869
2870 let root = self
2871 .ranks
2872 .first()
2873 .ok_or("sigmoid router runtime has no root rank")?;
2874 let _main = root.gpu.enter_main()?;
2875 let bf16 = root.htod_bytes(weight.bytes)?;
2876 let weight_f32 = root.bf16_to_f32(
2877 &bf16.slice(0..bf16.len()),
2878 weight.out_features * weight.in_features,
2879 )?;
2880 Ok(ResidentSigmoidTopKRouter {
2881 weight: weight_f32,
2882 correction_bias: root.htod(correction_bias)?,
2883 active: root.htod_bytes(&active_row)?,
2884 root_device: root.ctx().ordinal(),
2885 input_width: weight.in_features,
2886 expert_count: weight.out_features,
2887 experts_per_token,
2888 active_count,
2889 scaling_factor,
2890 route_norm,
2891 })
2892 }
2893
2894 pub fn sigmoid_topk_replicated_device_rows_host(
2899 &self,
2900 router: &ResidentSigmoidTopKRouter,
2901 input: &ResidentReplicatedDeviceRows,
2902 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2903 validate_replicated_device_rows(&self.ranks, input)?;
2904 if input.width != router.input_width {
2905 return Err(format!(
2906 "sigmoid router input width {} != resident width {}",
2907 input.width, router.input_width
2908 )
2909 .into());
2910 }
2911 let root = self
2912 .ranks
2913 .first()
2914 .ok_or("sigmoid router runtime has no root rank")?;
2915 let _main = root.gpu.enter_main()?;
2916 if root.ctx().ordinal() != router.root_device
2917 || router.weight.ordinal() != router.root_device
2918 || router.correction_bias.ordinal() != router.root_device
2919 || router.active.ordinal() != router.root_device
2920 {
2921 return Err("sigmoid router root residency changed".into());
2922 }
2923 let logits = root.router_gemv(
2924 &router.weight,
2925 &input.ranks[0],
2926 router.input_width,
2927 router.expert_count,
2928 input.tokens,
2929 )?;
2930 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2931 &logits,
2932 input.tokens,
2933 router.expert_count,
2934 router.experts_per_token,
2935 router.active_count,
2936 &router.correction_bias,
2937 &router.active,
2938 router.scaling_factor,
2939 router.route_norm,
2940 )?;
2941 Ok(SigmoidTopKHostOutput {
2942 logits: root.dtoh(&logits)?,
2943 selected,
2944 weights,
2945 })
2946 }
2947
2948 pub fn upload_replicated_bf16_swiglu(
2950 &self,
2951 gate: Bf16Matrix<'_>,
2952 up: Bf16Matrix<'_>,
2953 down: Bf16Matrix<'_>,
2954 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2955 gate.validate()?;
2956 up.validate()?;
2957 down.validate()?;
2958 if gate.in_features != up.in_features
2959 || gate.out_features != up.out_features
2960 || down.in_features != gate.out_features
2961 || down.out_features != gate.in_features
2962 {
2963 return Err(format!(
2964 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2965 gate.out_features,
2966 gate.in_features,
2967 up.out_features,
2968 up.in_features,
2969 down.out_features,
2970 down.in_features,
2971 )
2972 .into());
2973 }
2974 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2975 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2976 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2977 for engine in &self.ranks {
2978 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2979 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2980 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2981 }
2982 Ok(ResidentReplicatedBf16SwiGlu {
2983 gate: gate_ranks,
2984 up: up_ranks,
2985 down: down_ranks,
2986 input_width: gate.in_features,
2987 intermediate_width: gate.out_features,
2988 })
2989 }
2990
2991 pub fn replicated_bf16_swiglu_resident_device(
2993 &self,
2994 mlp: &ResidentReplicatedBf16SwiGlu,
2995 input: &ResidentReplicatedDeviceRows,
2996 activation_limit: Option<f32>,
2997 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2998 validate_step_expert_activation_limit(activation_limit)?;
2999 validate_replicated_device_rows(&self.ranks, input)?;
3000 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
3001 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
3002 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
3003 if input.width != mlp.input_width
3004 || mlp.gate.len() != self.ranks.len()
3005 || mlp.up.len() != self.ranks.len()
3006 || mlp.down.len() != self.ranks.len()
3007 {
3008 return Err("replicated BF16 SwiGLU residency or input width changed".into());
3009 }
3010
3011 let mut outputs = Vec::with_capacity(self.ranks.len());
3012 for rank in 0..self.ranks.len() {
3013 let engine = &self.ranks[rank];
3014 let gate = run_resident_bf16_rank_device(
3015 engine,
3016 &mlp.gate[rank],
3017 &input.ranks[rank],
3018 input.tokens,
3019 None,
3020 self.bulk_p2p,
3021 )?;
3022 let up = run_resident_bf16_rank_device(
3023 engine,
3024 &mlp.up[rank],
3025 &input.ranks[rank],
3026 input.tokens,
3027 None,
3028 self.bulk_p2p,
3029 )?;
3030 let _main = engine.gpu.enter_main()?;
3031 let values = input
3032 .tokens
3033 .checked_mul(mlp.intermediate_width)
3034 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
3035 let mut activation = engine.uninit(values)?;
3036 if let Some(limit) = activation_limit {
3037 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
3038 } else {
3039 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
3040 }
3041 outputs.push(run_resident_bf16_rank_device(
3042 engine,
3043 &mlp.down[rank],
3044 &activation,
3045 input.tokens,
3046 None,
3047 self.bulk_p2p,
3048 )?);
3049 }
3050 Ok(ResidentReplicatedDeviceRows {
3051 ranks: outputs,
3052 tokens: input.tokens,
3053 width: mlp.input_width,
3054 })
3055 }
3056
3057 pub fn rms_norm_replicated_device_rows(
3059 &self,
3060 input: &ResidentReplicatedDeviceRows,
3061 weight: &[f32],
3062 eps: f32,
3063 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3064 validate_replicated_device_rows(&self.ranks, input)?;
3065 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
3066 return Err(format!(
3067 "replicated RMS norm weight/eps {}/{} != width {}",
3068 weight.len(),
3069 eps,
3070 input.width
3071 )
3072 .into());
3073 }
3074 let mut ranks = Vec::with_capacity(self.ranks.len());
3075 for (rank, engine) in self.ranks.iter().enumerate() {
3076 let _main = engine.gpu.enter_main()?;
3077 let weight = engine.htod(weight)?;
3078 let mut output = engine.uninit(input.tokens * input.width)?;
3079 engine.rms_norm(
3080 &input.ranks[rank],
3081 &weight,
3082 &mut output,
3083 input.width,
3084 input.tokens,
3085 eps,
3086 )?;
3087 ranks.push(output);
3088 }
3089 Ok(ResidentReplicatedDeviceRows {
3090 ranks,
3091 tokens: input.tokens,
3092 width: input.width,
3093 })
3094 }
3095
3096 pub fn add_rms_norm_replicated_device_rows(
3098 &self,
3099 input: &ResidentReplicatedDeviceRows,
3100 update: &ResidentReplicatedDeviceRows,
3101 weight: &[f32],
3102 eps: f32,
3103 ) -> Result<
3104 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
3105 Box<dyn std::error::Error>,
3106 > {
3107 validate_replicated_device_rows(&self.ranks, input)?;
3108 validate_replicated_device_rows(&self.ranks, update)?;
3109 if input.tokens != update.tokens
3110 || input.width != update.width
3111 || weight.len() != input.width
3112 || !eps.is_finite()
3113 || eps <= 0.0
3114 {
3115 return Err(format!(
3116 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3117 input.tokens,
3118 input.width,
3119 update.tokens,
3120 update.width,
3121 weight.len(),
3122 )
3123 .into());
3124 }
3125 let values = input.tokens * input.width;
3126 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3127 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3128 for (rank, engine) in self.ranks.iter().enumerate() {
3129 let _main = engine.gpu.enter_main()?;
3130 let weight = engine.htod(weight)?;
3131 let mut residual = engine.uninit(values)?;
3132 let mut normalized = engine.uninit(values)?;
3133 engine.add_rms_norm(
3134 &input.ranks[rank],
3135 &update.ranks[rank],
3136 &weight,
3137 &mut residual,
3138 &mut normalized,
3139 input.width,
3140 input.tokens,
3141 eps,
3142 )?;
3143 residual_ranks.push(residual);
3144 normalized_ranks.push(normalized);
3145 }
3146 Ok((
3147 ResidentReplicatedDeviceRows {
3148 ranks: residual_ranks,
3149 tokens: input.tokens,
3150 width: input.width,
3151 },
3152 ResidentReplicatedDeviceRows {
3153 ranks: normalized_ranks,
3154 tokens: input.tokens,
3155 width: input.width,
3156 },
3157 ))
3158 }
3159
3160 pub fn collect_replicated_device_rows(
3161 &self,
3162 rows: &ResidentReplicatedDeviceRows,
3163 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3164 validate_replicated_device_rows(&self.ranks, rows)?;
3165 let mut outputs = Vec::with_capacity(self.ranks.len());
3166 for (rank, engine) in self.ranks.iter().enumerate() {
3167 let _main = engine.gpu.enter_main()?;
3168 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3169 }
3170 Ok(outputs)
3171 }
3172
3173 pub fn upload_bf16_row_parallel(
3174 &self,
3175 matrix: Bf16Matrix<'_>,
3176 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3177 matrix.validate()?;
3178 let tp = self.ranks.len();
3179 if matrix.in_features % tp != 0 {
3180 return Err(format!(
3181 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3182 matrix.in_features
3183 )
3184 .into());
3185 }
3186 let mut ranks = Vec::with_capacity(tp);
3187 for (rank, engine) in self.ranks.iter().enumerate() {
3188 let shard = bf16_row_shard(matrix, tp, rank)?;
3189 ranks.push(upload_bf16_rank(
3190 engine,
3191 Bf16Matrix {
3192 bytes: &shard,
3193 out_features: matrix.out_features,
3194 in_features: matrix.in_features / tp,
3195 },
3196 false,
3197 )?);
3198 }
3199 Ok(ResidentBf16RowParallel {
3200 ranks,
3201 out_features: matrix.out_features,
3202 in_features: matrix.in_features,
3203 })
3204 }
3205
3206 pub fn bf16_row_parallel_resident(
3207 &self,
3208 matrix: &ResidentBf16RowParallel,
3209 activations: &[f32],
3210 tokens: usize,
3211 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3212 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3213 validate_activations(activations, tokens, matrix.in_features)?;
3214 let tp = self.ranks.len();
3215 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3216 let mut rank_partials = Vec::with_capacity(tp);
3217 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3218 let local_activations =
3219 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3220 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3221 for (sum, value) in reduced.iter_mut().zip(&partial) {
3222 *sum += value;
3223 }
3224 rank_partials.push(partial);
3225 }
3226 Ok(RowParallelResult {
3227 reduced,
3228 rank_partials,
3229 })
3230 }
3231
3232 pub fn upload_step_bf16_row_parallel(
3234 &self,
3235 matrix: Bf16Matrix<'_>,
3236 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3237 self.upload_step_bf16_row_parallel_inner(matrix, false)
3238 }
3239
3240 pub fn upload_step_bf16_row_parallel_f32_mirror(
3241 &self,
3242 matrix: Bf16Matrix<'_>,
3243 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3244 self.upload_step_bf16_row_parallel_inner(matrix, true)
3245 }
3246
3247 fn upload_step_bf16_row_parallel_inner(
3248 &self,
3249 matrix: Bf16Matrix<'_>,
3250 f32_mirror: bool,
3251 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3252 matrix.validate()?;
3253 let tp = self.ranks.len();
3254 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3255 let local_in = matrix.in_features / tp;
3256 let blocks_per_rank = local_in / canonical_chunk_cols;
3257 let mut ranks = Vec::with_capacity(tp);
3258 for (rank, engine) in self.ranks.iter().enumerate() {
3259 let mut blocks = Vec::with_capacity(blocks_per_rank);
3260 for block in 0..blocks_per_rank {
3261 let global_block = rank * blocks_per_rank + block;
3262 let col_start = global_block * canonical_chunk_cols;
3263 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3264 blocks.push(upload_bf16_rank(
3265 engine,
3266 Bf16Matrix {
3267 bytes: &bytes,
3268 out_features: matrix.out_features,
3269 in_features: canonical_chunk_cols,
3270 },
3271 f32_mirror,
3272 )?);
3273 }
3274 ranks.push(blocks);
3275 }
3276 Ok(ResidentStepBf16RowParallel {
3277 ranks,
3278 out_features: matrix.out_features,
3279 in_features: matrix.in_features,
3280 canonical_chunk_cols,
3281 })
3282 }
3283
3284 pub fn step_bf16_row_parallel_resident(
3289 &self,
3290 matrix: &ResidentStepBf16RowParallel,
3291 activations: &[f32],
3292 tokens: usize,
3293 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3294 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3295 validate_activations(activations, tokens, matrix.in_features)?;
3296 let root = &self.ranks[0];
3297 let output_len = tokens
3298 .checked_mul(matrix.out_features)
3299 .ok_or("Step BF16 row output size overflow")?;
3300 let mut reduced = {
3301 let _main = root.gpu.enter_main()?;
3302 root.htod(&vec![0.0f32; output_len])?
3303 };
3304 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3305 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3306 for (block, resident) in blocks.iter().enumerate() {
3307 let global_block = rank * blocks_per_rank + block;
3308 let input = activation_shard(
3309 activations,
3310 tokens,
3311 matrix.in_features,
3312 PRODUCT_MAX_CARDS,
3313 global_block,
3314 );
3315 let partial =
3316 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3317 let next = {
3318 let _main = root.gpu.enter_main()?;
3319 let partial = root.htod(&partial)?;
3320 let mut next = root.uninit(output_len)?;
3321 root.add(&reduced, &partial, &mut next, output_len)?;
3322 next
3323 };
3324 reduced = next;
3325 }
3326 }
3327 let _main = root.gpu.enter_main()?;
3328 root.dtoh(&reduced)
3329 }
3330
3331 pub fn step_bf16_row_parallel_resident_native(
3337 &self,
3338 matrix: &ResidentStepBf16RowParallel,
3339 activations: &[f32],
3340 tokens: usize,
3341 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3342 if self.ranks.len() > 1 && !self.native_p2p {
3343 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3344 }
3345 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3346 validate_activations(activations, tokens, matrix.in_features)?;
3347 let root = &self.ranks[0];
3348 let root_input = {
3349 let _main = root.gpu.enter_main()?;
3350 root.htod(activations)?
3351 };
3352 {
3355 let _main = root.gpu.enter_main()?;
3356 root.stream().synchronize()?;
3357 }
3358 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3359 let _main = root.gpu.enter_main()?;
3360 root.dtoh(&reduced)
3361 }
3362
3363 pub fn step_bf16_row_parallel_resident_native_device(
3371 &self,
3372 matrix: &ResidentStepBf16RowParallel,
3373 root_activation: &CudaSlice<f32>,
3374 tokens: usize,
3375 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3376 if self.ranks.len() > 1 && !self.native_p2p {
3377 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3378 }
3379 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3380 let values = tokens
3381 .checked_mul(matrix.in_features)
3382 .ok_or("device Step BF16 row activation size overflow")?;
3383 let root = &self.ranks[0];
3384 if tokens == 0
3385 || root_activation.len() < values
3386 || root_activation.ordinal() != root.ctx().ordinal()
3387 {
3388 return Err("device Step BF16 row root activation geometry mismatch".into());
3389 }
3390 let root_input = {
3391 let _main = root.gpu.enter_main()?;
3392 let mut root_input = root.uninit(values)?;
3393 root.stream()
3394 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3395 root.stream().synchronize()?; root_input
3397 };
3398 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3399 let _main = root.gpu.enter_main()?;
3400 root.stream().synchronize()?;
3401 Ok(reduced)
3402 }
3403
3404 fn step_bf16_row_native_reduce_from_root(
3409 &self,
3410 matrix: &ResidentStepBf16RowParallel,
3411 root_input: &CudaSlice<f32>,
3412 tokens: usize,
3413 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3414 let root = &self.ranks[0];
3415 let output_len = tokens
3416 .checked_mul(matrix.out_features)
3417 .ok_or("native Step BF16 row output size overflow")?;
3418 let mut reduced = {
3419 let _main = root.gpu.enter_main()?;
3420 root.htod(&vec![0.0f32; output_len])?
3421 };
3422 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3423 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3424 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3425 let mut remote_partial_keepalive = Vec::new();
3426 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3427 for (block, resident) in blocks.iter().enumerate() {
3428 let global_block = rank * blocks_per_rank + block;
3429 let col_start = global_block * matrix.canonical_chunk_cols;
3430 let block_len = tokens
3431 .checked_mul(matrix.canonical_chunk_cols)
3432 .ok_or("native Step BF16 row block size overflow")?;
3433 let block_input = if self.bulk_p2p {
3434 let root_packed = {
3435 let _main = root.gpu.enter_main()?;
3436 let mut root_packed = root.uninit(block_len)?;
3437 root.copy_rows_strided(
3438 &root_input,
3439 &mut root_packed,
3440 matrix.canonical_chunk_cols,
3441 tokens,
3442 matrix.in_features,
3443 col_start,
3444 )?;
3445 root_packed
3446 };
3447 if rank == 0 {
3448 root_packed
3449 } else {
3450 {
3453 let _main = root.gpu.enter_main()?;
3454 root.stream().synchronize()?;
3455 }
3456 let engine = &self.ranks[rank];
3457 let _main = engine.gpu.enter_main()?;
3458 let mut block_input = engine.uninit(block_len)?;
3459 engine
3460 .stream()
3461 .memcpy_dtod(&root_packed, &mut block_input)?;
3462 root_packed_keepalive.push(root_packed);
3463 block_input
3464 }
3465 } else {
3466 let engine = &self.ranks[rank];
3467 let _main = engine.gpu.enter_main()?;
3468 let mut block_input = engine.uninit(block_len)?;
3469 for token in 0..tokens {
3470 let source_start = token * matrix.in_features + col_start;
3471 let source = root_input
3472 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3473 let destination_start = token * matrix.canonical_chunk_cols;
3474 let mut destination = block_input.slice_mut(
3475 destination_start..destination_start + matrix.canonical_chunk_cols,
3476 );
3477 engine.stream().memcpy_dtod(&source, &mut destination)?;
3478 }
3479 block_input
3480 };
3481 let partial = run_resident_bf16_rank_device(
3482 &self.ranks[rank],
3483 resident,
3484 &block_input,
3485 tokens,
3486 None,
3487 self.bulk_p2p,
3488 )?;
3489 block_input_keepalive.push(block_input);
3490 let root_partial = if rank == 0 {
3491 partial
3492 } else {
3493 {
3496 let engine = &self.ranks[rank];
3497 let _main = engine.gpu.enter_main()?;
3498 engine.stream().synchronize()?;
3499 }
3500 let _main = root.gpu.enter_main()?;
3501 let mut peer_partial = root.uninit(output_len)?;
3502 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3503 remote_partial_keepalive.push(partial);
3504 peer_partial
3505 };
3506 let next = {
3507 let _main = root.gpu.enter_main()?;
3508 let mut next = root.uninit(output_len)?;
3509 root.add(&reduced, &root_partial, &mut next, output_len)?;
3510 next
3511 };
3512 reduced = next;
3513 }
3514 }
3515 {
3516 let _main = root.gpu.enter_main()?;
3517 root.stream().synchronize()?;
3518 }
3519 drop(remote_partial_keepalive);
3520 drop(root_packed_keepalive);
3521 drop(block_input_keepalive);
3522 Ok(reduced)
3523 }
3524
3525 pub fn step_bf16_row_parallel_resident_root_device(
3528 &self,
3529 matrix: &ResidentStepBf16RowParallel,
3530 rank_activations: &[CudaSlice<f32>],
3531 tokens: usize,
3532 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3533 if self.ranks.len() > 1 && !self.native_p2p {
3534 return Err(
3535 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3536 );
3537 }
3538 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3539 let local_width = matrix.in_features / self.ranks.len();
3540 let shard_len = tokens
3541 .checked_mul(local_width)
3542 .ok_or("device Step BF16 row shard size overflow")?;
3543 if tokens == 0
3544 || rank_activations.len() != self.ranks.len()
3545 || rank_activations
3546 .iter()
3547 .zip(&self.ranks)
3548 .any(|(rows, engine)| {
3549 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3550 })
3551 {
3552 return Err("device Step BF16 row activation shard geometry changed".into());
3553 }
3554
3555 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3556 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3557 let mut partials = Vec::with_capacity(self.ranks.len());
3558 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3559 if blocks.len() != blocks_per_rank {
3560 return Err(format!(
3561 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3562 blocks.len()
3563 )
3564 .into());
3565 }
3566 let engine = &self.ranks[rank];
3567 let _main = engine.gpu.enter_main()?;
3568 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3569 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3570 for (block, resident) in blocks.iter().enumerate() {
3571 let block_len = tokens
3572 .checked_mul(matrix.canonical_chunk_cols)
3573 .ok_or("device Step BF16 row block size overflow")?;
3574 let mut block_input = engine.uninit(block_len)?;
3575 let local_col_start = block * matrix.canonical_chunk_cols;
3576 if self.bulk_p2p {
3577 engine.copy_rows_strided(
3578 &rank_activations[rank],
3579 &mut block_input,
3580 matrix.canonical_chunk_cols,
3581 tokens,
3582 local_width,
3583 local_col_start,
3584 )?;
3585 } else {
3586 for token in 0..tokens {
3587 let source_start = token * local_width + local_col_start;
3588 let source = rank_activations[rank]
3589 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3590 let destination_start = token * matrix.canonical_chunk_cols;
3591 let mut destination = block_input.slice_mut(
3592 destination_start..destination_start + matrix.canonical_chunk_cols,
3593 );
3594 engine.stream().memcpy_dtod(&source, &mut destination)?;
3595 }
3596 }
3597 let partial = run_resident_bf16_rank_device(
3598 engine,
3599 resident,
3600 &block_input,
3601 tokens,
3602 None,
3603 self.bulk_p2p,
3604 )?;
3605 rank_inputs.push(block_input);
3606 rank_partials.push(partial);
3607 }
3608 block_inputs.push(rank_inputs);
3609 partials.push(rank_partials);
3610 }
3611 for engine in self.ranks.iter().skip(1) {
3612 let _main = engine.gpu.enter_main()?;
3613 engine.stream().synchronize()?;
3614 }
3615
3616 let output_len = tokens
3617 .checked_mul(matrix.out_features)
3618 .ok_or("device Step BF16 row output size overflow")?;
3619 let root = &self.ranks[0];
3620 let _main = root.gpu.enter_main()?;
3621 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3622 let mut remote_partials = Vec::new();
3623 for (rank, rank_partials) in partials.into_iter().enumerate() {
3624 for partial in rank_partials {
3625 let root_partial = if rank == 0 {
3626 partial
3627 } else {
3628 let mut peer_partial = root.uninit(output_len)?;
3629 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3630 remote_partials.push(partial);
3631 peer_partial
3632 };
3633 let mut next = root.uninit(output_len)?;
3634 root.add(&reduced, &root_partial, &mut next, output_len)?;
3635 reduced = next;
3636 }
3637 }
3638 root.stream().synchronize()?;
3639 drop(remote_partials);
3640 drop(block_inputs);
3641 Ok(reduced)
3642 }
3643
3644 pub fn step_bf16_row_parallel_resident_replicated_device(
3646 &self,
3647 matrix: &ResidentStepBf16RowParallel,
3648 rank_activations: &[CudaSlice<f32>],
3649 tokens: usize,
3650 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3651 let reduced =
3652 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3653 let output_len = tokens
3654 .checked_mul(matrix.out_features)
3655 .ok_or("device Step BF16 row output size overflow")?;
3656 let mut ranks = Vec::with_capacity(self.ranks.len());
3657 ranks.push(reduced);
3658 for engine in self.ranks.iter().skip(1) {
3659 let _main = engine.gpu.enter_main()?;
3660 let mut peer_output = engine.uninit(output_len)?;
3661 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3662 ranks.push(peer_output);
3663 }
3664 Ok(ResidentReplicatedDeviceRows {
3665 ranks,
3666 tokens,
3667 width: matrix.out_features,
3668 })
3669 }
3670
3671 pub fn upload_expert(
3672 &self,
3673 gate: E4m3BlockMatrix<'_>,
3674 up: E4m3BlockMatrix<'_>,
3675 down: E4m3BlockMatrix<'_>,
3676 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3677 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3678 return Err("TP expert gate/up dimensions differ".into());
3679 }
3680 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3681 return Err(format!(
3682 "TP expert down {}x{} does not invert gate/up {}x{}",
3683 down.out_features, down.in_features, gate.out_features, gate.in_features
3684 )
3685 .into());
3686 }
3687 Ok(ResidentTpExpert {
3688 gate: self.upload_column_parallel(gate)?,
3689 up: self.upload_column_parallel(up)?,
3690 down: self.upload_row_parallel(down)?,
3691 input_width: gate.in_features,
3692 expert_width: gate.out_features,
3693 })
3694 }
3695
3696 pub fn run_expert(
3697 &self,
3698 expert: &ResidentTpExpert,
3699 input: &[f32],
3700 tokens: usize,
3701 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3702 validate_activations(input, tokens, expert.input_width)?;
3703 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3704 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3705 let activated: Vec<f32> = gate
3706 .gathered
3707 .iter()
3708 .zip(&up.gathered)
3709 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3710 .collect();
3711 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3712 Ok(self
3713 .row_parallel_resident(&expert.down, &activated, tokens)?
3714 .reduced)
3715 }
3716
3717 pub fn upload_expert_parallel(
3718 &self,
3719 gate: E4m3ExpertBank<'_>,
3720 up: E4m3ExpertBank<'_>,
3721 down: E4m3ExpertBank<'_>,
3722 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3723 gate.validate()?;
3724 up.validate()?;
3725 down.validate()?;
3726 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3727 return Err("EP gate/up/down expert counts differ".into());
3728 }
3729 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3730 return Err("EP gate/up dimensions differ".into());
3731 }
3732 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3733 return Err(format!(
3734 "EP down {}x{} does not invert gate/up {}x{}",
3735 down.out_features, down.in_features, gate.out_features, gate.in_features
3736 )
3737 .into());
3738 }
3739 if gate.expert_count % self.ranks.len() != 0 {
3740 return Err(format!(
3741 "EP expert count {} is not divisible by {} ranks",
3742 gate.expert_count,
3743 self.ranks.len()
3744 )
3745 .into());
3746 }
3747
3748 let per_rank = gate.expert_count / self.ranks.len();
3749 let mut ranks = Vec::with_capacity(self.ranks.len());
3750 for (rank, engine) in self.ranks.iter().enumerate() {
3751 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3752 ranks.push(ResidentEpRank {
3753 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3754 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3755 down: upload_expert_bank_rank(engine, down, expert_range)?,
3756 });
3757 }
3758 Ok(ResidentExpertParallel {
3759 ranks,
3760 expert_count: gate.expert_count,
3761 input_width: gate.in_features,
3762 expert_width: gate.out_features,
3763 })
3764 }
3765
3766 #[allow(clippy::too_many_arguments)]
3772 pub fn prepare_step_grouped_fp8_gate(
3773 &self,
3774 gate: E4m3ExpertBank<'_>,
3775 up: E4m3ExpertBank<'_>,
3776 down: E4m3ExpertBank<'_>,
3777 input: &[f32],
3778 tokens: usize,
3779 selected: &[usize],
3780 activation_limit: Option<f32>,
3781 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3782 gate.validate()?;
3783 up.validate()?;
3784 down.validate()?;
3785 validate_step_expert_activation_limit(activation_limit)?;
3786 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3787 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3788 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3789 {
3790 return Err(format!(
3791 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3792 got gate/up/down={}/{}/{}",
3793 gate.expert_count, up.expert_count, down.expert_count,
3794 )
3795 .into());
3796 }
3797 if gate.in_features != up.in_features
3798 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3799 || up.out_features != STEP_GROUPED_FP8_WIDTH
3800 || down.in_features != STEP_GROUPED_FP8_WIDTH
3801 || down.out_features != gate.in_features
3802 {
3803 return Err(format!(
3804 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3805 gate.out_features,
3806 gate.in_features,
3807 up.out_features,
3808 up.in_features,
3809 down.out_features,
3810 down.in_features,
3811 )
3812 .into());
3813 }
3814 validate_activations(input, tokens, gate.in_features)?;
3815 let pairs = tokens
3816 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3817 .ok_or("official Step grouped FP8 route count overflow")?;
3818 if selected.len() != pairs {
3819 return Err(format!(
3820 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3821 ({pairs})",
3822 selected.len()
3823 )
3824 .into());
3825 }
3826 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3827 let mut unique = routes.to_vec();
3828 unique.sort_unstable();
3829 unique.dedup();
3830 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3831 return Err(format!(
3832 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3833 {routes:?}"
3834 )
3835 .into());
3836 }
3837 }
3838
3839 let engine = self
3840 .ranks
3841 .first()
3842 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3843 let _main = engine.gpu.enter_main()?;
3844 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3845 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3846 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3847 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3848 let input = engine.htod(input)?;
3849 let route_csr = ExpertCsr::from_token_routes(
3850 STEP_GROUPED_FP8_EXPERTS,
3851 tokens,
3852 STEP_GROUPED_FP8_TOP_K,
3853 selected,
3854 )?
3855 .upload(engine)?;
3856 let pair_rows = (0..pairs).collect::<Vec<_>>();
3857 let down_csr =
3858 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3859 .upload(engine)?;
3860 let gate_workspace =
3861 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3862 let up_workspace =
3863 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3864 let down_workspace =
3865 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3866 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3867 Ok(PreparedStepGroupedFp8Gate {
3868 device: engine.ctx().ordinal(),
3869 gate,
3870 up,
3871 down,
3872 input,
3873 route_csr,
3874 down_csr,
3875 gate_workspace,
3876 up_workspace,
3877 down_workspace,
3878 activation,
3879 activation_limit,
3880 tokens,
3881 pairs,
3882 })
3883 }
3884
3885 pub fn run_step_grouped_fp8_gate(
3887 &self,
3888 plan: &mut PreparedStepGroupedFp8Gate,
3889 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3890 let engine = self
3891 .ranks
3892 .first()
3893 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3894 if engine.ctx().ordinal() != plan.device {
3895 return Err(format!(
3896 "official Step grouped FP8 plan device {} != rank-zero device {}",
3897 plan.device,
3898 engine.ctx().ordinal()
3899 )
3900 .into());
3901 }
3902 let _main = engine.gpu.enter_main()?;
3903
3904 plan.gate_workspace.quantize(engine, &plan.input)?;
3905 plan.gate_workspace.project(
3906 engine,
3907 &plan.gate.codes,
3908 &plan.gate.scales,
3909 &plan.route_csr,
3910 plan.gate.code_stride,
3911 plan.gate.scale_stride,
3912 1.0,
3913 )?;
3914 plan.up_workspace.quantize(engine, &plan.input)?;
3915 plan.up_workspace.project(
3916 engine,
3917 &plan.up.codes,
3918 &plan.up.scales,
3919 &plan.route_csr,
3920 plan.up.code_stride,
3921 plan.up.scale_stride,
3922 1.0,
3923 )?;
3924 if let Some(limit) = plan.activation_limit {
3925 engine.silu_clamped_mul_host_expf(
3926 plan.gate_workspace.output(),
3927 plan.up_workspace.output(),
3928 limit,
3929 &mut plan.activation,
3930 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3931 )?;
3932 } else {
3933 engine.silu_mul_host_expf(
3934 plan.gate_workspace.output(),
3935 plan.up_workspace.output(),
3936 &mut plan.activation,
3937 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3938 )?;
3939 }
3940 plan.down_workspace.quantize(engine, &plan.activation)?;
3941 plan.down_workspace.project(
3942 engine,
3943 &plan.down.codes,
3944 &plan.down.scales,
3945 &plan.down_csr,
3946 plan.down.code_stride,
3947 plan.down.scale_stride,
3948 1.0,
3949 )?;
3950
3951 Ok(StepGroupedFp8ProjectionOutput {
3952 gate: engine.dtoh(plan.gate_workspace.output())?,
3953 up: engine.dtoh(plan.up_workspace.output())?,
3954 down: engine.dtoh(plan.down_workspace.output())?,
3955 })
3956 }
3957
3958 pub fn prepare_step_grouped_expert_parallel_gate(
3959 &self,
3960 experts: &ResidentExpertParallel,
3961 input: &[f32],
3962 tokens: usize,
3963 selected: &[usize],
3964 activation_limit: Option<f32>,
3965 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3966 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3967 experts,
3968 input,
3969 tokens,
3970 selected,
3971 activation_limit,
3972 tokens,
3973 )
3974 }
3975
3976 #[allow(clippy::too_many_arguments)]
3977 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3978 &self,
3979 experts: &ResidentExpertParallel,
3980 input: &[f32],
3981 tokens: usize,
3982 selected: &[usize],
3983 activation_limit: Option<f32>,
3984 max_tokens: usize,
3985 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3986 if !self.native_p2p || !self.ep_device_arithmetic {
3987 return Err(
3988 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3989 );
3990 }
3991 validate_step_expert_activation_limit(activation_limit)?;
3992 validate_ep_residency(&self.ranks, experts)?;
3993 validate_activations(input, tokens, experts.input_width)?;
3994 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3995 return Err(format!(
3996 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3997 )
3998 .into());
3999 }
4000 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
4001 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
4002 {
4003 return Err(format!(
4004 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
4005 STEP_GROUPED_FP8_EXPERTS,
4006 STEP_GROUPED_FP8_WIDTH,
4007 experts.expert_count,
4008 experts.expert_width,
4009 )
4010 .into());
4011 }
4012 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4013 let max_pairs = max_tokens
4014 .checked_mul(STEP_GROUPED_FP8_TOP_K)
4015 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
4016 let input_capacity = max_tokens
4017 .checked_mul(experts.input_width)
4018 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
4019
4020 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4021 for engine in &self.ranks {
4022 let _main = engine.gpu.enter_main()?;
4023 rank_inputs.push(engine.uninit(input_capacity)?);
4024 }
4025
4026 let mut owners = Vec::with_capacity(self.ranks.len());
4027 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
4028 if rank.gate.expert_range != rank.up.expert_range
4029 || rank.gate.expert_range != rank.down.expert_range
4030 {
4031 return Err(format!(
4032 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
4033 owner_rank
4034 )
4035 .into());
4036 }
4037 let local_experts = rank.gate.expert_range.len();
4038 let engine = &self.ranks[owner_rank];
4039 let _main = engine.gpu.enter_main()?;
4040 let route_csr =
4041 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
4042 let down_csr =
4043 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
4044 let gate_workspace = Fp8GroupedWorkspace::new(
4045 engine,
4046 experts.input_width,
4047 experts.expert_width,
4048 max_tokens,
4049 max_pairs,
4050 )?;
4051 let up_workspace = Fp8GroupedWorkspace::new(
4052 engine,
4053 experts.input_width,
4054 experts.expert_width,
4055 max_tokens,
4056 max_pairs,
4057 )?;
4058 let down_workspace = Fp8GroupedWorkspace::new(
4059 engine,
4060 experts.expert_width,
4061 experts.input_width,
4062 max_pairs,
4063 max_pairs,
4064 )?;
4065 let activation = engine.uninit(
4066 max_pairs
4067 .checked_mul(experts.expert_width)
4068 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
4069 )?;
4070 owners.push(PreparedStepGroupedExpertOwner {
4071 rank: owner_rank,
4072 global_pairs: Vec::new(),
4073 route_csr,
4074 down_csr,
4075 gate_workspace,
4076 up_workspace,
4077 down_workspace,
4078 activation,
4079 });
4080 }
4081
4082 let mut plan = PreparedStepGroupedExpertParallelGate {
4083 rank_inputs,
4084 owners,
4085 activation_limit,
4086 tokens: 0,
4087 pairs: 0,
4088 max_tokens,
4089 max_pairs,
4090 input_width: experts.input_width,
4091 expert_width: experts.expert_width,
4092 generation: 0,
4093 executed_generation: None,
4094 ready: false,
4095 };
4096 self.refresh_step_grouped_expert_parallel_gate(
4097 experts, &mut plan, input, tokens, selected,
4098 )?;
4099 Ok(plan)
4100 }
4101
4102 fn prepare_step_grouped_expert_parallel_refresh(
4103 &self,
4104 experts: &ResidentExpertParallel,
4105 plan: &PreparedStepGroupedExpertParallelGate,
4106 tokens: usize,
4107 selected: &[usize],
4108 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
4109 {
4110 validate_ep_residency(&self.ranks, experts)?;
4111 if plan.rank_inputs.len() != self.ranks.len()
4112 || plan.owners.len() != self.ranks.len()
4113 || plan.input_width != experts.input_width
4114 || plan.expert_width != experts.expert_width
4115 || tokens > plan.max_tokens
4116 {
4117 return Err(format!(
4118 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4119 input={}/{} expert={}/{} tokens={}/{}",
4120 plan.rank_inputs.len(),
4121 self.ranks.len(),
4122 plan.owners.len(),
4123 self.ranks.len(),
4124 plan.input_width,
4125 experts.input_width,
4126 plan.expert_width,
4127 experts.expert_width,
4128 tokens,
4129 plan.max_tokens,
4130 )
4131 .into());
4132 }
4133 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4134 if pairs > plan.max_pairs {
4135 return Err(format!(
4136 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4137 plan.max_pairs
4138 )
4139 .into());
4140 }
4141 let next_generation = plan
4142 .generation
4143 .checked_add(1)
4144 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4145 let owner_routes = partition_expert_owner_routes(
4146 experts.expert_count,
4147 self.ranks.len(),
4148 tokens,
4149 STEP_GROUPED_FP8_TOP_K,
4150 selected,
4151 )?;
4152 let mut schedules = Vec::with_capacity(self.ranks.len());
4153 for routes in owner_routes {
4154 if routes.selected.is_empty() {
4155 schedules.push(None);
4156 continue;
4157 }
4158 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4159 let local_pairs = routes.selected.len();
4160 let route_csr = ExpertCsr::from_pair_rows(
4161 local_experts,
4162 tokens,
4163 &routes.selected,
4164 &routes.token_rows,
4165 )?;
4166 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4167 let down_csr = ExpertCsr::from_pair_rows(
4168 local_experts,
4169 local_pairs,
4170 &routes.selected,
4171 &down_rows,
4172 )?;
4173 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4174 global_pairs: routes.global_pairs,
4175 route_csr,
4176 down_csr,
4177 }));
4178 }
4179 Ok((pairs, next_generation, schedules))
4180 }
4181
4182 fn commit_step_grouped_expert_parallel_refresh(
4183 &self,
4184 plan: &mut PreparedStepGroupedExpertParallelGate,
4185 tokens: usize,
4186 pairs: usize,
4187 next_generation: u64,
4188 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4189 ) -> Result<(), Box<dyn std::error::Error>> {
4190 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4191 let engine = &self.ranks[owner.rank];
4192 let _main = engine.gpu.enter_main()?;
4193 if let Some(schedule) = schedule {
4194 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4195 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4196 owner.global_pairs = schedule.global_pairs;
4197 } else {
4198 owner.route_csr.clear();
4199 owner.down_csr.clear();
4200 owner.global_pairs.clear();
4201 }
4202 }
4203 plan.tokens = tokens;
4204 plan.pairs = pairs;
4205 plan.generation = next_generation;
4206 plan.ready = true;
4207 Ok(())
4208 }
4209
4210 pub fn refresh_step_grouped_expert_parallel_gate(
4211 &self,
4212 experts: &ResidentExpertParallel,
4213 plan: &mut PreparedStepGroupedExpertParallelGate,
4214 input: &[f32],
4215 tokens: usize,
4216 selected: &[usize],
4217 ) -> Result<(), Box<dyn std::error::Error>> {
4218 validate_activations(input, tokens, experts.input_width)?;
4219 let (pairs, next_generation, schedules) =
4220 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4221
4222 plan.ready = false;
4223 plan.executed_generation = None;
4224 {
4225 let root = &self.ranks[0];
4226 let _main = root.gpu.enter_main()?;
4227 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4228 root.stream().memcpy_htod(input, &mut destination)?;
4229 root.stream().synchronize()?;
4230 }
4231 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4232 let root_input = &root_inputs[0];
4233 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4234 let engine = &self.ranks[rank + 1];
4235 let _main = engine.gpu.enter_main()?;
4236 let mut destination = peer_input.slice_mut(0..input.len());
4237 engine
4238 .stream()
4239 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4240 }
4241 self.commit_step_grouped_expert_parallel_refresh(
4242 plan,
4243 tokens,
4244 pairs,
4245 next_generation,
4246 schedules,
4247 )
4248 }
4249
4250 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4255 &self,
4256 experts: &ResidentExpertParallel,
4257 plan: &mut PreparedStepGroupedExpertParallelGate,
4258 input: &CudaSlice<f32>,
4259 tokens: usize,
4260 selected: &[usize],
4261 ) -> Result<(), Box<dyn std::error::Error>> {
4262 let input_values = tokens
4263 .checked_mul(experts.input_width)
4264 .ok_or("Step owner-grouped FP8 input size overflow")?;
4265 let root = self
4266 .ranks
4267 .first()
4268 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4269 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4270 return Err(format!(
4271 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4272 device {}",
4273 input.len(),
4274 input.ordinal(),
4275 input_values,
4276 root.ctx().ordinal(),
4277 )
4278 .into());
4279 }
4280 let (pairs, next_generation, schedules) =
4281 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4282
4283 plan.ready = false;
4284 plan.executed_generation = None;
4285 {
4286 let _main = root.gpu.enter_main()?;
4287 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4288 root.stream()
4289 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4290 root.stream().synchronize()?;
4291 }
4292 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4293 let root_input = &root_inputs[0];
4294 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4295 let engine = &self.ranks[rank + 1];
4296 let _main = engine.gpu.enter_main()?;
4297 let mut destination = peer_input.slice_mut(0..input_values);
4298 engine
4299 .stream()
4300 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4301 }
4302 self.commit_step_grouped_expert_parallel_refresh(
4303 plan,
4304 tokens,
4305 pairs,
4306 next_generation,
4307 schedules,
4308 )
4309 }
4310
4311 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4316 &self,
4317 experts: &ResidentExpertParallel,
4318 plan: &mut PreparedStepGroupedExpertParallelGate,
4319 input: &ResidentReplicatedDeviceRows,
4320 ) -> Result<(), Box<dyn std::error::Error>> {
4321 validate_ep_residency(&self.ranks, experts)?;
4322 validate_replicated_device_rows(&self.ranks, input)?;
4323 if !plan.ready
4324 || input.tokens != plan.tokens
4325 || input.width != plan.input_width
4326 || input.tokens > plan.max_tokens
4327 || plan.rank_inputs.len() != self.ranks.len()
4328 || plan.owners.len() != self.ranks.len()
4329 || plan.input_width != experts.input_width
4330 || plan.expert_width != experts.expert_width
4331 {
4332 return Err("Step owner-grouped replicated input geometry changed".into());
4333 }
4334 let values = input
4335 .tokens
4336 .checked_mul(input.width)
4337 .ok_or("Step owner-grouped replicated input size overflow")?;
4338 let next_generation = plan
4339 .generation
4340 .checked_add(1)
4341 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4342 plan.ready = false;
4343 plan.executed_generation = None;
4344 for (rank, engine) in self.ranks.iter().enumerate() {
4345 let _main = engine.gpu.enter_main()?;
4346 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4347 engine
4348 .stream()
4349 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4350 }
4351 plan.generation = next_generation;
4352 plan.ready = true;
4353 Ok(())
4354 }
4355
4356 pub fn execute_step_grouped_expert_parallel_gate(
4357 &self,
4358 experts: &ResidentExpertParallel,
4359 plan: &mut PreparedStepGroupedExpertParallelGate,
4360 ) -> Result<(), Box<dyn std::error::Error>> {
4361 validate_ep_residency(&self.ranks, experts)?;
4362 if !plan.ready
4363 || plan.rank_inputs.len() != self.ranks.len()
4364 || plan.owners.len() != self.ranks.len()
4365 || plan.input_width != experts.input_width
4366 || plan.expert_width != experts.expert_width
4367 {
4368 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4369 }
4370 plan.executed_generation = None;
4371
4372 for owner in &mut plan.owners {
4373 if owner.global_pairs.is_empty() {
4374 continue;
4375 }
4376 let engine = &self.ranks[owner.rank];
4377 let bank = &experts.ranks[owner.rank];
4378 let _main = engine.gpu.enter_main()?;
4379 let local_pairs = owner.global_pairs.len();
4380 owner.gate_workspace.quantize_for_shape(
4381 engine,
4382 &plan.rank_inputs[owner.rank],
4383 plan.tokens,
4384 local_pairs,
4385 )?;
4386 owner.gate_workspace.project(
4387 engine,
4388 &bank.gate.codes,
4389 &bank.gate.scales,
4390 &owner.route_csr,
4391 bank.gate.code_stride,
4392 bank.gate.scale_stride,
4393 1.0,
4394 )?;
4395 owner.up_workspace.quantize_for_shape(
4396 engine,
4397 &plan.rank_inputs[owner.rank],
4398 plan.tokens,
4399 local_pairs,
4400 )?;
4401 owner.up_workspace.project(
4402 engine,
4403 &bank.up.codes,
4404 &bank.up.scales,
4405 &owner.route_csr,
4406 bank.up.code_stride,
4407 bank.up.scale_stride,
4408 1.0,
4409 )?;
4410 }
4411 for owner in &mut plan.owners {
4412 if owner.global_pairs.is_empty() {
4413 continue;
4414 }
4415 let engine = &self.ranks[owner.rank];
4416 let _main = engine.gpu.enter_main()?;
4417 let values = owner.global_pairs.len() * plan.expert_width;
4418 if let Some(limit) = plan.activation_limit {
4419 engine.silu_clamped_mul_host_expf(
4420 owner.gate_workspace.output(),
4421 owner.up_workspace.output(),
4422 limit,
4423 &mut owner.activation,
4424 values,
4425 )?;
4426 } else {
4427 engine.silu_mul_host_expf(
4428 owner.gate_workspace.output(),
4429 owner.up_workspace.output(),
4430 &mut owner.activation,
4431 values,
4432 )?;
4433 }
4434 }
4435 for owner in &mut plan.owners {
4436 if owner.global_pairs.is_empty() {
4437 continue;
4438 }
4439 let engine = &self.ranks[owner.rank];
4440 let bank = &experts.ranks[owner.rank];
4441 let _main = engine.gpu.enter_main()?;
4442 let local_pairs = owner.global_pairs.len();
4443 owner.down_workspace.quantize_for_shape(
4444 engine,
4445 &owner.activation,
4446 local_pairs,
4447 local_pairs,
4448 )?;
4449 owner.down_workspace.project(
4450 engine,
4451 &bank.down.codes,
4452 &bank.down.scales,
4453 &owner.down_csr,
4454 bank.down.code_stride,
4455 bank.down.scale_stride,
4456 1.0,
4457 )?;
4458 }
4459 plan.executed_generation = Some(plan.generation);
4460 Ok(())
4461 }
4462
4463 pub fn collect_step_grouped_expert_parallel_gate(
4464 &self,
4465 plan: &PreparedStepGroupedExpertParallelGate,
4466 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4467 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4468 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4469 }
4470 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4471 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4472 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4473 for owner in &plan.owners {
4474 if owner.global_pairs.is_empty() {
4475 continue;
4476 }
4477 let engine = &self.ranks[owner.rank];
4478 let _main = engine.gpu.enter_main()?;
4479 let owner_gate = engine.dtoh_view(
4480 &owner
4481 .gate_workspace
4482 .output()
4483 .slice(0..owner.gate_workspace.output_len()),
4484 )?;
4485 let owner_up = engine.dtoh_view(
4486 &owner
4487 .up_workspace
4488 .output()
4489 .slice(0..owner.up_workspace.output_len()),
4490 )?;
4491 let owner_down = engine.dtoh_view(
4492 &owner
4493 .down_workspace
4494 .output()
4495 .slice(0..owner.down_workspace.output_len()),
4496 )?;
4497 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4498 let local_expert = local_pair * plan.expert_width;
4499 let global_expert = global_pair * plan.expert_width;
4500 gate[global_expert..global_expert + plan.expert_width]
4501 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4502 up[global_expert..global_expert + plan.expert_width]
4503 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4504
4505 let local_hidden = local_pair * plan.input_width;
4506 let global_hidden = global_pair * plan.input_width;
4507 down[global_hidden..global_hidden + plan.input_width]
4508 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4509 }
4510 }
4511 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4512 }
4513
4514 pub fn run_step_grouped_expert_parallel_gate(
4515 &self,
4516 experts: &ResidentExpertParallel,
4517 plan: &mut PreparedStepGroupedExpertParallelGate,
4518 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4519 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4520 self.collect_step_grouped_expert_parallel_gate(plan)
4521 }
4522
4523 pub fn prepare_step_grouped_expert_parallel_combine(
4524 &self,
4525 plan: &PreparedStepGroupedExpertParallelGate,
4526 route_weights: &[f32],
4527 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4528 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4529 return Err(
4530 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4531 );
4532 }
4533 let owner_pairs = plan
4534 .owners
4535 .iter()
4536 .map(|owner| owner.global_pairs.as_slice())
4537 .collect::<Vec<_>>();
4538 let shape = validate_weighted_route_combine(
4539 plan.input_width,
4540 STEP_GROUPED_FP8_TOP_K,
4541 plan.max_tokens,
4542 plan.tokens,
4543 &owner_pairs,
4544 route_weights,
4545 )?;
4546 if shape.max_pairs != plan.max_pairs {
4547 return Err(format!(
4548 "Step owner-grouped combine capacity {} != projection capacity {}",
4549 shape.max_pairs, plan.max_pairs
4550 )
4551 .into());
4552 }
4553 let root = self
4554 .ranks
4555 .first()
4556 .ok_or("Step owner-grouped combine has no root rank")?;
4557 let slot_values = shape
4558 .max_pairs
4559 .checked_mul(plan.input_width)
4560 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4561 let output_values = plan
4562 .max_tokens
4563 .checked_mul(plan.input_width)
4564 .ok_or("Step owner-grouped combine output capacity overflow")?;
4565 let (root_device, owners, peer_staging, slots, weights, output) = {
4566 let _main = root.gpu.enter_main()?;
4567 let mut owners = Vec::with_capacity(plan.owners.len());
4568 for _ in &plan.owners {
4569 owners.push(PreparedPeerWeightedRouteOwner {
4570 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4571 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4572 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4573 active_pairs: 0,
4574 });
4575 }
4576 (
4577 root.ctx().ordinal(),
4578 owners,
4579 root.uninit(slot_values)?,
4580 root.uninit(slot_values)?,
4581 root.uninit(shape.max_pairs)?,
4582 root.uninit(output_values)?,
4583 )
4584 };
4585 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4586 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4587 for engine in self.ranks.iter().skip(1) {
4588 let _main = engine.gpu.enter_main()?;
4589 peer_devices.push(engine.ctx().ordinal());
4590 peer_outputs.push(engine.uninit(output_values)?);
4591 }
4592 let mut combine = PreparedPeerWeightedRouteCombine {
4593 root_device,
4594 owners,
4595 peer_staging,
4596 slots,
4597 weights,
4598 output,
4599 peer_devices,
4600 peer_outputs,
4601 width: plan.input_width,
4602 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4603 max_tokens: plan.max_tokens,
4604 max_pairs: shape.max_pairs,
4605 tokens: 0,
4606 pairs: 0,
4607 projection_generation: 0,
4608 output_generation: None,
4609 broadcast_generation: None,
4610 ready: false,
4611 };
4612 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4613 Ok(combine)
4614 }
4615
4616 pub fn refresh_step_grouped_expert_parallel_combine(
4617 &self,
4618 plan: &PreparedStepGroupedExpertParallelGate,
4619 combine: &mut PreparedPeerWeightedRouteCombine,
4620 route_weights: &[f32],
4621 ) -> Result<(), Box<dyn std::error::Error>> {
4622 let output_capacity = combine
4623 .max_tokens
4624 .checked_mul(combine.width)
4625 .ok_or("Step owner-grouped combine output capacity overflow")?;
4626 if !plan.ready
4627 || combine.owners.len() != plan.owners.len()
4628 || combine.peer_devices.len() + 1 != self.ranks.len()
4629 || combine.peer_outputs.len() + 1 != self.ranks.len()
4630 || combine.width != plan.input_width
4631 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4632 || combine.max_tokens != plan.max_tokens
4633 || combine.max_pairs != plan.max_pairs
4634 || combine.output.len() < output_capacity
4635 || combine
4636 .peer_outputs
4637 .iter()
4638 .any(|output| output.len() < output_capacity)
4639 {
4640 return Err("Step owner-grouped combine/projection geometry changed".into());
4641 }
4642 if self
4643 .ranks
4644 .iter()
4645 .skip(1)
4646 .zip(&combine.peer_devices)
4647 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4648 {
4649 return Err("Step owner-grouped combine peer devices changed".into());
4650 }
4651 let owner_pairs = plan
4652 .owners
4653 .iter()
4654 .map(|owner| owner.global_pairs.as_slice())
4655 .collect::<Vec<_>>();
4656 let shape = validate_weighted_route_combine(
4657 combine.width,
4658 combine.experts_per_token,
4659 combine.max_tokens,
4660 plan.tokens,
4661 &owner_pairs,
4662 route_weights,
4663 )?;
4664 if shape.max_pairs != combine.max_pairs {
4665 return Err("Step owner-grouped combine capacity changed during refresh".into());
4666 }
4667 let metadata = owner_pairs
4668 .iter()
4669 .map(|pairs| {
4670 let token_rows = pairs
4671 .iter()
4672 .map(|&pair| (pair / combine.experts_per_token) as i32)
4673 .collect::<Vec<_>>();
4674 let slots = pairs
4675 .iter()
4676 .map(|&pair| (pair % combine.experts_per_token) as i32)
4677 .collect::<Vec<_>>();
4678 let weights = pairs
4679 .iter()
4680 .map(|&pair| route_weights[pair])
4681 .collect::<Vec<_>>();
4682 (token_rows, slots, weights)
4683 })
4684 .collect::<Vec<_>>();
4685
4686 combine.ready = false;
4687 combine.output_generation = None;
4688 combine.broadcast_generation = None;
4689 let root = self
4690 .ranks
4691 .first()
4692 .ok_or("Step owner-grouped combine has no root rank")?;
4693 let _main = root.gpu.enter_main()?;
4694 if root.ctx().ordinal() != combine.root_device {
4695 return Err(format!(
4696 "Step owner-grouped combine root device changed {} != {}",
4697 root.ctx().ordinal(),
4698 combine.root_device
4699 )
4700 .into());
4701 }
4702 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4703 if token_rows.is_empty() {
4704 owner.active_pairs = 0;
4705 continue;
4706 }
4707 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4708 root.htod_i32_into(&mut owner.slots, &slots)?;
4709 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4710 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4711 owner.active_pairs = token_rows.len();
4712 }
4713 combine.tokens = plan.tokens;
4714 combine.pairs = shape.pairs;
4715 combine.projection_generation = plan.generation;
4716 combine.ready = true;
4717 Ok(())
4718 }
4719
4720 pub fn execute_step_grouped_expert_parallel_combine(
4721 &self,
4722 plan: &PreparedStepGroupedExpertParallelGate,
4723 combine: &mut PreparedPeerWeightedRouteCombine,
4724 ) -> Result<(), Box<dyn std::error::Error>> {
4725 if !plan.ready
4726 || plan.executed_generation != Some(plan.generation)
4727 || !combine.ready
4728 || combine.tokens != plan.tokens
4729 || combine.pairs != plan.pairs
4730 || combine.width != plan.input_width
4731 || combine.owners.len() != plan.owners.len()
4732 || combine.projection_generation != plan.generation
4733 {
4734 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4735 }
4736 combine.output_generation = None;
4737 combine.broadcast_generation = None;
4738 for owner in &plan.owners {
4739 if owner.rank == 0 || owner.global_pairs.is_empty() {
4740 continue;
4741 }
4742 let engine = &self.ranks[owner.rank];
4743 let _main = engine.gpu.enter_main()?;
4744 engine.stream().synchronize()?;
4745 }
4746 let root = self
4747 .ranks
4748 .first()
4749 .ok_or("Step owner-grouped combine has no root rank")?;
4750 let _main = root.gpu.enter_main()?;
4751 if root.ctx().ordinal() != combine.root_device {
4752 return Err("Step owner-grouped combine is not resident on the root device".into());
4753 }
4754 for (index, owner) in plan.owners.iter().enumerate() {
4755 let metadata = &combine.owners[index];
4756 if owner.global_pairs.len() != metadata.active_pairs {
4757 return Err(format!(
4758 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4759 owner.global_pairs.len(),
4760 metadata.active_pairs
4761 )
4762 .into());
4763 }
4764 if metadata.active_pairs == 0 {
4765 continue;
4766 }
4767 let values = metadata
4768 .active_pairs
4769 .checked_mul(combine.width)
4770 .ok_or("Step owner-grouped combine peer value count overflow")?;
4771 if owner.rank == 0 {
4772 root.scatter_slot(
4773 owner.down_workspace.output(),
4774 &metadata.token_rows,
4775 &metadata.slots,
4776 &metadata.weights,
4777 &mut combine.slots,
4778 &mut combine.weights,
4779 combine.width,
4780 combine.experts_per_token,
4781 metadata.active_pairs,
4782 )?;
4783 } else {
4784 let source = owner.down_workspace.output().slice(0..values);
4785 let mut destination = combine.peer_staging.slice_mut(0..values);
4786 root.stream().memcpy_dtod(&source, &mut destination)?;
4787 root.scatter_slot(
4788 &combine.peer_staging,
4789 &metadata.token_rows,
4790 &metadata.slots,
4791 &metadata.weights,
4792 &mut combine.slots,
4793 &mut combine.weights,
4794 combine.width,
4795 combine.experts_per_token,
4796 metadata.active_pairs,
4797 )?;
4798 }
4799 }
4800 root.reduce_slots_host(
4801 &combine.slots,
4802 &combine.weights,
4803 &mut combine.output,
4804 combine.width,
4805 combine.experts_per_token,
4806 combine.tokens,
4807 )?;
4808 combine.output_generation = Some(plan.generation);
4809 Ok(())
4810 }
4811
4812 pub fn collect_step_grouped_expert_parallel_combine(
4813 &self,
4814 plan: &PreparedStepGroupedExpertParallelGate,
4815 combine: &PreparedPeerWeightedRouteCombine,
4816 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4817 if !plan.ready
4818 || combine.output_generation != Some(plan.generation)
4819 || combine.projection_generation != plan.generation
4820 {
4821 return Err("Step owner-grouped combine output is stale or has not executed".into());
4822 }
4823 let root = self
4824 .ranks
4825 .first()
4826 .ok_or("Step owner-grouped combine has no root rank")?;
4827 let _main = root.gpu.enter_main()?;
4828 if root.ctx().ordinal() != combine.root_device {
4829 return Err("Step owner-grouped combine is not resident on the root device".into());
4830 }
4831 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4832 }
4833
4834 pub fn copy_step_grouped_expert_parallel_combine_root(
4839 &self,
4840 plan: &PreparedStepGroupedExpertParallelGate,
4841 combine: &PreparedPeerWeightedRouteCombine,
4842 destination: &Engine,
4843 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4844 if !plan.ready
4845 || combine.output_generation != Some(plan.generation)
4846 || combine.projection_generation != plan.generation
4847 {
4848 return Err("Step owner-grouped combine output is stale or has not executed".into());
4849 }
4850 let root = self
4851 .ranks
4852 .first()
4853 .ok_or("Step owner-grouped combine has no root rank")?;
4854 if root.ctx().ordinal() != combine.root_device
4855 || destination.ctx().ordinal() != combine.root_device
4856 {
4857 return Err(format!(
4858 "Step owner-grouped combine root/destination devices {}/{} != {}",
4859 root.ctx().ordinal(),
4860 destination.ctx().ordinal(),
4861 combine.root_device,
4862 )
4863 .into());
4864 }
4865 let values = combine
4866 .tokens
4867 .checked_mul(combine.width)
4868 .ok_or("Step owner-grouped combine copy size overflow")?;
4869 {
4870 let _main = root.gpu.enter_main()?;
4871 root.stream().synchronize()?;
4872 }
4873 let _main = destination.gpu.enter_main()?;
4874 let mut output = destination.uninit(values)?;
4875 destination
4876 .stream()
4877 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4878 Ok(output)
4879 }
4880
4881 pub fn broadcast_step_grouped_expert_parallel_combine(
4882 &self,
4883 plan: &PreparedStepGroupedExpertParallelGate,
4884 combine: &mut PreparedPeerWeightedRouteCombine,
4885 ) -> Result<(), Box<dyn std::error::Error>> {
4886 if !plan.ready
4887 || combine.output_generation != Some(plan.generation)
4888 || combine.projection_generation != plan.generation
4889 || combine.peer_devices.len() + 1 != self.ranks.len()
4890 || combine.peer_outputs.len() + 1 != self.ranks.len()
4891 {
4892 return Err("Step owner-grouped combine output cannot be broadcast".into());
4893 }
4894 combine.broadcast_generation = None;
4895 let values = combine
4896 .tokens
4897 .checked_mul(combine.width)
4898 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4899 {
4900 let root = self
4901 .ranks
4902 .first()
4903 .ok_or("Step owner-grouped combine has no root rank")?;
4904 let _main = root.gpu.enter_main()?;
4905 if root.ctx().ordinal() != combine.root_device {
4906 return Err("Step owner-grouped combine root device changed".into());
4907 }
4908 root.stream().synchronize()?;
4909 }
4910 let source = &combine.output;
4911 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4912 let engine = &self.ranks[index + 1];
4913 let _main = engine.gpu.enter_main()?;
4914 if engine.ctx().ordinal() != combine.peer_devices[index] {
4915 return Err(format!(
4916 "Step owner-grouped combine peer {} device changed",
4917 index + 1
4918 )
4919 .into());
4920 }
4921 let mut destination = destination_buffer.slice_mut(0..values);
4922 engine
4923 .stream()
4924 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4925 }
4926 combine.broadcast_generation = Some(plan.generation);
4927 Ok(())
4928 }
4929
4930 pub fn collect_step_grouped_expert_parallel_broadcast(
4931 &self,
4932 plan: &PreparedStepGroupedExpertParallelGate,
4933 combine: &PreparedPeerWeightedRouteCombine,
4934 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4935 if !plan.ready
4936 || combine.output_generation != Some(plan.generation)
4937 || combine.broadcast_generation != Some(plan.generation)
4938 || combine.peer_outputs.len() + 1 != self.ranks.len()
4939 {
4940 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4941 }
4942 let values = combine
4943 .tokens
4944 .checked_mul(combine.width)
4945 .ok_or("Step owner-grouped combine collection size overflow")?;
4946 let mut outputs = Vec::with_capacity(self.ranks.len());
4947 {
4948 let root = &self.ranks[0];
4949 let _main = root.gpu.enter_main()?;
4950 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4951 }
4952 for (index, output) in combine.peer_outputs.iter().enumerate() {
4953 let engine = &self.ranks[index + 1];
4954 let _main = engine.gpu.enter_main()?;
4955 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4956 }
4957 Ok(outputs)
4958 }
4959
4960 pub fn finish_step_grouped_expert_parallel_layer(
4962 &self,
4963 plan: &PreparedStepGroupedExpertParallelGate,
4964 combine: &PreparedPeerWeightedRouteCombine,
4965 shared: &ResidentReplicatedDeviceRows,
4966 residual: &ResidentReplicatedDeviceRows,
4967 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4968 validate_replicated_device_rows(&self.ranks, shared)?;
4969 validate_replicated_device_rows(&self.ranks, residual)?;
4970 if !plan.ready
4971 || plan.executed_generation != Some(plan.generation)
4972 || combine.output_generation != Some(plan.generation)
4973 || combine.broadcast_generation != Some(plan.generation)
4974 || combine.projection_generation != plan.generation
4975 || combine.peer_outputs.len() + 1 != self.ranks.len()
4976 || shared.tokens != combine.tokens
4977 || residual.tokens != combine.tokens
4978 || shared.width != combine.width
4979 || residual.width != combine.width
4980 {
4981 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4982 }
4983 let values = combine
4984 .tokens
4985 .checked_mul(combine.width)
4986 .ok_or("Step full-layer output size overflow")?;
4987 let mut ranks = Vec::with_capacity(self.ranks.len());
4988 for rank in 0..self.ranks.len() {
4989 let engine = &self.ranks[rank];
4990 let _main = engine.gpu.enter_main()?;
4991 let routed = if rank == 0 {
4992 &combine.output
4993 } else {
4994 &combine.peer_outputs[rank - 1]
4995 };
4996 let mut ffn = engine.uninit(values)?;
4997 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4998 let mut output = engine.uninit(values)?;
4999 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
5000 ranks.push(output);
5001 }
5002 Ok(ResidentReplicatedDeviceRows {
5003 ranks,
5004 tokens: combine.tokens,
5005 width: combine.width,
5006 })
5007 }
5008
5009 pub fn run_step_grouped_expert_parallel_combine(
5010 &self,
5011 plan: &PreparedStepGroupedExpertParallelGate,
5012 combine: &mut PreparedPeerWeightedRouteCombine,
5013 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5014 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
5015 self.collect_step_grouped_expert_parallel_combine(plan, combine)
5016 }
5017
5018 pub fn upload_tensor_parallel(
5019 &self,
5020 gate: E4m3ExpertBank<'_>,
5021 up: E4m3ExpertBank<'_>,
5022 down: E4m3ExpertBank<'_>,
5023 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
5024 gate.validate()?;
5025 up.validate()?;
5026 down.validate()?;
5027 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
5028 return Err("TP gate/up/down expert counts differ".into());
5029 }
5030 if gate.in_features != up.in_features || gate.out_features != up.out_features {
5031 return Err("TP gate/up dimensions differ".into());
5032 }
5033 if down.in_features != gate.out_features || down.out_features != gate.in_features {
5034 return Err(format!(
5035 "TP down {}x{} does not invert gate/up {}x{}",
5036 down.out_features, down.in_features, gate.out_features, gate.in_features
5037 )
5038 .into());
5039 }
5040 let tp = self.ranks.len();
5041 validate_column_bank_shape(gate, tp)?;
5042 validate_column_bank_shape(up, tp)?;
5043 validate_row_bank_shape(down, tp)?;
5044
5045 let mut gate_ranks = Vec::with_capacity(tp);
5046 let mut up_ranks = Vec::with_capacity(tp);
5047 let mut down_ranks = Vec::with_capacity(tp);
5048 for (rank, engine) in self.ranks.iter().enumerate() {
5049 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
5050 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
5051 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
5052 }
5053 Ok(ResidentTensorParallel {
5054 bank: ResidentTpExpertBank {
5055 gate: gate_ranks,
5056 up: up_ranks,
5057 down: down_ranks,
5058 expert_count: gate.expert_count,
5059 input_width: gate.in_features,
5060 expert_width: gate.out_features,
5061 },
5062 })
5063 }
5064
5065 pub fn run_tensor_parallel_routes(
5066 &self,
5067 experts: &ResidentTensorParallel,
5068 input: &[f32],
5069 tokens: usize,
5070 selected: &[usize],
5071 route_weights: &[f32],
5072 experts_per_token: usize,
5073 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5074 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
5075 validate_activations(input, tokens, experts.bank.input_width)?;
5076 let pairs = tokens
5077 .checked_mul(experts_per_token)
5078 .ok_or("TP route count overflow")?;
5079 if selected.len() != pairs || route_weights.len() != pairs {
5080 return Err(format!(
5081 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
5082 {experts_per_token} ({pairs})",
5083 selected.len(),
5084 route_weights.len(),
5085 )
5086 .into());
5087 }
5088 if !route_weights.iter().all(|weight| weight.is_finite()) {
5089 return Err("TP route weights contain a non-finite value".into());
5090 }
5091
5092 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
5093 for token in 0..tokens {
5094 let input_row =
5095 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
5096 for slot in 0..experts_per_token {
5097 let pair = token * experts_per_token + slot;
5098 let expert = selected[pair];
5099 if expert >= experts.bank.expert_count {
5100 return Err(format!(
5101 "TP selected expert {expert} outside 0..{}",
5102 experts.bank.expert_count
5103 )
5104 .into());
5105 }
5106 let down = if self.native_p2p {
5107 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
5108 } else {
5109 let gate =
5110 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
5111 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5112 let activated: Vec<f32> = gate
5113 .iter()
5114 .zip(&up)
5115 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5116 .collect();
5117 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5118 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5119 };
5120 let weight = route_weights[pair];
5121 for (sum, value) in output
5122 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5123 .iter_mut()
5124 .zip(down)
5125 {
5126 *sum += weight * value;
5127 }
5128 }
5129 }
5130 Ok(output)
5131 }
5132
5133 fn run_column_bank_expert(
5134 &self,
5135 ranks: &[ResidentE4m3ExpertBankRank],
5136 expert: usize,
5137 input: &[f32],
5138 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5139 let local_out = ranks
5140 .first()
5141 .ok_or("TP column bank has no ranks")?
5142 .out_features;
5143 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5144 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5145 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5146 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5147 }
5148 Ok(gathered)
5149 }
5150
5151 fn run_row_bank_expert(
5152 &self,
5153 ranks: &[ResidentE4m3ExpertBankRank],
5154 expert: usize,
5155 input: &[f32],
5156 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5157 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5158 if input.len() != local_in * ranks.len() {
5159 return Err(format!(
5160 "TP row input {} != {} ranks x {local_in}",
5161 input.len(),
5162 ranks.len()
5163 )
5164 .into());
5165 }
5166 let out_features = ranks[0].out_features;
5167 let mut reduced = vec![0.0f32; out_features];
5168 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5169 let blocks = bank
5170 .k_blocks
5171 .ok_or("TP row bank is not packed in native K-block order")?;
5172 if blocks * FP8_BLOCK != local_in {
5173 return Err(format!(
5174 "TP row bank has {blocks} blocks but local input width is {local_in}"
5175 )
5176 .into());
5177 }
5178 for block in 0..blocks {
5179 let global_start = rank * local_in + block * FP8_BLOCK;
5180 let partial = run_resident_bank_expert_block(
5181 engine,
5182 bank,
5183 expert,
5184 block,
5185 &input[global_start..global_start + FP8_BLOCK],
5186 )?;
5187 for (sum, value) in reduced.iter_mut().zip(partial) {
5188 *sum += value;
5189 }
5190 }
5191 }
5192 Ok(reduced)
5193 }
5194
5195 fn run_tensor_parallel_expert_native(
5196 &self,
5197 bank: &ResidentTpExpertBank,
5198 expert: usize,
5199 input: &[f32],
5200 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5201 if !self.native_p2p || self.ranks.len() < 2 {
5202 return Err("native TP expert execution requires at least two P2P ranks".into());
5203 }
5204 let local_out = bank
5205 .gate
5206 .first()
5207 .ok_or("native TP gate bank has no ranks")?
5208 .out_features;
5209 if local_out * self.ranks.len() != bank.expert_width {
5210 return Err(format!(
5211 "native TP gate shards {}x{local_out} != expert width {}",
5212 self.ranks.len(),
5213 bank.expert_width
5214 )
5215 .into());
5216 }
5217
5218 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5221 let root_input = {
5222 let root = &self.ranks[0];
5223 let _main = root.gpu.enter_main()?;
5224 root.htod(input)?
5225 };
5226 rank_inputs.push(root_input);
5227 for engine in &self.ranks[1..] {
5228 let peer_input = {
5229 let _main = engine.gpu.enter_main()?;
5230 let mut peer_input = engine.uninit(input.len())?;
5231 engine
5232 .stream()
5233 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5234 peer_input
5235 };
5236 rank_inputs.push(peer_input);
5237 }
5238
5239 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5240 let mut up_shards = Vec::with_capacity(self.ranks.len());
5241 for rank in 0..self.ranks.len() {
5242 gate_shards.push(run_resident_bank_expert_device(
5243 &self.ranks[rank],
5244 &bank.gate[rank],
5245 expert,
5246 &rank_inputs[rank],
5247 1,
5248 )?);
5249 up_shards.push(run_resident_bank_expert_device(
5250 &self.ranks[rank],
5251 &bank.up[rank],
5252 expert,
5253 &rank_inputs[rank],
5254 1,
5255 )?);
5256 }
5257
5258 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5262 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5263 let activated = gate
5264 .iter()
5265 .zip(&up)
5266 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5267 .collect::<Vec<_>>();
5268 debug_assert_eq!(activated.len(), bank.expert_width);
5269
5270 let root_activated = {
5271 let root = &self.ranks[0];
5272 let _main = root.gpu.enter_main()?;
5273 root.htod(&activated)?
5274 };
5275 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5276 for (rank, engine) in self.ranks.iter().enumerate() {
5277 let start = rank * local_out;
5278 let source = root_activated.slice(start..start + local_out);
5279 let local = {
5280 let _main = engine.gpu.enter_main()?;
5281 let mut local = engine.uninit(local_out)?;
5282 engine.stream().memcpy_dtod(&source, &mut local)?;
5283 local
5284 };
5285 rank_activated.push(local);
5286 }
5287
5288 let out_features = bank
5289 .down
5290 .first()
5291 .ok_or("native TP down bank has no ranks")?
5292 .out_features;
5293 let mut reduced = {
5294 let root = &self.ranks[0];
5295 let _main = root.gpu.enter_main()?;
5296 root.htod(&vec![0.0f32; out_features])?
5297 };
5298 let mut remote_partial_keepalive = Vec::new();
5299 for rank in 0..self.ranks.len() {
5300 let down = &bank.down[rank];
5301 let blocks = down
5302 .k_blocks
5303 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5304 if blocks * FP8_BLOCK != local_out {
5305 return Err(format!(
5306 "native TP rank {rank} has {blocks} blocks but local activation width is \
5307 {local_out}"
5308 )
5309 .into());
5310 }
5311 for block in 0..blocks {
5312 let start = block * FP8_BLOCK;
5313 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5314 let partial = run_resident_bank_expert_block_device(
5315 &self.ranks[rank],
5316 down,
5317 expert,
5318 block,
5319 &input_block,
5320 )?;
5321 let root_partial = if rank == 0 {
5322 partial
5323 } else {
5324 let root = &self.ranks[0];
5325 let _main = root.gpu.enter_main()?;
5326 let mut peer_partial = root.uninit(out_features)?;
5327 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5328 remote_partial_keepalive.push(partial);
5329 peer_partial
5330 };
5331 let next = {
5332 let root = &self.ranks[0];
5333 let _main = root.gpu.enter_main()?;
5334 let mut next = root.uninit(out_features)?;
5335 root.add(&reduced, &root_partial, &mut next, out_features)?;
5336 next
5337 };
5338 reduced = next;
5339 }
5340 }
5341 let output = {
5342 let root = &self.ranks[0];
5343 let _main = root.gpu.enter_main()?;
5344 root.dtoh(&reduced)?
5345 };
5346 drop(remote_partial_keepalive);
5347 Ok(output)
5348 }
5349
5350 pub fn gather_native_column_shards_device(
5352 &self,
5353 shards: &[CudaSlice<f32>],
5354 tokens: usize,
5355 local_out: usize,
5356 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5357 let shard_len = tokens
5358 .checked_mul(local_out)
5359 .ok_or("native TP gather shard size overflow")?;
5360 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5361 return Err("native TP gather shard geometry mismatch".into());
5362 }
5363 for engine in &self.ranks[1..] {
5367 let _main = engine.gpu.enter_main()?;
5368 engine.stream().synchronize()?;
5369 }
5370 let root = &self.ranks[0];
5371 let _main = root.gpu.enter_main()?;
5372 let global_out = shards
5373 .len()
5374 .checked_mul(local_out)
5375 .ok_or("native TP gather output width overflow")?;
5376 let gathered_len = tokens
5377 .checked_mul(global_out)
5378 .ok_or("native TP gather output size overflow")?;
5379 let mut gathered = root.uninit(gathered_len)?;
5380 if self.bulk_p2p {
5381 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5382 if shards.len() > 1 {
5383 let mut staging = root.uninit(shard_len)?;
5384 for (rank, shard) in shards.iter().enumerate().skip(1) {
5385 root.stream().memcpy_dtod(shard, &mut staging)?;
5386 root.place_rows_strided(
5387 &staging,
5388 &mut gathered,
5389 local_out,
5390 tokens,
5391 global_out,
5392 rank * local_out,
5393 )?;
5394 }
5395 }
5396 } else {
5397 for token in 0..tokens {
5398 for (rank, shard) in shards.iter().enumerate() {
5399 let source = shard.slice(token * local_out..(token + 1) * local_out);
5400 let start = token * global_out + rank * local_out;
5401 let mut destination = gathered.slice_mut(start..start + local_out);
5402 root.stream().memcpy_dtod(&source, &mut destination)?;
5403 }
5404 }
5405 }
5406 Ok(gathered)
5407 }
5408
5409 pub fn gather_native_column_shards(
5410 &self,
5411 shards: &[CudaSlice<f32>],
5412 tokens: usize,
5413 local_out: usize,
5414 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5415 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5416 let root = &self.ranks[0];
5417 let _main = root.gpu.enter_main()?;
5418 root.dtoh(&gathered)
5419 }
5420
5421 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5422 &self.decode_v2
5423 }
5424
5425 pub(crate) fn decode_v2_ensure(
5434 &self,
5435 e: &Engine,
5436 q_m: &ResidentBf16ColumnParallel,
5437 k_m: &ResidentBf16ColumnParallel,
5438 v_m: &ResidentBf16ColumnParallel,
5439 o_m: &ResidentStepBf16RowParallel,
5440 heads: usize,
5441 ) -> Result<usize, Box<dyn std::error::Error>> {
5442 if self.ranks.len() > 1 && !self.native_p2p {
5443 return Err("step TP decode v2 requires native P2P ranks".into());
5444 }
5445 let ranks = self.ranks.len();
5446 let fused_door = step_tp_qkv_fused_enabled()?;
5450 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5451 ResidentBf16Weight::F32(_) => true,
5452 ResidentBf16Weight::Bf16(_) => fused_door,
5453 };
5454 for matrix in [q_m, k_m, v_m] {
5455 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5456 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5457 return Err("step TP decode v2 QKV geometry mismatch".into());
5458 }
5459 for rank in &matrix.ranks {
5460 if !arm_ok(&rank.weight) {
5461 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5462 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5463 .into());
5464 }
5465 }
5466 }
5467 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5468 for blocks in &o_m.ranks {
5469 for block in blocks {
5470 if !arm_ok(&block.weight) {
5471 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5472 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5473 .into());
5474 }
5475 }
5476 }
5477 if v_m.out_features != k_m.out_features
5478 || o_m.in_features != q_m.out_features
5479 || heads == 0
5480 || heads % ranks != 0
5481 {
5482 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5483 }
5484 let local_q_dim = q_m.out_features / ranks;
5485 let local_kv_dim = k_m.out_features / ranks;
5486 let o_out = o_m.out_features;
5487 let o_block_cols = o_m.canonical_chunk_cols;
5488 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5489 if blocks_per_rank == 0
5490 || o_m
5491 .ranks
5492 .iter()
5493 .any(|blocks| blocks.len() != blocks_per_rank)
5494 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5495 {
5496 return Err("step TP decode v2 O canonical block grid mismatch".into());
5497 }
5498
5499 let mut guard = self
5500 .decode_v2
5501 .lock()
5502 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5503 if let Some(index) = guard.iter().position(|ws| {
5504 ws.local_q_dim == local_q_dim
5505 && ws.local_kv_dim == local_kv_dim
5506 && ws.heads == heads
5507 && ws.o_out == o_out
5508 && ws.o_block_cols == o_block_cols
5509 && ws.blocks_per_rank == blocks_per_rank
5510 && ws.e_device == e.ctx().ordinal()
5511 && ws.q.len() == ranks
5512 }) {
5513 return Ok(index);
5514 }
5515
5516 let mut q_raw = Vec::with_capacity(ranks);
5517 let mut k_raw = Vec::with_capacity(ranks);
5518 let mut v_raw = Vec::with_capacity(ranks);
5519 let mut q = Vec::with_capacity(ranks);
5520 let mut k = Vec::with_capacity(ranks);
5521 let mut pos = Vec::with_capacity(ranks);
5522 let mut gate = Vec::with_capacity(ranks);
5523 let mut attn_out = Vec::with_capacity(ranks);
5524 let mut gated = Vec::with_capacity(ranks);
5525 let mut fuse_ctr = Vec::with_capacity(ranks);
5526 let mut o_partials = Vec::with_capacity(ranks);
5527 let mut ev_rank = Vec::with_capacity(ranks);
5528 let direct_join = oproj_direct_on();
5529 for (rank, engine) in self.ranks.iter().enumerate() {
5530 let _main = engine.gpu.enter_main()?;
5531 q_raw.push(engine.uninit(local_q_dim)?);
5532 k_raw.push(engine.uninit(local_kv_dim)?);
5533 v_raw.push(engine.uninit(local_kv_dim)?);
5534 q.push(engine.uninit(local_q_dim)?);
5535 k.push(engine.uninit(local_kv_dim)?);
5536 pos.push(engine.htod_i32(&[0])?);
5537 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5538 gate.push(engine.uninit(heads / ranks)?);
5539 attn_out.push(engine.uninit(local_q_dim)?);
5540 gated.push(engine.uninit(local_q_dim)?);
5541 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5542 for _ in 0..blocks_per_rank {
5543 if direct_join && rank != 0 {
5546 let root = &self.ranks[0];
5547 let _root_main = root.gpu.enter_main()?;
5548 rank_partials.push(root.uninit(o_out)?);
5549 } else {
5550 rank_partials.push(engine.uninit(o_out)?);
5551 }
5552 }
5553 o_partials.push(rank_partials);
5554 ev_rank.push(engine.ctx().new_event(None)?);
5555 }
5556 let root = &self.ranks[0];
5557 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5558 let _main = root.gpu.enter_main()?;
5559 (
5560 root.uninit(o_out)?,
5561 root.uninit(o_out)?,
5562 root.uninit(o_out)?,
5563 root.htod(&vec![0.0f32; o_out])?,
5564 root.uninit(ranks * local_kv_dim)?,
5565 root.uninit(ranks * local_kv_dim)?,
5566 root.ctx().new_event(None)?,
5567 root.ctx().new_event(None)?,
5568 )
5569 };
5570 let (gate_e, ev_entry) = {
5571 let _main = e.gpu.enter_main()?;
5572 (e.uninit(heads)?, e.ctx().new_event(None)?)
5573 };
5574 let raw_attn_in = Vec::new();
5575 let raw_pos = Vec::new();
5576 guard.push(StepTpDecodeV2Ws {
5577 tcol_q: Vec::new(),
5578 tcol_k: Vec::new(),
5579 tcol_v: Vec::new(),
5580 tcol_g: Vec::new(),
5581 tcol_in: Vec::new(),
5582 tcol_cap: 0,
5583 w8_aq: Vec::new(),
5584 w8_ad: Vec::new(),
5585 w8_in: 0,
5586 w8o_aq: Vec::new(),
5587 w8o_ad: Vec::new(),
5588 w8o_in: 0,
5589 w8t_aq: Vec::new(),
5590 w8t_ad: Vec::new(),
5591 w8t_in: 0,
5592 w8t_oaq: Vec::new(),
5593 w8t_oad: Vec::new(),
5594 w8t_oin: 0,
5595 w8t_cap: 0,
5596 fa2_q: Vec::new(),
5597 fa2_gate: Vec::new(),
5598 fa2_gated: Vec::new(),
5599 fa2_cap: 0,
5600 rope_k_t: Vec::new(),
5601 rope_ctr_t: Vec::new(),
5602 rope_pos_t: Vec::new(),
5603 rows_tabs: Vec::new(),
5604 rows_tab_t: Vec::new(),
5605 rows_tab_shadow: Vec::new(),
5606 tcol_gated: Vec::new(),
5607 tcol_opart: Vec::new(),
5608 tcol_opeer: None,
5609 tcol_omix: None,
5610 tcol_ocap: 0,
5611 q_raw,
5612 k_raw,
5613 v_raw,
5614 q,
5615 k,
5616 pos,
5617 fuse_ctr,
5618 gate,
5619 attn_out,
5620 gated,
5621 o_partials,
5622 ev_rank,
5623 peer_partial,
5624 reduce_a,
5625 reduce_b,
5626 zeros,
5627 k_shadow,
5628 v_shadow,
5629 ev_refresh,
5630 ev_oproj,
5631 gate_e,
5632 attn_in: Vec::new(),
5633 h_stage: None,
5634 pos_stage: None,
5635 raw_h_stage: 0,
5636 raw_pos_stage: 0,
5637 raw_attn_in,
5638 raw_pos,
5639 raw_o_partial1: 0,
5640 raw_peer_partial: 0,
5641 raw_k1: 0,
5642 raw_v1: 0,
5643 raw_k_shadow: 0,
5644 raw_v_shadow: 0,
5645 raw_mixed_stage_e: 0,
5646 raw_reduce_a: 0,
5647 raw_shadow_stage_e: (0, 0),
5648 ev_entry,
5649 e_device: e.ctx().ordinal(),
5650 local_q_dim,
5651 local_kv_dim,
5652 heads,
5653 o_out,
5654 o_block_cols,
5655 blocks_per_rank,
5656 });
5657 eprintln!(
5658 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5659 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5660 residency=persistent ordering=evented performance_claim=false"
5661 );
5662 Ok(guard.len() - 1)
5663 }
5664
5665 #[allow(clippy::too_many_arguments)]
5673 #[allow(clippy::too_many_arguments)]
5678 pub fn decode_v2_input_qkv_tcol(
5679 &self,
5680 ws_index: usize,
5681 e: &Engine,
5682 h_t: &CudaSlice<f32>,
5683 t: usize,
5684 q_m: &ResidentBf16ColumnParallel,
5685 k_m: &ResidentBf16ColumnParallel,
5686 v_m: &ResidentBf16ColumnParallel,
5687 gate_shards: Option<StepTpGateShards<'_>>,
5688 ) -> Result<(), Box<dyn std::error::Error>> {
5689 let ranks = self.ranks.len();
5690 let mut guard = self
5691 .decode_v2
5692 .lock()
5693 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5694 let ws = guard
5695 .get_mut(ws_index)
5696 .ok_or("step TP decode v2 workspace index out of range")?;
5697 let in_f = q_m.in_features;
5698 if h_t.len() < t * in_f || t == 0 || t > 32 {
5699 return Err("decode_v2_input_qkv_tcol geometry".into());
5700 }
5701 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5703 ws.tcol_q.clear();
5704 ws.tcol_k.clear();
5705 ws.tcol_v.clear();
5706 ws.tcol_g.clear();
5707 ws.tcol_in.clear();
5708 for engine in &self.ranks {
5709 let _m = engine.gpu.enter_main()?;
5710 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
5711 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
5712 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
5713 ws.tcol_g
5714 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
5715 ws.tcol_in.push(engine.uninit(32 * in_f)?);
5716 }
5717 ws.tcol_cap = 32;
5718 }
5719 use cudarc::driver::DevicePtr;
5721 let raw_src = {
5722 let _main = e.gpu.enter_main()?;
5723 let stream = e.stream();
5724 let (p, _g) = h_t.device_ptr(&stream);
5725 ws.ev_entry.record(&stream)?;
5726 p as u64
5727 };
5728 for rank in 0..ranks {
5729 let engine = &self.ranks[rank];
5730 let _main = engine.gpu.enter_main()?;
5731 engine.stream().wait(&ws.ev_entry)?;
5732 let raw_dst = {
5733 let stream = engine.stream();
5734 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5735 p as u64
5736 };
5737 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5738 let out_g = match &gate_shards {
5739 Some(_) => ws.heads / ranks,
5740 None => 0,
5741 };
5742 match (
5743 &q_m.ranks[rank].weight,
5744 &k_m.ranks[rank].weight,
5745 &v_m.ranks[rank].weight,
5746 ) {
5747 (
5748 ResidentBf16Weight::Bf16(wq),
5749 ResidentBf16Weight::Bf16(wk),
5750 ResidentBf16Weight::Bf16(wv),
5751 ) => {
5752 let wg = match &gate_shards {
5753 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5754 Some(StepTpGateShards::F32(_)) => {
5755 return Err(
5756 "tcol verify: gate shard class does not match bf16 QKV".into()
5757 );
5758 }
5759 None => wq,
5760 };
5761 let StepTpDecodeV2Ws {
5762 tcol_q,
5763 tcol_k,
5764 tcol_v,
5765 tcol_g,
5766 tcol_in,
5767 local_q_dim,
5768 local_kv_dim,
5769 w8t_aq,
5770 w8t_ad,
5771 w8t_in,
5772 w8t_cap,
5773 ..
5774 } = &mut *ws;
5775 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5778 let refk = *REFK
5779 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5780 if refk {
5781 let lq = *local_q_dim;
5782 let lkv = *local_kv_dim;
5783 let mut hrow = engine.uninit(in_f)?;
5784 let mut qr = engine.uninit(lq)?;
5785 let mut kr = engine.uninit(lkv)?;
5786 let mut vr = engine.uninit(lkv)?;
5787 let mut gr = engine.uninit(out_g.max(1))?;
5788 for c in 0..t {
5789 {
5790 let mut dst = hrow.slice_mut(0..in_f);
5791 engine.stream().memcpy_dtod(
5792 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5793 &mut dst,
5794 )?;
5795 }
5796 engine.matvec_bf16_qkvg_into(
5797 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5798 lq, lkv, out_g,
5799 )?;
5800 let stream = engine.stream();
5801 {
5802 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5803 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5804 }
5805 {
5806 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5807 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5808 }
5809 {
5810 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5811 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5812 }
5813 if out_g > 0 {
5814 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5815 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5816 }
5817 }
5818 } else if crate::step_tp_w8_on()
5819 && q_m.ranks[rank].q8.is_some()
5820 && k_m.ranks[rank].q8.is_some()
5821 && v_m.ranks[rank].q8.is_some()
5822 && in_f % 32 == 0
5823 {
5824 if *w8t_in != in_f || *w8t_cap < t || w8t_aq.len() != ranks {
5830 w8t_aq.clear();
5831 w8t_ad.clear();
5832 for e_rank in &self.ranks {
5833 let _m = e_rank.gpu.enter_main()?;
5834 w8t_aq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
5835 w8t_ad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
5836 }
5837 *w8t_in = in_f;
5838 *w8t_cap = 32;
5839 }
5840 engine.quantize_q8_1_into(
5841 &tcol_in[rank],
5842 t,
5843 in_f,
5844 &mut w8t_aq[rank],
5845 &mut w8t_ad[rank],
5846 )?;
5847 engine.qmatvec_q8_0_qkv_rp_t_into(
5848 q_m.ranks[rank].q8.as_ref().unwrap(),
5849 k_m.ranks[rank].q8.as_ref().unwrap(),
5850 v_m.ranks[rank].q8.as_ref().unwrap(),
5851 &w8t_aq[rank],
5852 &w8t_ad[rank],
5853 &mut tcol_q[rank],
5854 &mut tcol_k[rank],
5855 &mut tcol_v[rank],
5856 in_f,
5857 *local_q_dim,
5858 *local_kv_dim,
5859 t,
5860 )?;
5861 if out_g > 0 {
5862 engine.matvec_bf16_rows_into(
5863 wg,
5864 &tcol_in[rank],
5865 &mut tcol_g[rank],
5866 in_f,
5867 out_g,
5868 t,
5869 )?;
5870 }
5871 } else {
5872 engine.matvec_bf16_qkvg_tcol_into(
5873 wq,
5874 wk,
5875 wv,
5876 wg,
5877 &tcol_in[rank],
5878 &mut tcol_q[rank],
5879 &mut tcol_k[rank],
5880 &mut tcol_v[rank],
5881 &mut tcol_g[rank],
5882 in_f,
5883 *local_q_dim,
5884 *local_kv_dim,
5885 out_g,
5886 t,
5887 )?;
5888 }
5889 }
5890 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5891 }
5892 }
5893 Ok(())
5894 }
5895
5896 pub(crate) fn decode_v2_oproj_tcol_eligible(
5900 &self,
5901 ws: &StepTpDecodeV2Ws,
5902 o_m: &ResidentStepBf16RowParallel,
5903 ) -> bool {
5904 self.ranks.len() == 2
5905 && ws.blocks_per_rank == 4
5906 && step_tp_qkv_fused_enabled().unwrap_or(false)
5907 && no_local_shadow_on()
5908 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5909 && o_m
5910 .ranks
5911 .iter()
5912 .flatten()
5913 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5914 }
5915
5916 pub(crate) fn decode_v2_stash_fa2(
5921 &self,
5922 ws: &mut StepTpDecodeV2Ws,
5923 e: &Engine,
5924 col: usize,
5925 ) -> Result<(), Box<dyn std::error::Error>> {
5926 let ranks = self.ranks.len();
5927 if col >= 32 {
5928 return Err("decode_v2_stash_fa2 column out of range".into());
5929 }
5930 let lq = ws.local_q_dim;
5931 let lg = (ws.heads / ranks).max(1);
5932 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
5933 ws.fa2_q.clear();
5934 ws.fa2_gate.clear();
5935 ws.fa2_gated.clear();
5936 ws.rope_k_t.clear();
5937 ws.rope_ctr_t.clear();
5938 ws.rope_pos_t.clear();
5939 ws.rows_tab_t.clear();
5940 for engine in &self.ranks {
5941 let _m = engine.gpu.enter_main()?;
5942 ws.fa2_q.push(engine.uninit(32 * lq)?);
5943 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5944 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5945 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
5946 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5947 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5948 ws.rows_tab_t
5949 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
5950 }
5951 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5952 ws.fa2_cap = 32;
5953 }
5954 for rank in 0..ranks {
5955 let engine = &self.ranks[rank];
5956 let _main = engine.gpu.enter_main()?;
5957 {
5958 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5959 engine
5960 .stream()
5961 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5962 }
5963 {
5964 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5965 engine
5966 .stream()
5967 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5968 }
5969 ws.ev_rank[rank].record(&engine.stream())?;
5970 }
5971 {
5972 let _main = e.gpu.enter_main()?;
5973 for ev in ws.ev_rank.iter() {
5974 e.stream().wait(ev)?;
5975 }
5976 }
5977 Ok(())
5978 }
5979
5980 #[allow(clippy::too_many_arguments)]
5987 pub(crate) fn decode_v2_spec_fa2_join(
5988 &self,
5989 ws_index: usize,
5990 e: &Engine,
5991 o_m: &ResidentStepBf16RowParallel,
5992 kv: &ResidentTpKvCache,
5993 head_dim: usize,
5994 window: usize,
5995 bucket_max: usize,
5996 scale: f32,
5997 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5998 let ranks = self.ranks.len();
5999 static ONCE: std::sync::Once = std::sync::Once::new();
6001 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
6002 {
6003 let mut guard = self
6004 .decode_v2
6005 .lock()
6006 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6007 let ws = guard
6008 .get_mut(ws_index)
6009 .ok_or("step TP decode v2 workspace index out of range")?;
6010 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
6011 return Err("spec fa2 join without stashed columns".into());
6012 }
6013 let lq = ws.local_q_dim;
6014 let local_heads = (ws.heads / ranks).max(1);
6015 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6016 let capacity = kv.physical_capacity();
6017 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
6018 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
6020 ws.tcol_gated.clear();
6021 ws.tcol_opart.clear();
6022 for engine in &self.ranks {
6023 let _m = engine.gpu.enter_main()?;
6024 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6025 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6026 }
6027 let root = &self.ranks[0];
6028 let _m = root.gpu.enter_main()?;
6029 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6030 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6031 ws.tcol_ocap = 32;
6032 }
6033 for rank in 0..ranks {
6034 let engine = &self.ranks[rank];
6035 let _main = engine.gpu.enter_main()?;
6036 let rank_cache = kv
6037 .rank(rank)
6038 .ok_or("spec fa2 join lost its KV cache rank")?;
6039 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
6040 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
6041 {
6042 let StepTpDecodeV2Ws {
6043 fa2_q,
6044 fa2_gate,
6045 fa2_gated,
6046 ..
6047 } = &mut *ws;
6048 engine.fa_decode_dcw2(
6049 &fa2_q[rank],
6050 &k_ring,
6051 &v_ring,
6052 &mut fa2_gated[rank],
6053 head_dim,
6054 local_heads,
6055 local_kv_heads,
6056 rank_cache.len_d(),
6057 rank_cache.base_d(),
6058 window,
6059 bucket_max,
6060 scale,
6061 k_tok_bytes,
6062 v_tok_bytes,
6063 &fa2_gate[rank],
6064 )?;
6065 }
6066 let StepTpDecodeV2Ws {
6069 fa2_gated,
6070 tcol_gated,
6071 ..
6072 } = &mut *ws;
6073 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
6074 engine
6075 .stream()
6076 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
6077 }
6078 }
6079 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
6080 }
6081
6082 #[allow(clippy::too_many_arguments)]
6092 pub(crate) fn decode_v2_rope_fa_rows(
6093 &self,
6094 ws_index: usize,
6095 e: &Engine,
6096 o_m: &ResidentStepBf16RowParallel,
6097 session_parts: &[Vec<[u64; 4]>],
6098 tab_keys: &[u64],
6099 positions: &[i32],
6100 stage_pos: bool,
6101 same_session: bool,
6102 q_norms: &[CudaSlice<f32>],
6103 k_norms: &[CudaSlice<f32>],
6104 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
6105 t: usize,
6106 head_dim: usize,
6107 n_rot: usize,
6108 window: usize,
6109 max_ns: usize,
6110 scale: f32,
6111 k_tok_bytes: usize,
6112 v_tok_bytes: usize,
6113 eps: f32,
6114 rope_base: f32,
6115 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6116 use cudarc::driver::DevicePtr;
6117 let ranks = self.ranks.len();
6118 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
6119 return Err("rope fa rows geometry".into());
6120 }
6121 {
6122 let mut guard = self
6123 .decode_v2
6124 .lock()
6125 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6126 let ws = guard
6127 .get_mut(ws_index)
6128 .ok_or("step TP decode v2 workspace index out of range")?;
6129 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
6130 return Err("rope fa rows without tcol slabs".into());
6131 }
6132 let lq = ws.local_q_dim;
6133 let lkv = ws.local_kv_dim;
6134 let lg = (ws.heads / ranks).max(1);
6135 let local_heads = (ws.heads / ranks).max(1);
6136 let local_kv_heads = (lkv / head_dim).max(1);
6137 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks || ws.rows_tab_t.len() != ranks {
6139 ws.fa2_q.clear();
6140 ws.fa2_gate.clear();
6141 ws.fa2_gated.clear();
6142 ws.rope_k_t.clear();
6143 ws.rope_ctr_t.clear();
6144 ws.rope_pos_t.clear();
6145 ws.rows_tab_t.clear();
6146 for engine in &self.ranks {
6147 let _m = engine.gpu.enter_main()?;
6148 ws.fa2_q.push(engine.uninit(32 * lq)?);
6149 ws.fa2_gate.push(engine.uninit(32 * lg)?);
6150 ws.fa2_gated.push(engine.uninit(32 * lq)?);
6151 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
6152 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
6153 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
6154 ws.rows_tab_t
6155 .push(engine.stream().clone_htod(&[0u64; 32 * 6])?);
6156 }
6157 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
6158 ws.fa2_cap = 32;
6159 }
6160 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6161 ws.tcol_gated.clear();
6162 ws.tcol_opart.clear();
6163 for engine in &self.ranks {
6164 let _m = engine.gpu.enter_main()?;
6165 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6166 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6167 }
6168 let root = &self.ranks[0];
6169 let _m = root.gpu.enter_main()?;
6170 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6171 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6172 ws.tcol_ocap = 32;
6173 }
6174 for rank in 0..ranks {
6175 let engine = &self.ranks[rank];
6176 let _main = engine.gpu.enter_main()?;
6177 if stage_pos {
6178 let host: Vec<i32> = positions[..t].to_vec();
6179 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
6180 engine.stream().memcpy_htod(&host, &mut view)?;
6181 }
6182 let ctr_base = {
6201 let s = engine.stream();
6202 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6203 p as u64
6204 };
6205 let host = rows_tab_host(&session_parts[rank], ctr_base, same_session, t);
6206 if rows_tab_stale_scan() {
6212 if ws.rows_tab_shadow.len() != ranks {
6213 ws.rows_tab_shadow = (0..ranks).map(|_| Default::default()).collect();
6214 }
6215 let n = ROWS_TAB_ENGAGED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6216 if let Some(prev) = ws.rows_tab_shadow[rank].get(&tab_keys[rank]) {
6217 if prev != &host {
6218 let words = ["k", "v", "len", "base", "ctr", "back"];
6219 let moved: Vec<String> = (0..host.len())
6220 .filter(|&i| prev.get(i) != Some(&host[i]))
6221 .map(|i| format!("{}[row{}]", words[i % 6], i / 6))
6222 .collect();
6223 let stale =
6224 ROWS_TAB_STALE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6225 eprintln!(
6226 "[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",
6227 tab_keys[rank],
6228 moved.join(",")
6229 );
6230 }
6231 }
6232 ws.rows_tab_shadow[rank].insert(tab_keys[rank], host.clone());
6233 }
6234 let legacy_memo = !rows_tab_restage_on();
6235 if legacy_memo && !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6236 let tab = engine.stream().clone_htod(&host)?;
6237 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6238 }
6239 if !legacy_memo {
6240 let mut view = ws.rows_tab_t[rank].slice_mut(0..t * 6);
6241 engine.stream().memcpy_htod(&host, &mut view)?;
6242 }
6243 let StepTpDecodeV2Ws {
6244 tcol_q,
6245 tcol_k,
6246 tcol_v,
6247 tcol_g,
6248 fa2_q,
6249 fa2_gated,
6250 rope_k_t,
6251 rope_pos_t,
6252 rows_tabs,
6253 rows_tab_t,
6254 ..
6255 } = &mut *ws;
6256 let tab = if legacy_memo {
6257 rows_tabs[rank]
6258 .get(&tab_keys[rank])
6259 .ok_or("rows tab memo lost its entry")?
6260 } else {
6261 &rows_tab_t[rank]
6262 };
6263 engine.qk_norm_rope_append_inc_dcw_rows(
6264 &tcol_q[rank],
6265 &tcol_k[rank],
6266 &tcol_v[rank],
6267 &q_norms[rank],
6268 &k_norms[rank],
6269 &mut fa2_q[rank],
6270 &mut rope_k_t[rank],
6271 tab,
6272 &rope_pos_t[rank],
6273 same_session,
6274 t,
6275 lkv,
6276 lkv,
6277 k_tok_bytes,
6278 v_tok_bytes,
6279 head_dim,
6280 n_rot,
6281 local_heads,
6282 local_kv_heads,
6283 eps,
6284 rope_base,
6285 1.0,
6286 rope_freqs[rank],
6287 )?;
6288 engine.fa_decode_dcw_rows(
6289 &fa2_q[rank],
6290 tab,
6291 &mut fa2_gated[rank],
6292 t,
6293 head_dim,
6294 local_heads,
6295 local_kv_heads,
6296 window,
6297 max_ns,
6298 scale,
6299 k_tok_bytes,
6300 v_tok_bytes,
6301 &tcol_g[rank],
6302 )?;
6303 let StepTpDecodeV2Ws {
6304 fa2_gated,
6305 tcol_gated,
6306 ..
6307 } = &mut *ws;
6308 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6309 engine
6310 .stream()
6311 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6312 }
6313 }
6314 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6315 }
6316
6317 #[allow(clippy::too_many_arguments)]
6324 pub(crate) fn decode_v2_fa_rows_join(
6325 &self,
6326 ws_index: usize,
6327 e: &Engine,
6328 o_m: &ResidentStepBf16RowParallel,
6329 tabs: &[&crate::CudaSlice<u64>],
6330 t: usize,
6331 head_dim: usize,
6332 window: usize,
6333 max_ns: usize,
6334 scale: f32,
6335 k_tok_bytes: usize,
6336 v_tok_bytes: usize,
6337 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6338 let ranks = self.ranks.len();
6339 if tabs.len() != ranks {
6340 return Err("fa rows join needs one table per rank".into());
6341 }
6342 {
6343 let mut guard = self
6344 .decode_v2
6345 .lock()
6346 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6347 let ws = guard
6348 .get_mut(ws_index)
6349 .ok_or("step TP decode v2 workspace index out of range")?;
6350 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6351 return Err("fa rows join without stashed rows".into());
6352 }
6353 let lq = ws.local_q_dim;
6354 let local_heads = (ws.heads / ranks).max(1);
6355 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6356 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6357 ws.tcol_gated.clear();
6358 ws.tcol_opart.clear();
6359 for engine in &self.ranks {
6360 let _m = engine.gpu.enter_main()?;
6361 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6362 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6363 }
6364 let root = &self.ranks[0];
6365 let _m = root.gpu.enter_main()?;
6366 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6367 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6368 ws.tcol_ocap = 32;
6369 }
6370 for rank in 0..ranks {
6371 let engine = &self.ranks[rank];
6372 let _main = engine.gpu.enter_main()?;
6373 {
6374 let StepTpDecodeV2Ws {
6375 fa2_q,
6376 fa2_gate,
6377 fa2_gated,
6378 ..
6379 } = &mut *ws;
6380 engine.fa_decode_dcw_rows(
6381 &fa2_q[rank],
6382 tabs[rank],
6383 &mut fa2_gated[rank],
6384 t,
6385 head_dim,
6386 local_heads,
6387 local_kv_heads,
6388 window,
6389 max_ns,
6390 scale,
6391 k_tok_bytes,
6392 v_tok_bytes,
6393 &fa2_gate[rank],
6394 )?;
6395 }
6396 let StepTpDecodeV2Ws {
6397 fa2_gated,
6398 tcol_gated,
6399 ..
6400 } = &mut *ws;
6401 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6402 engine
6403 .stream()
6404 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6405 }
6406 }
6407 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6408 }
6409
6410 pub(crate) fn decode_v2_stash_gated(
6415 &self,
6416 ws: &mut StepTpDecodeV2Ws,
6417 e: &Engine,
6418 col: usize,
6419 ) -> Result<(), Box<dyn std::error::Error>> {
6420 let ranks = self.ranks.len();
6421 if col >= 32 {
6425 return Err("decode_v2_stash_gated column out of range".into());
6426 }
6427 let lq = ws.local_q_dim;
6428 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6429 ws.tcol_gated.clear();
6430 ws.tcol_opart.clear();
6431 for engine in &self.ranks {
6432 let _m = engine.gpu.enter_main()?;
6433 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6434 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6435 }
6436 let root = &self.ranks[0];
6437 let _m = root.gpu.enter_main()?;
6438 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6439 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6440 ws.tcol_ocap = 32;
6441 }
6442 for rank in 0..ranks {
6443 let engine = &self.ranks[rank];
6444 let _main = engine.gpu.enter_main()?;
6445 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6446 engine
6447 .stream()
6448 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6449 ws.ev_rank[rank].record(&engine.stream())?;
6453 }
6454 {
6455 let _main = e.gpu.enter_main()?;
6456 for ev in ws.ev_rank.iter() {
6457 e.stream().wait(ev)?;
6458 }
6459 }
6460 Ok(())
6461 }
6462
6463 pub(crate) fn decode_v2_oproj_tcol(
6469 &self,
6470 ws_index: usize,
6471 e: &Engine,
6472 o_m: &ResidentStepBf16RowParallel,
6473 t: usize,
6474 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6475 let ranks = self.ranks.len();
6476 let mut guard = self
6477 .decode_v2
6478 .lock()
6479 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6480 let ws = guard
6481 .get_mut(ws_index)
6482 .ok_or("step TP decode v2 workspace index out of range")?;
6483 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6484 return Err("decode_v2_oproj_tcol geometry".into());
6485 }
6486 for rank in 0..ranks {
6487 let engine = &self.ranks[rank];
6488 let _main = engine.gpu.enter_main()?;
6489 let mut weights = Vec::with_capacity(4);
6490 for block in 0..4 {
6491 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6492 return Err("tcol o_proj requires bf16-resident O blocks".into());
6493 };
6494 weights.push(weight);
6495 }
6496 {
6497 let StepTpDecodeV2Ws {
6498 tcol_gated,
6499 tcol_opart,
6500 local_q_dim,
6501 o_block_cols,
6502 o_out,
6503 w8t_oaq,
6504 w8t_oad,
6505 w8t_oin,
6506 w8t_cap,
6507 ..
6508 } = &mut *ws;
6509 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6512 let refk = *REFK
6513 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6514 if refk {
6515 let lq = *local_q_dim;
6516 let mut xr = engine.uninit(lq)?;
6517 let mut yr = engine.uninit(*o_out)?;
6518 for c in 0..t {
6519 {
6520 let mut dst = xr.slice_mut(0..lq);
6521 engine.stream().memcpy_dtod(
6522 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6523 &mut dst,
6524 )?;
6525 }
6526 engine.matvec_bf16_b4_into(
6527 [weights[0], weights[1], weights[2], weights[3]],
6528 &xr,
6529 &mut yr,
6530 *o_block_cols,
6531 *o_out,
6532 )?;
6533 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6534 engine
6535 .stream()
6536 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6537 }
6538 } else if crate::step_tp_w8_on()
6539 && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some())
6540 && (4 * *o_block_cols) % 32 == 0
6541 {
6542 let in_f = 4 * *o_block_cols;
6546 if *w8t_oin != in_f || *w8t_cap < t || w8t_oaq.len() != ranks {
6547 w8t_oaq.clear();
6548 w8t_oad.clear();
6549 for e_rank in &self.ranks {
6550 let _m = e_rank.gpu.enter_main()?;
6551 w8t_oaq.push(e_rank.alloc_i8_uninit(32 * in_f)?);
6552 w8t_oad.push(e_rank.alloc_uninit::<f32>(32 * (in_f / 32))?);
6553 }
6554 *w8t_oin = in_f;
6555 *w8t_cap = (*w8t_cap).max(32);
6556 }
6557 engine.quantize_q8_1_into(
6558 &tcol_gated[rank],
6559 t,
6560 in_f,
6561 &mut w8t_oaq[rank],
6562 &mut w8t_oad[rank],
6563 )?;
6564 engine.qmatvec_q8_0_b4_rp_t_into(
6565 [
6566 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6567 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6568 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6569 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6570 ],
6571 &w8t_oaq[rank],
6572 &w8t_oad[rank],
6573 &mut tcol_opart[rank],
6574 *o_block_cols,
6575 *o_out,
6576 t,
6577 )?;
6578 } else {
6579 engine.matvec_bf16_b4_tcol_into(
6580 [weights[0], weights[1], weights[2], weights[3]],
6581 &tcol_gated[rank],
6582 &mut tcol_opart[rank],
6583 *o_block_cols,
6584 *o_out,
6585 t,
6586 )?;
6587 }
6588 }
6589 if rank != 0 {
6590 ws.ev_rank[rank].record(&engine.stream())?;
6591 }
6592 }
6593 let root = &self.ranks[0];
6594 {
6595 let _main = root.gpu.enter_main()?;
6596 for ev in ws.ev_rank.iter().skip(1) {
6597 root.stream().wait(ev)?;
6598 }
6599 {
6600 let StepTpDecodeV2Ws {
6601 tcol_opart,
6602 tcol_opeer,
6603 tcol_omix,
6604 o_out,
6605 ..
6606 } = &mut *ws;
6607 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6608 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6609 {
6610 let mut dst = opeer.slice_mut(0..t * *o_out);
6611 root.stream()
6612 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6613 }
6614 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6617 }
6618 ws.ev_oproj.record(&root.stream())?;
6619 }
6620 let _main = e.gpu.enter_main()?;
6621 e.stream().wait(&ws.ev_oproj)?;
6622 let mut out = e.uninit(t * ws.o_out)?;
6623 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6624 e.stream().memcpy_dtod(
6625 &omix.slice(0..t * ws.o_out),
6626 &mut out.slice_mut(0..t * ws.o_out),
6627 )?;
6628 Ok(out)
6629 }
6630
6631 pub(crate) fn decode_v2_input_qkv(
6632 &self,
6633 ws: &mut StepTpDecodeV2Ws,
6634 e: &Engine,
6635 h: &CudaSlice<f32>,
6636 pos_d: &CudaSlice<i32>,
6637 gate_raw: Option<&CudaSlice<f32>>,
6638 gate_shards: Option<StepTpGateShards<'_>>,
6639 decode_input: &mut ResidentReplicatedDeviceRows,
6640 q_m: &ResidentBf16ColumnParallel,
6641 k_m: &ResidentBf16ColumnParallel,
6642 v_m: &ResidentBf16ColumnParallel,
6643 q_norm: &[CudaSlice<f32>],
6644 k_norm: &[CudaSlice<f32>],
6645 head_dim: usize,
6646 n_rot: usize,
6647 rope_base: f32,
6648 rope_freqs: &[Option<&CudaSlice<f32>>],
6649 rms_eps: f32,
6650 defer_norm_rope: bool,
6651 tcol_col: Option<usize>,
6652 ) -> Result<(), Box<dyn std::error::Error>> {
6653 let ranks = self.ranks.len();
6654 validate_replicated_device_rows(&self.ranks, decode_input)?;
6655 if decode_input.tokens != 1
6656 || decode_input.width != q_m.in_features
6657 || pos_d.len() != 1
6658 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6659 || gate_raw.is_none() != gate_shards.is_some()
6660 || gate_shards.as_ref().is_some_and(|shards| match shards {
6661 StepTpGateShards::F32(shards) => shards.len() != ranks,
6662 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6663 })
6664 || q_norm.len() != ranks
6665 || k_norm.len() != ranks
6666 || rope_freqs.len() != ranks
6667 || e.ctx().ordinal() != ws.e_device
6668 {
6669 return Err("step TP decode v2 input geometry mismatch".into());
6670 }
6671
6672 let qkv_fused = step_tp_qkv_fused_enabled()?;
6673 if gate_shards.is_some() && !qkv_fused {
6674 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6675 }
6676 let values = decode_input.width;
6677 if h.len() != values {
6678 return Err(format!(
6679 "step TP decode v2 hidden width {} != replicated width {values}",
6680 h.len()
6681 )
6682 .into());
6683 }
6684
6685 if qkv_fused {
6686 if ws.h_stage.is_none() {
6690 use cudarc::driver::DevicePtr;
6691 let _main = e.gpu.enter_main()?;
6692 let h_stage = e.uninit(values)?;
6693 let pos_stage = e.htod_i32(&[0])?;
6694 {
6695 let stream = e.stream();
6696 let (hp, _g0) = h_stage.device_ptr(&stream);
6697 let (pp, _g1) = pos_stage.device_ptr(&stream);
6698 ws.raw_h_stage = hp as u64;
6699 ws.raw_pos_stage = pp as u64;
6700 }
6701 ws.h_stage = Some(h_stage);
6702 ws.pos_stage = Some(pos_stage);
6703 for rank in 0..ranks {
6704 use cudarc::driver::DevicePtr;
6705 let engine = &self.ranks[rank];
6706 let _rmain = engine.gpu.enter_main()?;
6707 let attn_in = engine.uninit(values)?;
6708 let (dp, pp) = {
6709 let stream = engine.stream();
6710 let (dp, _g2) = attn_in.device_ptr(&stream);
6711 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6712 (dp as u64, pp as u64)
6713 };
6714 ws.raw_attn_in.push(dp);
6715 ws.raw_pos.push(pp);
6716 ws.attn_in.push(attn_in);
6717 }
6718 {
6719 use cudarc::driver::DevicePtr;
6720 let root = &self.ranks[0];
6721 let _rmain = root.gpu.enter_main()?;
6722 let stream = root.stream();
6723 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6724 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6725 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6726 ws.raw_peer_partial = a as u64;
6727 ws.raw_k_shadow = b as u64;
6728 ws.raw_v_shadow = c as u64;
6729 }
6730 {
6731 use cudarc::driver::DevicePtr;
6732 let rank1 = &self.ranks[1];
6733 let _rmain = rank1.gpu.enter_main()?;
6734 let stream = rank1.stream();
6735 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6736 let (b, _g) = ws.k[1].device_ptr(&stream);
6737 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6738 ws.raw_o_partial1 = a as u64;
6739 ws.raw_k1 = b as u64;
6740 ws.raw_v1 = c as u64;
6741 }
6742 }
6743 {
6744 let _main = e.gpu.enter_main()?;
6745 {
6746 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6749 let mut dst = h_stage.slice_mut(0..values);
6750 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6751 }
6752 {
6753 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6754 let mut dst = pos_stage.slice_mut(0..1);
6755 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6756 }
6757 ws.ev_entry.record(&e.stream())?;
6758 }
6759 for rank in 0..ranks {
6760 let engine = &self.ranks[rank];
6761 let _main = engine.gpu.enter_main()?;
6762 engine.stream().wait(&ws.ev_entry)?;
6763 }
6764 } else {
6765 {
6767 let _main = e.gpu.enter_main()?;
6768 if let Some(gate_raw) = gate_raw {
6769 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6770 e.stream()
6771 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6772 }
6773 ws.ev_entry.record(&e.stream())?;
6774 }
6775 {
6776 let root = &self.ranks[0];
6777 let _main = root.gpu.enter_main()?;
6778 root.stream().wait(&ws.ev_entry)?;
6779 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6780 root.stream()
6781 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6782 ws.ev_refresh.record(&root.stream())?;
6783 }
6784 for rank in 1..ranks {
6785 let engine = &self.ranks[rank];
6786 let _main = engine.gpu.enter_main()?;
6787 engine.stream().wait(&ws.ev_refresh)?;
6788 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6789 let mut destination = peer_rows[0].slice_mut(0..values);
6790 engine
6791 .stream()
6792 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6793 }
6794 }
6795 for rank in 0..ranks {
6796 self.decode_v2_input_qkv_rank(
6797 ws,
6798 pos_d,
6799 decode_input,
6800 q_m,
6801 k_m,
6802 v_m,
6803 q_norm,
6804 k_norm,
6805 head_dim,
6806 n_rot,
6807 rope_base,
6808 rope_freqs,
6809 rms_eps,
6810 gate_shards.as_ref(),
6811 qkv_fused,
6812 defer_norm_rope,
6813 rank,
6814 tcol_col,
6815 )?;
6816 }
6817 Ok(())
6818 }
6819
6820 #[allow(clippy::too_many_arguments)]
6823 pub(crate) fn decode_v2_input_qkv_rank(
6824 &self,
6825 ws: &mut StepTpDecodeV2Ws,
6826 pos_d: &CudaSlice<i32>,
6827 decode_input: &mut ResidentReplicatedDeviceRows,
6828 q_m: &ResidentBf16ColumnParallel,
6829 k_m: &ResidentBf16ColumnParallel,
6830 v_m: &ResidentBf16ColumnParallel,
6831 q_norm: &[CudaSlice<f32>],
6832 k_norm: &[CudaSlice<f32>],
6833 head_dim: usize,
6834 n_rot: usize,
6835 rope_base: f32,
6836 rope_freqs: &[Option<&CudaSlice<f32>>],
6837 rms_eps: f32,
6838 gate_shards: Option<&StepTpGateShards<'_>>,
6839 qkv_fused: bool,
6840 defer_norm_rope: bool,
6841 rank: usize,
6842 tcol_col: Option<usize>,
6843 ) -> Result<(), Box<dyn std::error::Error>> {
6844 let ranks = self.ranks.len();
6845 let local_heads = ws.local_q_dim / head_dim;
6846 let local_kv_heads = ws.local_kv_dim / head_dim;
6847 let engine = &self.ranks[rank];
6848 let _main = engine.gpu.enter_main()?;
6849 let ws_e_device = ws.e_device;
6850 if qkv_fused && tcol_col.is_some() {
6855 let c = tcol_col.expect("checked");
6856 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6857 return Err("tcol select without precompute".into());
6858 }
6859 if engine.ctx().ordinal() != ws_e_device {
6863 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6864 }
6865 let StepTpDecodeV2Ws {
6866 tcol_q,
6867 tcol_k,
6868 tcol_v,
6869 tcol_g,
6870 q_raw,
6871 k_raw,
6872 v_raw,
6873 gate,
6874 local_q_dim,
6875 local_kv_dim,
6876 heads,
6877 ..
6878 } = &mut *ws;
6879 let lg = *heads / ranks;
6880 let stream = engine.stream();
6881 {
6882 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6883 stream.memcpy_dtod(
6884 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6885 &mut dst,
6886 )?;
6887 }
6888 {
6889 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6890 stream.memcpy_dtod(
6891 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6892 &mut dst,
6893 )?;
6894 }
6895 {
6896 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6897 stream.memcpy_dtod(
6898 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6899 &mut dst,
6900 )?;
6901 }
6902 if lg > 0 {
6903 let mut dst = gate[rank].slice_mut(0..lg);
6904 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6905 }
6906 if !defer_norm_rope {
6907 } else {
6911 return Ok(());
6912 }
6913 }
6914 if qkv_fused {
6915 let same_dev = engine.ctx().ordinal() == ws.e_device;
6920 if !same_dev {
6921 raw_copy_bytes(
6922 ws.raw_attn_in[rank],
6923 ws.raw_h_stage,
6924 q_m.in_features * 4,
6925 engine,
6926 )?;
6927 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6928 }
6929 let StepTpDecodeV2Ws {
6930 q_raw,
6931 k_raw,
6932 v_raw,
6933 gate,
6934 gate_e,
6935 attn_in,
6936 h_stage,
6937 heads,
6938 local_q_dim,
6939 local_kv_dim,
6940 w8_aq,
6941 w8_ad,
6942 w8_in,
6943 ..
6944 } = &mut *ws;
6945 let input_ref: &CudaSlice<f32> = if same_dev {
6946 h_stage
6947 .as_ref()
6948 .ok_or("step TP decode v2 stage not armed")?
6949 } else {
6950 &attn_in[rank]
6951 };
6952 match (
6953 &q_m.ranks[rank].weight,
6954 &k_m.ranks[rank].weight,
6955 &v_m.ranks[rank].weight,
6956 ) {
6957 (
6958 ResidentBf16Weight::F32(wq),
6959 ResidentBf16Weight::F32(wk),
6960 ResidentBf16Weight::F32(wv),
6961 ) => {
6962 let (wg, out_g) = match &gate_shards {
6963 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6964 Some(StepTpGateShards::Bf16(_)) => {
6965 return Err("step TP decode v2 gate shard class does not \
6966 match the F32 projections"
6967 .into());
6968 }
6969 None => (&*gate_e, 0),
6971 };
6972 engine.matvec_f32_qkv_into(
6973 wq,
6974 wk,
6975 wv,
6976 wg,
6977 input_ref,
6978 &mut q_raw[rank],
6979 &mut k_raw[rank],
6980 &mut v_raw[rank],
6981 &mut gate[rank],
6982 q_m.in_features,
6983 *local_q_dim,
6984 *local_kv_dim,
6985 out_g,
6986 )?;
6987 }
6988 (
6989 ResidentBf16Weight::Bf16(wq),
6990 ResidentBf16Weight::Bf16(wk),
6991 ResidentBf16Weight::Bf16(wv),
6992 ) => {
6993 let (wg, out_g) = match &gate_shards {
6994 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6995 Some(StepTpGateShards::F32(_)) => {
6996 return Err("step TP decode v2 gate shard class does not \
6997 match the bf16 projections"
6998 .into());
6999 }
7000 None => (wq, 0),
7001 };
7002 let in_f = q_m.in_features;
7009 let q8_ready = crate::step_tp_w8_on()
7010 && q_m.ranks[rank].q8.is_some()
7011 && k_m.ranks[rank].q8.is_some()
7012 && v_m.ranks[rank].q8.is_some();
7013 if q8_ready {
7014 if *w8_in != in_f || w8_aq.len() != ranks {
7015 w8_aq.clear();
7016 w8_ad.clear();
7017 for e_rank in &self.ranks {
7018 let _m = e_rank.gpu.enter_main()?;
7019 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7020 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7021 }
7022 *w8_in = in_f;
7023 }
7024 engine.quantize_q8_1_into(
7025 input_ref,
7026 1,
7027 in_f,
7028 &mut w8_aq[rank],
7029 &mut w8_ad[rank],
7030 )?;
7031 engine.qmatvec_q8_0_qkv_rp_into(
7036 q_m.ranks[rank].q8.as_ref().unwrap(),
7037 k_m.ranks[rank].q8.as_ref().unwrap(),
7038 v_m.ranks[rank].q8.as_ref().unwrap(),
7039 &w8_aq[rank],
7040 &w8_ad[rank],
7041 &mut q_raw[rank],
7042 &mut k_raw[rank],
7043 &mut v_raw[rank],
7044 in_f,
7045 *local_q_dim,
7046 *local_kv_dim,
7047 )?;
7048 if out_g > 0 {
7049 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
7050 }
7051 } else {
7052 engine.matvec_bf16_qkvg_into(
7053 wq,
7054 wk,
7055 wv,
7056 wg,
7057 input_ref,
7058 &mut q_raw[rank],
7059 &mut k_raw[rank],
7060 &mut v_raw[rank],
7061 &mut gate[rank],
7062 q_m.in_features,
7063 *local_q_dim,
7064 *local_kv_dim,
7065 out_g,
7066 )?;
7067 }
7068 }
7069 _ => {
7070 return Err("step TP decode v2 QKV projections mix residency classes".into());
7071 }
7072 }
7073 } else {
7074 for (matrix, local_out, raw) in [
7075 (q_m, ws.local_q_dim, &mut ws.q_raw),
7076 (k_m, ws.local_kv_dim, &mut ws.k_raw),
7077 (v_m, ws.local_kv_dim, &mut ws.v_raw),
7078 ] {
7079 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
7080 return Err("step TP decode v2 lost its F32 projection residency".into());
7081 };
7082 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
7083 engine.linear_f32_resident_canonical_rows_t1_into(
7084 &decode_input.ranks[rank],
7085 values_w,
7086 &mut raw[rank],
7087 matrix.in_features,
7088 local_out,
7089 chunk_rows,
7090 )?;
7091 }
7092 }
7093 if qkv_fused && defer_norm_rope {
7094 } else if qkv_fused {
7096 let StepTpDecodeV2Ws {
7099 q_raw,
7100 k_raw,
7101 q,
7102 k,
7103 pos,
7104 pos_stage,
7105 ..
7106 } = &mut *ws;
7107 let same_dev = engine.ctx().ordinal() == ws_e_device;
7108 let pos_ref: &CudaSlice<i32> = if same_dev {
7109 pos_stage
7110 .as_ref()
7111 .ok_or("step TP decode v2 pos stage not armed")?
7112 } else {
7113 &pos[rank]
7114 };
7115 engine.qk_norm_rope_into(
7116 &q_raw[rank],
7117 &k_raw[rank],
7118 &q_norm[rank],
7119 &k_norm[rank],
7120 &mut q[rank],
7121 &mut k[rank],
7122 pos_ref,
7123 head_dim,
7124 n_rot,
7125 local_heads,
7126 local_kv_heads,
7127 rms_eps,
7128 rope_base,
7129 1.0,
7130 rope_freqs[rank],
7131 )?;
7132 } else {
7133 engine.rms_norm(
7134 &ws.q_raw[rank],
7135 &q_norm[rank],
7136 &mut ws.q[rank],
7137 head_dim,
7138 local_heads,
7139 rms_eps,
7140 )?;
7141 engine.rms_norm(
7142 &ws.k_raw[rank],
7143 &k_norm[rank],
7144 &mut ws.k[rank],
7145 head_dim,
7146 local_kv_heads,
7147 rms_eps,
7148 )?;
7149 {
7150 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
7151 engine
7152 .stream()
7153 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
7154 }
7155 engine.rope_neox2(
7156 &mut ws.q[rank],
7157 &mut ws.k[rank],
7158 &ws.pos[rank],
7159 head_dim,
7160 n_rot,
7161 local_heads,
7162 local_kv_heads,
7163 1,
7164 rope_base,
7165 1.0,
7166 rope_freqs[rank],
7167 )?;
7168 }
7169 if gate_shards.is_none() {
7170 let gate_start = rank * (ws.heads / ranks);
7171 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
7172 engine.stream().memcpy_dtod(
7173 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
7174 &mut gate_dst,
7175 )?;
7176 }
7177 Ok(())
7178 }
7179
7180 pub(crate) fn decode_v2_finish_rank_partial(
7184 &self,
7185 ws: &mut StepTpDecodeV2Ws,
7186 o_m: &ResidentStepBf16RowParallel,
7187 o_fused: bool,
7188 rank: usize,
7189 ) -> Result<(), Box<dyn std::error::Error>> {
7190 let engine = &self.ranks[rank];
7191 let _main = engine.gpu.enter_main()?;
7192 if o_fused {
7193 let StepTpDecodeV2Ws {
7194 gated,
7195 o_partials,
7196 o_block_cols,
7197 o_out,
7198 w8o_aq,
7199 w8o_ad,
7200 w8o_in,
7201 ..
7202 } = &mut *ws;
7203 let all_f32 = o_m.ranks[rank]
7204 .iter()
7205 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
7206 if all_f32 {
7207 let mut weights = Vec::with_capacity(4);
7208 for block in 0..4 {
7209 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7210 unreachable!("all_f32 checked above");
7211 };
7212 weights.push(weight);
7213 }
7214 engine.matvec_f32_b4_into(
7215 [weights[0], weights[1], weights[2], weights[3]],
7216 &gated[rank],
7217 &mut o_partials[rank][0],
7218 *o_block_cols,
7219 *o_out,
7220 )?;
7221 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
7222 let in_f = 4 * *o_block_cols;
7227 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
7228 w8o_aq.clear();
7229 w8o_ad.clear();
7230 for e_rank in &self.ranks {
7231 let _m = e_rank.gpu.enter_main()?;
7232 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
7233 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
7234 }
7235 *w8o_in = in_f;
7236 }
7237 engine.quantize_q8_1_into(
7238 &gated[rank],
7239 1,
7240 in_f,
7241 &mut w8o_aq[rank],
7242 &mut w8o_ad[rank],
7243 )?;
7244 engine.qmatvec_q8_0_b4_rp_into(
7245 [
7246 o_m.ranks[rank][0].q8.as_ref().unwrap(),
7247 o_m.ranks[rank][1].q8.as_ref().unwrap(),
7248 o_m.ranks[rank][2].q8.as_ref().unwrap(),
7249 o_m.ranks[rank][3].q8.as_ref().unwrap(),
7250 ],
7251 &w8o_aq[rank],
7252 &w8o_ad[rank],
7253 &mut o_partials[rank][0],
7254 *o_block_cols,
7255 *o_out,
7256 )?;
7257 } else {
7258 let mut weights = Vec::with_capacity(4);
7259 for block in 0..4 {
7260 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
7261 return Err("step TP decode v2 O projections mix residency classes".into());
7262 };
7263 weights.push(weight);
7264 }
7265 engine.matvec_bf16_b4_into(
7266 [weights[0], weights[1], weights[2], weights[3]],
7267 &gated[rank],
7268 &mut o_partials[rank][0],
7269 *o_block_cols,
7270 *o_out,
7271 )?;
7272 }
7273 } else {
7274 for block in 0..ws.blocks_per_rank {
7275 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7276 return Err("step TP decode v2 lost its F32 O residency".into());
7277 };
7278 let x =
7279 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7280 let w = weight.slice(0..weight.len());
7281 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7282 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7283 }
7284 }
7285 Ok(())
7286 }
7287
7288 pub(crate) fn decode_v2_finish(
7296 &self,
7297 ws: &mut StepTpDecodeV2Ws,
7298 e: &Engine,
7299 o_m: &ResidentStepBf16RowParallel,
7300 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7301 let ranks = self.ranks.len();
7302 if e.ctx().ordinal() != ws.e_device {
7303 return Err("step TP decode v2 finish engine changed".into());
7304 }
7305 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7310
7311 for rank in 0..ranks {
7314 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7315 if rank == 0 {
7316 continue;
7319 }
7320 let engine = &self.ranks[rank];
7321 let _main = engine.gpu.enter_main()?;
7322 ws.ev_rank[rank].record(&engine.stream())?;
7323 }
7324
7325 let root = &self.ranks[0];
7327 #[allow(unused_assignments)]
7328 let mut final_in_a = false;
7329 {
7330 let _main = root.gpu.enter_main()?;
7331 for ev in ws.ev_rank.iter().skip(1) {
7332 root.stream().wait(ev)?;
7333 }
7334 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7335 ws.ev_oproj.record(&root.stream())?;
7341 let _main = e.gpu.enter_main()?;
7342 e.stream().wait(&ws.ev_oproj)?;
7343 let mut output = e.uninit(ws.o_out)?;
7344 if oproj_tail_on() && oproj_tail_eligible() {
7345 use cudarc::driver::DevicePtr;
7348 let stream = e.stream();
7349 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7350 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7351 set_oproj_tail((p0 as u64, p1 as u64));
7352 return Ok(output);
7353 }
7354 e.add(
7355 &ws.o_partials[0][0],
7356 &ws.o_partials[1][0],
7357 &mut output,
7358 ws.o_out,
7359 )?;
7360 return Ok(output);
7361 }
7362 if o_fused {
7363 self.decode_v2_finish_root_fused(ws)?;
7364 ws.ev_oproj.record(&root.stream())?;
7365 let _main = e.gpu.enter_main()?;
7366 e.stream().wait(&ws.ev_oproj)?;
7367 let mut output = e.uninit(ws.o_out)?;
7368 e.stream().memcpy_dtod(
7369 &ws.reduce_a.slice(0..ws.o_out),
7370 &mut output.slice_mut(0..ws.o_out),
7371 )?;
7372 return Ok(output);
7373 }
7374 let mut first = true;
7375 let mut current_is_a = false;
7376 for rank in 0..ranks {
7377 for block in 0..ws.blocks_per_rank {
7378 let use_peer = rank != 0;
7379 if use_peer {
7380 root.stream()
7381 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
7382 }
7383 match (first, current_is_a, use_peer) {
7385 (true, _, true) => {
7386 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7387 }
7388 (true, _, false) => root.add(
7389 &ws.zeros,
7390 &ws.o_partials[0][block],
7391 &mut ws.reduce_a,
7392 ws.o_out,
7393 )?,
7394 (false, true, true) => {
7395 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7396 }
7397 (false, true, false) => root.add(
7398 &ws.reduce_a,
7399 &ws.o_partials[0][block],
7400 &mut ws.reduce_b,
7401 ws.o_out,
7402 )?,
7403 (false, false, true) => {
7404 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7405 }
7406 (false, false, false) => root.add(
7407 &ws.reduce_b,
7408 &ws.o_partials[0][block],
7409 &mut ws.reduce_a,
7410 ws.o_out,
7411 )?,
7412 }
7413 current_is_a = first || !current_is_a;
7414 first = false;
7415 }
7416 }
7417 final_in_a = current_is_a;
7418
7419 for rank in 0..ranks {
7420 let start = rank * ws.local_kv_dim;
7421 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
7422 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
7423 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
7424 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
7425 }
7426 ws.ev_oproj.record(&root.stream())?;
7427 }
7428
7429 let _main = e.gpu.enter_main()?;
7433 e.stream().wait(&ws.ev_oproj)?;
7434 let mut output = e.uninit(ws.o_out)?;
7435 let source = if final_in_a {
7436 &ws.reduce_a
7437 } else {
7438 &ws.reduce_b
7439 };
7440 e.stream().memcpy_dtod(
7441 &source.slice(0..ws.o_out),
7442 &mut output.slice_mut(0..ws.o_out),
7443 )?;
7444 Ok(output)
7445 }
7446
7447 pub fn run_routed_experts(
7448 &self,
7449 experts: &ResidentExpertParallel,
7450 input: &[f32],
7451 tokens: usize,
7452 selected: &[usize],
7453 route_weights: &[f32],
7454 experts_per_token: usize,
7455 activation_limit: Option<f32>,
7456 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7457 validate_step_expert_activation_limit(activation_limit)?;
7458 validate_ep_residency(&self.ranks, experts)?;
7459 validate_activations(input, tokens, experts.input_width)?;
7460 let pairs = tokens
7461 .checked_mul(experts_per_token)
7462 .ok_or("EP route count overflow")?;
7463 if selected.len() != pairs || route_weights.len() != pairs {
7464 return Err(format!(
7465 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7466 {experts_per_token} ({pairs})",
7467 selected.len(),
7468 route_weights.len(),
7469 )
7470 .into());
7471 }
7472 if !route_weights.iter().all(|weight| weight.is_finite()) {
7473 return Err("EP route weights contain a non-finite value".into());
7474 }
7475 if self.native_p2p {
7476 return self.run_routed_experts_native(
7477 experts,
7478 input,
7479 tokens,
7480 selected,
7481 route_weights,
7482 experts_per_token,
7483 activation_limit,
7484 );
7485 }
7486
7487 let mut output = vec![0.0f32; tokens * experts.input_width];
7488 let per_rank = experts.expert_count / experts.ranks.len();
7489 for token in 0..tokens {
7490 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7491 for slot in 0..experts_per_token {
7492 let pair = token * experts_per_token + slot;
7493 let expert = selected[pair];
7494 if expert >= experts.expert_count {
7495 return Err(format!(
7496 "EP selected expert {expert} outside 0..{}",
7497 experts.expert_count
7498 )
7499 .into());
7500 }
7501 let owner = expert / per_rank;
7502 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7503 let rank = &experts.ranks[owner];
7504 let engine = &self.ranks[owner];
7505 let gate =
7506 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7507 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7508 let activated: Vec<f32> = gate
7509 .iter()
7510 .zip(&up)
7511 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7512 .collect();
7513 debug_assert_eq!(activated.len(), experts.expert_width);
7514 let down =
7515 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7516 let weight = route_weights[pair];
7517 for (sum, value) in output
7518 [token * experts.input_width..(token + 1) * experts.input_width]
7519 .iter_mut()
7520 .zip(down)
7521 {
7522 *sum += weight * value;
7523 }
7524 }
7525 }
7526 Ok(output)
7527 }
7528
7529 fn run_routed_experts_native(
7530 &self,
7531 experts: &ResidentExpertParallel,
7532 input: &[f32],
7533 tokens: usize,
7534 selected: &[usize],
7535 route_weights: &[f32],
7536 experts_per_token: usize,
7537 activation_limit: Option<f32>,
7538 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7539 if !self.native_p2p || self.ranks.len() < 2 {
7540 return Err("native EP execution requires at least two P2P ranks".into());
7541 }
7542 if self.ep_device_arithmetic {
7543 return self.run_routed_experts_native_device(
7544 experts,
7545 input,
7546 tokens,
7547 selected,
7548 route_weights,
7549 experts_per_token,
7550 activation_limit,
7551 );
7552 }
7553 let mut output = vec![0.0f32; tokens * experts.input_width];
7554 let per_rank = experts.expert_count / experts.ranks.len();
7555 for token in 0..tokens {
7556 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7557 let mut rank_inputs = (0..self.ranks.len())
7558 .map(|_| None)
7559 .collect::<Vec<Option<CudaSlice<f32>>>>();
7560 rank_inputs[0] = Some({
7561 let root = &self.ranks[0];
7562 let _main = root.gpu.enter_main()?;
7563 root.htod(input_row)?
7564 });
7565
7566 for slot in 0..experts_per_token {
7567 let pair = token * experts_per_token + slot;
7568 let expert = selected[pair];
7569 if expert >= experts.expert_count {
7570 return Err(format!(
7571 "EP selected expert {expert} outside 0..{}",
7572 experts.expert_count
7573 )
7574 .into());
7575 }
7576 let owner = expert / per_rank;
7577 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7578 if rank_inputs[owner].is_none() {
7579 let peer_input = {
7580 let root_input = rank_inputs[0]
7581 .as_ref()
7582 .ok_or("native EP lost its root input")?;
7583 let engine = &self.ranks[owner];
7584 let _main = engine.gpu.enter_main()?;
7585 let mut peer_input = engine.uninit(experts.input_width)?;
7586 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7587 peer_input
7588 };
7589 rank_inputs[owner] = Some(peer_input);
7590 }
7591
7592 let rank = &experts.ranks[owner];
7593 let engine = &self.ranks[owner];
7594 let owner_input = rank_inputs[owner]
7595 .as_ref()
7596 .ok_or("native EP owner input is absent after dispatch")?;
7597 let gate = run_resident_bank_expert_device(
7598 engine,
7599 &rank.gate,
7600 local_expert,
7601 owner_input,
7602 1,
7603 )?;
7604 let up = run_resident_bank_expert_device(
7605 engine,
7606 &rank.up,
7607 local_expert,
7608 owner_input,
7609 1,
7610 )?;
7611 let (gate, up) = {
7612 let _main = engine.gpu.enter_main()?;
7613 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
7614 };
7615 let activated = gate
7616 .iter()
7617 .zip(&up)
7618 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7619 .collect::<Vec<_>>();
7620 debug_assert_eq!(activated.len(), experts.expert_width);
7621 let activated = {
7622 let _main = engine.gpu.enter_main()?;
7623 engine.htod(&activated)?
7624 };
7625 let down = run_resident_bank_expert_device(
7626 engine,
7627 &rank.down,
7628 local_expert,
7629 &activated,
7630 1,
7631 )?;
7632 let down = if owner == 0 {
7633 let _main = engine.gpu.enter_main()?;
7634 engine.dtoh(&down)?
7635 } else {
7636 let root = &self.ranks[0];
7637 let _main = root.gpu.enter_main()?;
7638 let mut root_down = root.uninit(experts.input_width)?;
7639 root.stream().memcpy_dtod(&down, &mut root_down)?;
7640 root.dtoh(&root_down)?
7641 };
7642 let weight = route_weights[pair];
7643 for (sum, value) in output
7644 [token * experts.input_width..(token + 1) * experts.input_width]
7645 .iter_mut()
7646 .zip(down)
7647 {
7648 *sum += weight * value;
7649 }
7650 }
7651 }
7652 Ok(output)
7653 }
7654
7655 fn run_routed_experts_native_device(
7656 &self,
7657 experts: &ResidentExpertParallel,
7658 input: &[f32],
7659 tokens: usize,
7660 selected: &[usize],
7661 route_weights: &[f32],
7662 experts_per_token: usize,
7663 activation_limit: Option<f32>,
7664 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7665 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
7666 return Err(
7667 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
7668 );
7669 }
7670 let mut output = Vec::with_capacity(tokens * experts.input_width);
7671 let per_rank = experts.expert_count / experts.ranks.len();
7672 let root = &self.ranks[0];
7673 for token in 0..tokens {
7674 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7675 let mut rank_inputs = (0..self.ranks.len())
7676 .map(|_| None)
7677 .collect::<Vec<Option<CudaSlice<f32>>>>();
7678 rank_inputs[0] = Some({
7679 let _main = root.gpu.enter_main()?;
7680 root.htod(input_row)?
7681 });
7682 let mut root_output = {
7683 let _main = root.gpu.enter_main()?;
7684 root.zeros(experts.input_width)?
7685 };
7686 let mut remote_down_keepalive = Vec::new();
7687
7688 for slot in 0..experts_per_token {
7689 let pair = token * experts_per_token + slot;
7690 let expert = selected[pair];
7691 if expert >= experts.expert_count {
7692 return Err(format!(
7693 "EP selected expert {expert} outside 0..{}",
7694 experts.expert_count
7695 )
7696 .into());
7697 }
7698 let owner = expert / per_rank;
7699 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7700 if rank_inputs[owner].is_none() {
7701 let peer_input = {
7702 let root_input = rank_inputs[0]
7703 .as_ref()
7704 .ok_or("native EP lost its root input")?;
7705 let engine = &self.ranks[owner];
7706 let _main = engine.gpu.enter_main()?;
7707 let mut peer_input = engine.uninit(experts.input_width)?;
7708 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7709 peer_input
7710 };
7711 rank_inputs[owner] = Some(peer_input);
7712 }
7713
7714 let rank = &experts.ranks[owner];
7715 let engine = &self.ranks[owner];
7716 let owner_input = rank_inputs[owner]
7717 .as_ref()
7718 .ok_or("native EP owner input is absent after dispatch")?;
7719 let gate = run_resident_bank_expert_device(
7720 engine,
7721 &rank.gate,
7722 local_expert,
7723 owner_input,
7724 1,
7725 )?;
7726 let up = run_resident_bank_expert_device(
7727 engine,
7728 &rank.up,
7729 local_expert,
7730 owner_input,
7731 1,
7732 )?;
7733 let activated = {
7734 let _main = engine.gpu.enter_main()?;
7735 let mut activated = engine.uninit(experts.expert_width)?;
7736 if let Some(limit) = activation_limit {
7737 engine.silu_clamped_mul_host_expf(
7738 &gate,
7739 &up,
7740 limit,
7741 &mut activated,
7742 experts.expert_width,
7743 )?;
7744 } else {
7745 engine.silu_mul_host_expf(
7746 &gate,
7747 &up,
7748 &mut activated,
7749 experts.expert_width,
7750 )?;
7751 }
7752 activated
7753 };
7754 let down = run_resident_bank_expert_device(
7755 engine,
7756 &rank.down,
7757 local_expert,
7758 &activated,
7759 1,
7760 )?;
7761 let root_down = if owner == 0 {
7762 down
7763 } else {
7764 let _main = root.gpu.enter_main()?;
7765 let mut root_down = root.uninit(experts.input_width)?;
7766 root.stream().memcpy_dtod(&down, &mut root_down)?;
7767 remote_down_keepalive.push(down);
7771 root_down
7772 };
7773 let _main = root.gpu.enter_main()?;
7774 let mut destination = root_output.slice_mut(0..experts.input_width);
7775 root.axpy_host_into(
7776 &root_down.slice(0..root_down.len()),
7777 route_weights[pair],
7778 &mut destination,
7779 experts.input_width,
7780 )?;
7781 }
7782
7783 let _main = root.gpu.enter_main()?;
7784 let root_output = root.dtoh(&root_output)?;
7785 drop(remote_down_keepalive);
7786 output.extend(root_output);
7787 }
7788 Ok(output)
7789 }
7790}
7791
7792fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7793 if matrix.out_features % tp != 0 {
7794 return Err(format!(
7795 "column-parallel out_features {} is not divisible by TP={tp}",
7796 matrix.out_features
7797 ));
7798 }
7799 let local_out = matrix.out_features / tp;
7800 if local_out % FP8_BLOCK != 0 {
7801 return Err(format!(
7802 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7803 E4M3 scale block"
7804 ));
7805 }
7806 Ok(())
7807}
7808
7809fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7810 if !matches!(tp, 1 | 2 | 4 | 8) {
7811 return Err(format!(
7812 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7813 ));
7814 }
7815 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7816 return Err(format!(
7817 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7818 ));
7819 }
7820 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7821 let local_out = out_features / tp;
7822 if local_out % canonical_rows != 0 {
7823 return Err(format!(
7824 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7825 {canonical_rows}-row chunks"
7826 ));
7827 }
7828 Ok(canonical_rows)
7829}
7830
7831fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7832 if !matches!(tp, 1 | 2 | 4 | 8) {
7833 return Err(format!(
7834 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7835 ));
7836 }
7837 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7838 return Err(format!(
7839 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7840 ));
7841 }
7842 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7843 let local_in = in_features / tp;
7844 if local_in % canonical_cols != 0 {
7845 return Err(format!(
7846 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7847 {canonical_cols}-column chunks"
7848 ));
7849 }
7850 Ok(canonical_cols)
7851}
7852
7853fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7854 if matrix.in_features % tp != 0 {
7855 return Err(format!(
7856 "row-parallel in_features {} is not divisible by TP={tp}",
7857 matrix.in_features
7858 ));
7859 }
7860 let local_in = matrix.in_features / tp;
7861 if local_in % FP8_BLOCK != 0 {
7862 return Err(format!(
7863 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7864 E4M3 scale block"
7865 ));
7866 }
7867 Ok(())
7868}
7869
7870fn upload_rank(
7871 engine: &Engine,
7872 matrix: E4m3BlockMatrix<'_>,
7873) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7874 let _main = engine.gpu.enter_main()?;
7875 matrix.validate()?;
7876 Ok(ResidentE4m3Rank {
7877 codes: engine.htod_bytes(matrix.codes)?,
7878 scales: engine.htod(matrix.scales)?,
7879 out_features: matrix.out_features,
7880 in_features: matrix.in_features,
7881 })
7882}
7883
7884fn upload_bf16_rank(
7885 engine: &Engine,
7886 matrix: Bf16Matrix<'_>,
7887 f32_mirror: bool,
7888) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7889 let _main = engine.gpu.enter_main()?;
7890 matrix.validate()?;
7891 let bytes = engine.htod_bytes(matrix.bytes)?;
7892 let weight = if f32_mirror {
7893 let values = matrix
7894 .out_features
7895 .checked_mul(matrix.in_features)
7896 .ok_or("resident BF16 mirror element count overflow")?;
7897 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7898 } else {
7899 ResidentBf16Weight::Bf16(bytes)
7900 };
7901 let q8 = if crate::step_tp_w8_on() && matrix.in_features % 32 == 0 {
7905 if let ResidentBf16Weight::Bf16(bytes) = &weight {
7906 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
7913 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
7914 engine.encode_q8_0_from_bf16(
7915 bytes,
7916 &mut interleaved,
7917 matrix.in_features,
7918 matrix.out_features,
7919 )?;
7920 let mirror =
7921 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
7922 Some(mirror)
7923 } else {
7924 None
7925 }
7926 } else {
7927 None
7928 };
7929 Ok(ResidentBf16Rank {
7930 weight,
7931 out_features: matrix.out_features,
7932 in_features: matrix.in_features,
7933 q8,
7934 })
7935}
7936
7937fn upload_expert_bank_rank(
7938 engine: &Engine,
7939 bank: E4m3ExpertBank<'_>,
7940 expert_range: Range<usize>,
7941) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7942 let _main = engine.gpu.enter_main()?;
7943 bank.validate()?;
7944 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7945 return Err(format!(
7946 "invalid EP expert range {expert_range:?} for {} experts",
7947 bank.expert_count
7948 )
7949 .into());
7950 }
7951 let code_stride = bank.out_features * bank.in_features;
7952 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7953 Ok(ResidentE4m3ExpertBankRank {
7954 codes: engine.htod_bytes(
7955 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7956 )?,
7957 scales: engine.htod(
7958 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7959 )?,
7960 expert_range,
7961 out_features: bank.out_features,
7962 in_features: bank.in_features,
7963 code_stride,
7964 scale_stride,
7965 k_blocks: None,
7966 })
7967}
7968
7969fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7970 if bank.out_features % tp != 0 {
7971 return Err(format!(
7972 "TP expert output width {} is not divisible by TP={tp}",
7973 bank.out_features
7974 ));
7975 }
7976 let local_out = bank.out_features / tp;
7977 if local_out % FP8_BLOCK != 0 {
7978 return Err(format!(
7979 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7980 ));
7981 }
7982 Ok(())
7983}
7984
7985fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7986 if bank.in_features % tp != 0 {
7987 return Err(format!(
7988 "TP expert input width {} is not divisible by TP={tp}",
7989 bank.in_features
7990 ));
7991 }
7992 let local_in = bank.in_features / tp;
7993 if local_in % FP8_BLOCK != 0 {
7994 return Err(format!(
7995 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7996 ));
7997 }
7998 Ok(())
7999}
8000
8001fn upload_column_bank_rank(
8002 engine: &Engine,
8003 bank: E4m3ExpertBank<'_>,
8004 tp: usize,
8005 rank: usize,
8006) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8007 let _main = engine.gpu.enter_main()?;
8008 let packed = pack_column_bank_rank(bank, tp, rank)?;
8009 Ok(ResidentE4m3ExpertBankRank {
8010 codes: engine.htod_bytes(&packed.codes)?,
8011 scales: engine.htod(&packed.scales)?,
8012 expert_range: packed.expert_range,
8013 out_features: packed.out_features,
8014 in_features: packed.in_features,
8015 code_stride: packed.code_stride,
8016 scale_stride: packed.scale_stride,
8017 k_blocks: packed.k_blocks,
8018 })
8019}
8020
8021fn pack_column_bank_rank(
8022 bank: E4m3ExpertBank<'_>,
8023 tp: usize,
8024 rank: usize,
8025) -> Result<PackedE4m3ExpertBankRank, String> {
8026 bank.validate()?;
8027 validate_column_bank_shape(bank, tp)?;
8028 if rank >= tp {
8029 return Err(format!("TP rank {rank} outside 0..{tp}"));
8030 }
8031 let local_out = bank.out_features / tp;
8032 let full_code_stride = bank.out_features * bank.in_features;
8033 let local_code_stride = local_out * bank.in_features;
8034 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8035 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
8036 let local_scale_rows = local_out / FP8_BLOCK;
8037 let local_scale_stride = local_scale_rows * scale_cols;
8038 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8039 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8040 let row_start = rank * local_out;
8041 let scale_row_start = rank * local_scale_rows;
8042 for expert in 0..bank.expert_count {
8043 let code_start = expert * full_code_stride + row_start * bank.in_features;
8044 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
8045 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
8046 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
8047 }
8048 Ok(PackedE4m3ExpertBankRank {
8049 codes,
8050 scales,
8051 expert_range: 0..bank.expert_count,
8052 out_features: local_out,
8053 in_features: bank.in_features,
8054 code_stride: local_code_stride,
8055 scale_stride: local_scale_stride,
8056 k_blocks: None,
8057 })
8058}
8059
8060fn upload_row_bank_rank(
8061 engine: &Engine,
8062 bank: E4m3ExpertBank<'_>,
8063 tp: usize,
8064 rank: usize,
8065) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
8066 let _main = engine.gpu.enter_main()?;
8067 let packed = pack_row_bank_rank(bank, tp, rank)?;
8068 Ok(ResidentE4m3ExpertBankRank {
8069 codes: engine.htod_bytes(&packed.codes)?,
8070 scales: engine.htod(&packed.scales)?,
8071 expert_range: packed.expert_range,
8072 out_features: packed.out_features,
8073 in_features: packed.in_features,
8074 code_stride: packed.code_stride,
8075 scale_stride: packed.scale_stride,
8076 k_blocks: packed.k_blocks,
8077 })
8078}
8079
8080fn pack_row_bank_rank(
8081 bank: E4m3ExpertBank<'_>,
8082 tp: usize,
8083 rank: usize,
8084) -> Result<PackedE4m3ExpertBankRank, String> {
8085 bank.validate()?;
8086 validate_row_bank_shape(bank, tp)?;
8087 if rank >= tp {
8088 return Err(format!("TP rank {rank} outside 0..{tp}"));
8089 }
8090 let local_in = bank.in_features / tp;
8091 let full_code_stride = bank.out_features * bank.in_features;
8092 let local_code_stride = bank.out_features * local_in;
8093 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
8094 let local_scale_cols = local_in / FP8_BLOCK;
8095 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
8096 let full_scale_stride = scale_rows * full_scale_cols;
8097 let local_scale_stride = scale_rows * local_scale_cols;
8098 let global_block_start = rank * local_scale_cols;
8099 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
8100 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
8101 for expert in 0..bank.expert_count {
8102 let expert_code_start = expert * full_code_stride;
8103 let expert_scale_start = expert * full_scale_stride;
8104 for local_block in 0..local_scale_cols {
8105 let global_block = global_block_start + local_block;
8106 let column_start = global_block * FP8_BLOCK;
8107 for row in 0..bank.out_features {
8108 let start = expert_code_start + row * bank.in_features + column_start;
8109 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
8110 }
8111 for row in 0..scale_rows {
8112 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
8113 }
8114 }
8115 }
8116 Ok(PackedE4m3ExpertBankRank {
8117 codes,
8118 scales,
8119 expert_range: 0..bank.expert_count,
8120 out_features: bank.out_features,
8121 in_features: local_in,
8122 code_stride: local_code_stride,
8123 scale_stride: local_scale_stride,
8124 k_blocks: Some(local_scale_cols),
8125 })
8126}
8127
8128fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
8129 if engines.len() != ranks.len() {
8130 return Err(format!(
8131 "resident TP rank count {} != runtime rank count {}",
8132 ranks.len(),
8133 engines.len()
8134 ));
8135 }
8136 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8137 let device = engine.ctx().ordinal();
8138 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
8139 return Err(format!(
8140 "resident TP rank {rank} is not owned by runtime device {device}"
8141 ));
8142 }
8143 }
8144 Ok(())
8145}
8146
8147fn validate_tp_bank_residency(
8148 engines: &[Engine],
8149 experts: &ResidentTpExpertBank,
8150) -> Result<(), String> {
8151 if engines.len() != experts.gate.len()
8152 || engines.len() != experts.up.len()
8153 || engines.len() != experts.down.len()
8154 {
8155 return Err(format!(
8156 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
8157 experts.gate.len(),
8158 experts.up.len(),
8159 experts.down.len(),
8160 engines.len()
8161 ));
8162 }
8163 for (rank, engine) in engines.iter().enumerate() {
8164 let device = engine.ctx().ordinal();
8165 for (projection, bank) in [
8166 ("gate", &experts.gate[rank]),
8167 ("up", &experts.up[rank]),
8168 ("down", &experts.down[rank]),
8169 ] {
8170 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8171 return Err(format!(
8172 "resident TP rank {rank} {projection} bank is not owned by runtime device \
8173 {device}"
8174 ));
8175 }
8176 }
8177 }
8178 Ok(())
8179}
8180
8181fn validate_ep_residency(
8182 engines: &[Engine],
8183 experts: &ResidentExpertParallel,
8184) -> Result<(), String> {
8185 if engines.len() != experts.ranks.len() {
8186 return Err(format!(
8187 "resident EP rank count {} != runtime rank count {}",
8188 experts.ranks.len(),
8189 engines.len()
8190 ));
8191 }
8192 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
8193 let device = engine.ctx().ordinal();
8194 for (projection, bank) in [
8195 ("gate", &resident.gate),
8196 ("up", &resident.up),
8197 ("down", &resident.down),
8198 ] {
8199 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
8200 return Err(format!(
8201 "resident EP rank {rank} {projection} bank is not owned by runtime device \
8202 {device}"
8203 ));
8204 }
8205 }
8206 }
8207 Ok(())
8208}
8209
8210fn run_rank(
8211 engine: &Engine,
8212 matrix: E4m3BlockMatrix<'_>,
8213 activations: &[f32],
8214 tokens: usize,
8215) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8216 let _main = engine.gpu.enter_main()?;
8217 let codes = engine.htod_bytes(matrix.codes)?;
8218 let scales = engine.htod(matrix.scales)?;
8219 let activations = engine.htod(activations)?;
8220 let output = engine.qmatvec_mmq_fp8_blk(
8221 &codes,
8222 &scales,
8223 &activations,
8224 tokens,
8225 matrix.in_features,
8226 matrix.out_features,
8227 )?;
8228 engine.dtoh(&output)
8229}
8230
8231fn run_resident_rank(
8232 engine: &Engine,
8233 matrix: &ResidentE4m3Rank,
8234 activations: &[f32],
8235 tokens: usize,
8236) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8237 let _main = engine.gpu.enter_main()?;
8238 let activations = engine.htod(activations)?;
8239 let output = engine.qmatvec_mmq_fp8_blk(
8240 &matrix.codes,
8241 &matrix.scales,
8242 &activations,
8243 tokens,
8244 matrix.in_features,
8245 matrix.out_features,
8246 )?;
8247 engine.dtoh(&output)
8248}
8249
8250fn run_resident_bf16_rank(
8251 engine: &Engine,
8252 matrix: &ResidentBf16Rank,
8253 activations: &[f32],
8254 tokens: usize,
8255 canonical_chunk_rows: Option<usize>,
8256) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8257 let _main = engine.gpu.enter_main()?;
8258 let activations = engine.htod(activations)?;
8259 let output = run_resident_bf16_rank_device(
8260 engine,
8261 matrix,
8262 &activations,
8263 tokens,
8264 canonical_chunk_rows,
8265 false,
8266 )?;
8267 engine.dtoh(&output)
8268}
8269
8270fn run_resident_bf16_rank_device(
8271 engine: &Engine,
8272 matrix: &ResidentBf16Rank,
8273 activations: &CudaSlice<f32>,
8274 tokens: usize,
8275 canonical_chunk_rows: Option<usize>,
8276 strided_chunk_output: bool,
8277) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8278 let _main = engine.gpu.enter_main()?;
8279 if activations.ordinal() != engine.ctx().ordinal() {
8280 return Err(format!(
8281 "resident BF16 activation device {} != rank device {}",
8282 activations.ordinal(),
8283 engine.ctx().ordinal()
8284 )
8285 .into());
8286 }
8287 if activations.len() != tokens * matrix.in_features {
8288 return Err(format!(
8289 "resident BF16 activation count {} != {tokens}x{}",
8290 activations.len(),
8291 matrix.in_features
8292 )
8293 .into());
8294 }
8295 match (&matrix.weight, canonical_chunk_rows) {
8296 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8297 .linear_bf16_resident_canonical_rows(
8298 activations,
8299 bytes,
8300 tokens,
8301 matrix.in_features,
8302 matrix.out_features,
8303 rows,
8304 ),
8305 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8306 activations,
8307 bytes,
8308 tokens,
8309 matrix.in_features,
8310 matrix.out_features,
8311 ),
8312 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8313 .linear_f32_resident_canonical_rows_strided(
8314 activations,
8315 values,
8316 tokens,
8317 matrix.in_features,
8318 matrix.out_features,
8319 rows,
8320 ),
8321 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8322 activations,
8323 values,
8324 tokens,
8325 matrix.in_features,
8326 matrix.out_features,
8327 rows,
8328 ),
8329 (ResidentBf16Weight::F32(values), None) => engine.linear(
8330 activations,
8331 values,
8332 tokens,
8333 matrix.in_features,
8334 matrix.out_features,
8335 ),
8336 }
8337}
8338
8339fn validate_resident_bf16_ranks(
8340 engines: &[Engine],
8341 ranks: &[ResidentBf16Rank],
8342) -> Result<(), String> {
8343 if engines.len() != ranks.len() {
8344 return Err(format!(
8345 "resident BF16 TP rank count {} != runtime rank count {}",
8346 ranks.len(),
8347 engines.len(),
8348 ));
8349 }
8350 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8351 let device = engine.ctx().ordinal();
8352 if matrix.weight.ordinal() != device {
8353 return Err(format!(
8354 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8355 ));
8356 }
8357 }
8358 Ok(())
8359}
8360
8361fn validate_step_bf16_row_residency(
8362 engines: &[Engine],
8363 matrix: &ResidentStepBf16RowParallel,
8364) -> Result<(), String> {
8365 if engines.len() != matrix.ranks.len() {
8366 return Err(format!(
8367 "resident Step BF16 row rank count {} != runtime rank count {}",
8368 matrix.ranks.len(),
8369 engines.len(),
8370 ));
8371 }
8372 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8373 if matrix.canonical_chunk_cols != canonical_cols {
8374 return Err(format!(
8375 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8376 matrix.canonical_chunk_cols
8377 ));
8378 }
8379 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8380 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8381 if blocks.len() != blocks_per_rank {
8382 return Err(format!(
8383 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8384 blocks.len()
8385 ));
8386 }
8387 let device = engine.ctx().ordinal();
8388 for (block, resident) in blocks.iter().enumerate() {
8389 if resident.weight.ordinal() != device
8390 || resident.in_features != canonical_cols
8391 || resident.out_features != matrix.out_features
8392 {
8393 return Err(format!(
8394 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8395 device or geometry"
8396 ));
8397 }
8398 }
8399 }
8400 Ok(())
8401}
8402
8403fn validate_replicated_device_rows(
8404 engines: &[Engine],
8405 rows: &ResidentReplicatedDeviceRows,
8406) -> Result<(), String> {
8407 let rank_lengths = rows
8408 .ranks
8409 .iter()
8410 .map(|rank_rows| rank_rows.len())
8411 .collect::<Vec<_>>();
8412 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8413 if rows
8414 .ranks
8415 .iter()
8416 .zip(engines)
8417 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8418 {
8419 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8420 }
8421 Ok(())
8422}
8423
8424fn replicated_device_row_values(
8425 tokens: usize,
8426 width: usize,
8427 expected_ranks: usize,
8428 rank_lengths: &[usize],
8429) -> Result<usize, String> {
8430 let values = tokens
8431 .checked_mul(width)
8432 .ok_or("replicated device row size overflow")?;
8433 if tokens == 0
8434 || width == 0
8435 || expected_ranks == 0
8436 || rank_lengths.len() != expected_ranks
8437 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8438 {
8439 return Err(format!(
8440 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8441 tokens,
8442 width,
8443 rank_lengths.len(),
8444 expected_ranks
8445 ));
8446 }
8447 Ok(values)
8448}
8449
8450fn replicated_device_row_source_values(
8451 tokens: usize,
8452 width: usize,
8453 source_len: usize,
8454 source_device: usize,
8455 root_device: usize,
8456) -> Result<usize, String> {
8457 let values = tokens
8458 .checked_mul(width)
8459 .ok_or("replicated device row size overflow")?;
8460 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8461 return Err(format!(
8462 "replicated device row source has inconsistent geometry/device \
8463 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8464 ));
8465 }
8466 Ok(values)
8467}
8468
8469fn bf16_column_shard(
8470 matrix: Bf16Matrix<'_>,
8471 tp: usize,
8472 rank: usize,
8473) -> Result<Bf16Matrix<'_>, String> {
8474 matrix.validate()?;
8475 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8476 return Err(format!(
8477 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8478 matrix.out_features
8479 ));
8480 }
8481 let local_out = matrix.out_features / tp;
8482 let row_bytes = matrix.in_features * 2;
8483 let start = rank * local_out * row_bytes;
8484 Ok(Bf16Matrix {
8485 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8486 out_features: local_out,
8487 in_features: matrix.in_features,
8488 })
8489}
8490
8491fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8492 matrix.validate()?;
8493 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8494 return Err(format!(
8495 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8496 matrix.in_features
8497 ));
8498 }
8499 let local_in = matrix.in_features / tp;
8500 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8501 for row in 0..matrix.out_features {
8502 let start = (row * matrix.in_features + rank * local_in) * 2;
8503 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8504 }
8505 Ok(bytes)
8506}
8507
8508fn bf16_row_block(
8509 matrix: Bf16Matrix<'_>,
8510 col_start: usize,
8511 block_cols: usize,
8512) -> Result<Vec<u8>, String> {
8513 matrix.validate()?;
8514 let col_end = col_start
8515 .checked_add(block_cols)
8516 .ok_or("BF16 row block column overflow")?;
8517 if block_cols == 0 || col_end > matrix.in_features {
8518 return Err(format!(
8519 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8520 matrix.in_features
8521 ));
8522 }
8523 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8524 for row in 0..matrix.out_features {
8525 let start = (row * matrix.in_features + col_start) * 2;
8526 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8527 }
8528 Ok(bytes)
8529}
8530
8531fn run_resident_bank_expert(
8532 engine: &Engine,
8533 bank: &ResidentE4m3ExpertBankRank,
8534 local_expert: usize,
8535 activations: &[f32],
8536 tokens: usize,
8537) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8538 let _main = engine.gpu.enter_main()?;
8539 if bank.k_blocks.is_some() {
8540 return Err("block-major TP row bank requires canonical block execution".into());
8541 }
8542 let local_count = bank.expert_range.end - bank.expert_range.start;
8543 if local_expert >= local_count {
8544 return Err(format!(
8545 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8546 bank.expert_range
8547 )
8548 .into());
8549 }
8550 validate_activations(activations, tokens, bank.in_features)?;
8551 let activations = engine.htod(activations)?;
8552 let weight = bank
8553 .codes
8554 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8555 let scales = bank
8556 .scales
8557 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8558 let input = activations.slice(0..activations.len());
8559 let output = engine.qmatvec_mmq_fp8_blk_view(
8560 &weight,
8561 &scales,
8562 &input,
8563 tokens,
8564 bank.in_features,
8565 bank.out_features,
8566 )?;
8567 engine.dtoh(&output)
8568}
8569
8570fn run_resident_bank_expert_block(
8571 engine: &Engine,
8572 bank: &ResidentE4m3ExpertBankRank,
8573 local_expert: usize,
8574 block: usize,
8575 activations: &[f32],
8576) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8577 let _main = engine.gpu.enter_main()?;
8578 let local_count = bank.expert_range.end - bank.expert_range.start;
8579 if local_expert >= local_count {
8580 return Err(format!(
8581 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8582 bank.expert_range
8583 )
8584 .into());
8585 }
8586 let blocks = bank
8587 .k_blocks
8588 .ok_or("TP row bank is not packed in native K-block order")?;
8589 if block >= blocks {
8590 return Err(format!("TP row block {block} outside 0..{blocks}").into());
8591 }
8592 validate_activations(activations, 1, FP8_BLOCK)?;
8593 let block_code_stride = bank.out_features * FP8_BLOCK;
8594 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8595 if bank.in_features != blocks * FP8_BLOCK
8596 || bank.code_stride != blocks * block_code_stride
8597 || bank.scale_stride != blocks * block_scale_stride
8598 {
8599 return Err("TP row bank block-major geometry is inconsistent".into());
8600 }
8601
8602 let expert_code_start = local_expert * bank.code_stride;
8603 let expert_scale_start = local_expert * bank.scale_stride;
8604 let weight = bank.codes.slice(
8605 expert_code_start + block * block_code_stride
8606 ..expert_code_start + (block + 1) * block_code_stride,
8607 );
8608 let scales = bank.scales.slice(
8609 expert_scale_start + block * block_scale_stride
8610 ..expert_scale_start + (block + 1) * block_scale_stride,
8611 );
8612 let activations = engine.htod(activations)?;
8613 let input = activations.slice(0..activations.len());
8614 let output = engine.qmatvec_mmq_fp8_blk_view(
8615 &weight,
8616 &scales,
8617 &input,
8618 1,
8619 FP8_BLOCK,
8620 bank.out_features,
8621 )?;
8622 engine.dtoh(&output)
8623}
8624
8625fn run_resident_bank_expert_device(
8626 engine: &Engine,
8627 bank: &ResidentE4m3ExpertBankRank,
8628 local_expert: usize,
8629 activations: &CudaSlice<f32>,
8630 tokens: usize,
8631) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8632 let _main = engine.gpu.enter_main()?;
8633 if bank.k_blocks.is_some() {
8634 return Err("block-major TP row bank requires canonical block execution".into());
8635 }
8636 let local_count = bank.expert_range.end - bank.expert_range.start;
8637 if local_expert >= local_count {
8638 return Err(format!(
8639 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8640 bank.expert_range
8641 )
8642 .into());
8643 }
8644 let expected = tokens
8645 .checked_mul(bank.in_features)
8646 .ok_or("native TP activation size overflow")?;
8647 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
8648 return Err(format!(
8649 "native TP activation len/device {}/{} != expected {expected}/{}",
8650 activations.len(),
8651 activations.ordinal(),
8652 engine.ctx().ordinal()
8653 )
8654 .into());
8655 }
8656 let weight = bank
8657 .codes
8658 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8659 let scales = bank
8660 .scales
8661 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8662 let input = activations.slice(0..activations.len());
8663 engine.qmatvec_mmq_fp8_blk_view(
8664 &weight,
8665 &scales,
8666 &input,
8667 tokens,
8668 bank.in_features,
8669 bank.out_features,
8670 )
8671}
8672
8673fn run_resident_bank_expert_block_device(
8674 engine: &Engine,
8675 bank: &ResidentE4m3ExpertBankRank,
8676 local_expert: usize,
8677 block: usize,
8678 activations: &cudarc::driver::CudaView<'_, f32>,
8679) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8680 let _main = engine.gpu.enter_main()?;
8681 let local_count = bank.expert_range.end - bank.expert_range.start;
8682 if local_expert >= local_count {
8683 return Err(format!(
8684 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8685 bank.expert_range
8686 )
8687 .into());
8688 }
8689 let blocks = bank
8690 .k_blocks
8691 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
8692 if block >= blocks {
8693 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
8694 }
8695 let activation_device = activations.stream().context().ordinal();
8696 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
8697 return Err(format!(
8698 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
8699 activations.len(),
8700 activation_device,
8701 engine.ctx().ordinal()
8702 )
8703 .into());
8704 }
8705 let block_code_stride = bank.out_features * FP8_BLOCK;
8706 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8707 if bank.in_features != blocks * FP8_BLOCK
8708 || bank.code_stride != blocks * block_code_stride
8709 || bank.scale_stride != blocks * block_scale_stride
8710 {
8711 return Err("native TP row bank block-major geometry is inconsistent".into());
8712 }
8713 let expert_code_start = local_expert * bank.code_stride;
8714 let expert_scale_start = local_expert * bank.scale_stride;
8715 let weight = bank.codes.slice(
8716 expert_code_start + block * block_code_stride
8717 ..expert_code_start + (block + 1) * block_code_stride,
8718 );
8719 let scales = bank.scales.slice(
8720 expert_scale_start + block * block_scale_stride
8721 ..expert_scale_start + (block + 1) * block_scale_stride,
8722 );
8723 engine.qmatvec_mmq_fp8_blk_view(
8724 &weight,
8725 &scales,
8726 activations,
8727 1,
8728 FP8_BLOCK,
8729 bank.out_features,
8730 )
8731}
8732
8733fn configure_native_p2p(
8734 ranks: &[Engine],
8735 devices: &[usize],
8736) -> Result<(), Box<dyn std::error::Error>> {
8737 if ranks.len() != devices.len() || ranks.len() < 2 {
8738 return Err("native TP P2P setup requires matching multi-rank devices".into());
8739 }
8740 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8741 if engine.ctx().ordinal() != device {
8742 return Err(format!(
8743 "native TP rank {rank} context device {} != requested device {device}",
8744 engine.ctx().ordinal()
8745 )
8746 .into());
8747 }
8748 }
8749
8750 for src in 0..ranks.len() {
8751 for dst in 0..ranks.len() {
8752 if src == dst {
8753 continue;
8754 }
8755 let mut can_access = 0;
8756 unsafe {
8757 cudarc::driver::sys::cuDeviceCanAccessPeer(
8758 &mut can_access,
8759 ranks[src].ctx().cu_device(),
8760 ranks[dst].ctx().cu_device(),
8761 )
8762 .result()?;
8763 }
8764 if can_access == 0 {
8765 return Err(format!(
8766 "native TP requires P2P, but dev{} cannot access dev{}",
8767 devices[src], devices[dst]
8768 )
8769 .into());
8770 }
8771 ranks[src].ctx().bind_to_thread()?;
8772 let rc =
8773 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8774 use cudarc::driver::sys::cudaError_enum as E;
8775 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8776 return Err(format!(
8777 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8778 devices[src], devices[dst]
8779 )
8780 .into());
8781 }
8782 }
8783 }
8784
8785 for &owner in devices {
8786 for &accessor in devices {
8787 if owner == accessor {
8788 continue;
8789 }
8790 let device = cudarc::driver::result::device::get(owner as i32)?;
8791 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8792 unsafe {
8793 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8794 }
8795 let desc = cudarc::driver::sys::CUmemAccessDesc {
8796 location: cudarc::driver::sys::CUmemLocation {
8797 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8798 id: accessor as i32,
8799 },
8800 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8801 };
8802 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8803 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8804 return Err(format!(
8805 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8806 {rc:?}"
8807 )
8808 .into());
8809 }
8810 }
8811 }
8812
8813 for src in 0..ranks.len() {
8814 for dst in 0..ranks.len() {
8815 if src == dst {
8816 continue;
8817 }
8818 for &words in NATIVE_P2P_PROBE_WORDS {
8819 let expected = (0..words)
8820 .map(|index| {
8821 (index as u32)
8822 .wrapping_mul(0x9e37_79b9)
8823 .wrapping_add(((src as u32) << 16) | dst as u32)
8824 })
8825 .collect::<Vec<_>>();
8826 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8827 let source = ranks[src].htod_u32_v(&expected)?;
8828 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8829 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8830 let actual = ranks[dst].dtoh_u32(&destination)?;
8831 if actual != expected {
8832 let mismatches = actual
8833 .iter()
8834 .zip(&expected)
8835 .filter(|(actual, expected)| actual != expected)
8836 .count();
8837 return Err(format!(
8838 "native TP peer probe dev{}->dev{} failed at {} bytes: \
8839 {mismatches}/{} words differ",
8840 devices[src],
8841 devices[dst],
8842 words * std::mem::size_of::<u32>(),
8843 expected.len()
8844 )
8845 .into());
8846 }
8847 }
8848 }
8849 }
8850 ranks[0].ctx().bind_to_thread()?;
8851 eprintln!(
8852 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8853 directions={} byte_ladder={:?} mismatches=0",
8854 ranks.len() * (ranks.len() - 1),
8855 NATIVE_P2P_PROBE_WORDS
8856 .iter()
8857 .map(|words| words * std::mem::size_of::<u32>())
8858 .collect::<Vec<_>>(),
8859 );
8860 Ok(())
8861}
8862
8863fn validate_activations(
8864 activations: &[f32],
8865 tokens: usize,
8866 in_features: usize,
8867) -> Result<(), String> {
8868 let expected = tokens
8869 .checked_mul(in_features)
8870 .ok_or_else(|| "activation size overflow".to_string())?;
8871 if activations.len() != expected {
8872 return Err(format!(
8873 "activation count {} != {tokens}x{in_features} ({expected})",
8874 activations.len()
8875 ));
8876 }
8877 if !activations.iter().all(|value| value.is_finite()) {
8878 return Err("activations contain a non-finite value".to_string());
8879 }
8880 Ok(())
8881}
8882
8883fn column_shard(
8884 matrix: E4m3BlockMatrix<'_>,
8885 tp: usize,
8886 rank: usize,
8887) -> Result<E4m3BlockMatrix<'_>, String> {
8888 let local_out = matrix.out_features / tp;
8889 let row_start = rank * local_out;
8890 let code_start = row_start * matrix.in_features;
8891 let code_end = code_start + local_out * matrix.in_features;
8892 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8893 let local_scale_rows = local_out / FP8_BLOCK;
8894 let scale_start = rank * local_scale_rows * scale_cols;
8895 let scale_end = scale_start + local_scale_rows * scale_cols;
8896 Ok(E4m3BlockMatrix {
8897 codes: &matrix.codes[code_start..code_end],
8898 scales: &matrix.scales[scale_start..scale_end],
8899 out_features: local_out,
8900 in_features: matrix.in_features,
8901 })
8902}
8903
8904fn row_shard(
8905 matrix: E4m3BlockMatrix<'_>,
8906 tp: usize,
8907 rank: usize,
8908) -> Result<(Vec<u8>, Vec<f32>), String> {
8909 let local_in = matrix.in_features / tp;
8910 let col_start = rank * local_in;
8911 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8912 for row in 0..matrix.out_features {
8913 let start = row * matrix.in_features + col_start;
8914 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8915 }
8916
8917 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8918 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8919 let local_scale_cols = local_in / FP8_BLOCK;
8920 let scale_col_start = rank * local_scale_cols;
8921 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8922 for row in 0..scale_rows {
8923 let start = row * scale_cols + scale_col_start;
8924 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8925 }
8926 Ok((codes, scales))
8927}
8928
8929fn activation_shard(
8930 activations: &[f32],
8931 tokens: usize,
8932 in_features: usize,
8933 tp: usize,
8934 rank: usize,
8935) -> Vec<f32> {
8936 let local_in = in_features / tp;
8937 let col_start = rank * local_in;
8938 let mut shard = Vec::with_capacity(tokens * local_in);
8939 for token in 0..tokens {
8940 let start = token * in_features + col_start;
8941 shard.extend_from_slice(&activations[start..start + local_in]);
8942 }
8943 shard
8944}
8945
8946#[derive(Clone, Copy)]
8966pub struct Nvfp4BlockMatrix<'a> {
8967 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8971 pub in_features: usize,
8972}
8973
8974impl Nvfp4BlockMatrix<'_> {
8975 pub fn validate(&self) -> Result<(), String> {
8976 if self.in_features == 0 || self.out_features == 0 {
8977 return Err("NVFP4 matrix has a zero dimension".to_string());
8978 }
8979 if self.in_features % 64 != 0 {
8980 return Err(format!(
8981 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8982 self.in_features
8983 ));
8984 }
8985 if self.codes.len() != self.out_features * self.in_features / 2 {
8986 return Err(format!(
8987 "NVFP4 code bytes {} != {}x{}/2",
8988 self.codes.len(),
8989 self.out_features,
8990 self.in_features
8991 ));
8992 }
8993 if self.scales.len() != self.out_features * self.in_features / 16 {
8994 return Err(format!(
8995 "NVFP4 scale bytes {} != {}x{}/16",
8996 self.scales.len(),
8997 self.out_features,
8998 self.in_features
8999 ));
9000 }
9001 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
9002 return Err(format!(
9003 "NVFP4 macro scale {} is not finite-positive",
9004 self.macro_scale
9005 ));
9006 }
9007 Ok(())
9008 }
9009}
9010
9011#[derive(Clone, Copy)]
9013pub struct Nvfp4ExpertBank<'a> {
9014 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
9018 pub out_features: usize,
9019 pub in_features: usize,
9020}
9021
9022impl Nvfp4ExpertBank<'_> {
9023 pub fn validate(&self) -> Result<(), String> {
9024 if self.expert_count == 0 {
9025 return Err("NVFP4 expert bank is empty".to_string());
9026 }
9027 if self.macros.len() != self.expert_count {
9028 return Err(format!(
9029 "NVFP4 bank macros {} != expert count {}",
9030 self.macros.len(),
9031 self.expert_count
9032 ));
9033 }
9034 self.expert(0).map(|_| ())
9035 }
9036
9037 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
9038 if expert >= self.expert_count {
9039 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
9040 }
9041 let code_stride = self.out_features * self.in_features / 2;
9042 let scale_stride = self.out_features * self.in_features / 16;
9043 if self.codes.len() != self.expert_count * code_stride
9044 || self.scales.len() != self.expert_count * scale_stride
9045 {
9046 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
9047 }
9048 let matrix = Nvfp4BlockMatrix {
9049 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
9050 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
9051 macro_scale: self.macros[expert],
9052 out_features: self.out_features,
9053 in_features: self.in_features,
9054 };
9055 matrix.validate()?;
9056 Ok(matrix)
9057 }
9058}
9059
9060pub struct ResidentNvfp4Rank {
9062 blocks: crate::CudaSlice<u8>,
9063 macro_scale: f32,
9064 out_features: usize,
9065 in_features: usize,
9066 row_bytes: usize,
9067}
9068
9069pub struct ResidentNvfp4ColumnParallel {
9070 ranks: Vec<ResidentNvfp4Rank>,
9071 pub out_features: usize,
9072 pub in_features: usize,
9073}
9074
9075pub struct ResidentNvfp4RowParallel {
9076 ranks: Vec<ResidentNvfp4Rank>,
9077 pub out_features: usize,
9078 pub in_features: usize,
9079}
9080
9081pub struct ResidentTpNvfp4Expert {
9082 gate: ResidentNvfp4ColumnParallel,
9083 up: ResidentNvfp4ColumnParallel,
9084 down: ResidentNvfp4RowParallel,
9085 pub input_width: usize,
9086 pub expert_width: usize,
9087}
9088
9089pub struct ResidentNvfp4ColumnBankRank {
9093 bank: crate::CudaSlice<u8>,
9097 expert_bytes: usize,
9098 local_out: usize,
9099 in_features: usize,
9100 row_bytes: usize,
9101}
9102
9103impl ResidentNvfp4ColumnBankRank {
9104 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9105 self.bank
9106 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9107 }
9108}
9109
9110pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
9116
9117pub struct ResidentNvfp4RowBankRank {
9118 bank: crate::CudaSlice<u8>,
9120 expert_bytes: usize,
9121 device_rank: usize, out_features: usize,
9123 local_in: usize,
9124 row_bytes: usize,
9125}
9126
9127impl ResidentNvfp4RowBankRank {
9128 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
9129 self.bank
9130 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
9131 }
9132}
9133
9134impl ResidentNvfp4TensorParallel {
9135 pub(crate) fn device_workspace_handle(
9136 &self,
9137 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
9138 &self.device_workspace
9139 }
9140}
9141
9142pub struct ResidentNvfp4TensorParallel {
9143 gate: Vec<ResidentNvfp4ColumnBankRank>,
9144 up: Vec<ResidentNvfp4ColumnBankRank>,
9145 down: Vec<ResidentNvfp4RowBankRank>,
9146 macros_gate: Vec<f32>,
9147 macros_up: Vec<f32>,
9148 macros_down: Vec<f32>,
9149 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
9153 macros_up_dev: Vec<crate::CudaSlice<f32>>,
9154 macros_down_dev: Vec<crate::CudaSlice<f32>>,
9155 pub expert_count: usize,
9156 pub input_width: usize,
9157 pub expert_width: usize,
9158 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
9161 prime_tables: std::sync::Mutex<Vec<crate::CudaSlice<u64>>>,
9165 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
9169 pub(crate) ep2: bool,
9173}
9174
9175pub struct Nvfp4T2Workspace {
9179 input2: Vec<crate::CudaSlice<f32>>,
9180 in_q2: Vec<crate::CudaSlice<i8>>,
9181 in_d2: Vec<crate::CudaSlice<f32>>,
9182 sel2: Vec<crate::CudaSlice<i32>>,
9183 route_w2: Vec<crate::CudaSlice<f32>>,
9184 gate_out2: Vec<crate::CudaSlice<f32>>,
9185 up_out2: Vec<crate::CudaSlice<f32>>,
9186 act_q2: Vec<crate::CudaSlice<i8>>,
9187 act_d2: Vec<crate::CudaSlice<f32>>,
9188 partial2: Vec<crate::CudaSlice<f32>>,
9189 acc_a: Vec<crate::CudaSlice<f32>>,
9191 acc_b: Vec<crate::CudaSlice<f32>>,
9192 acc2: Vec<crate::CudaSlice<f32>>,
9195 peer2: crate::CudaSlice<f32>,
9196 omix2: crate::CudaSlice<f32>,
9197 peer_a: crate::CudaSlice<f32>,
9199 peer_b: crate::CudaSlice<f32>,
9200 omix_a: crate::CudaSlice<f32>,
9201 omix_b: crate::CudaSlice<f32>,
9202 ev_entry: CudaEvent,
9203 ev_rank: Vec<CudaEvent>,
9204 ev_root: CudaEvent,
9205 t_cap: usize,
9206 n_sel: usize,
9207 e_device: usize,
9208}
9209
9210fn nvfp4_trow_workspace_needs_grow(
9211 current: Option<(usize, usize)>,
9212 t: usize,
9213 n_sel: usize,
9214) -> bool {
9215 current.is_none_or(|(t_cap, n_sel_cap)| t_cap < t || n_sel_cap < n_sel)
9216}
9217
9218#[cfg(test)]
9219mod nvfp4_trow_workspace_tests {
9220 use super::nvfp4_trow_workspace_needs_grow;
9221
9222 #[test]
9223 fn workspace_grows_but_never_shrinks_between_spec_and_batch() {
9224 assert!(nvfp4_trow_workspace_needs_grow(None, 2, 16));
9225 assert!(nvfp4_trow_workspace_needs_grow(Some((2, 16)), 8, 64));
9226 assert!(!nvfp4_trow_workspace_needs_grow(Some((8, 64)), 2, 16));
9227 assert!(!nvfp4_trow_workspace_needs_grow(Some((32, 256)), 8, 64));
9228 }
9229}
9230
9231struct RoutesGraph {
9238 exec: cudarc::driver::sys::CUgraphExec,
9239 parent: cudarc::driver::sys::CUgraph,
9240 _children: Vec<cudarc::driver::CudaGraph>,
9241}
9242unsafe impl Send for RoutesGraph {}
9245
9246impl Drop for RoutesGraph {
9247 fn drop(&mut self) {
9248 unsafe {
9249 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
9250 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
9251 }
9252 }
9253}
9254
9255impl Nvfp4DeviceRoutesWorkspace {
9256 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
9257 self.in_stage_e.as_ref()
9258 }
9259 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9260 self.in_stage_e.as_mut()
9261 }
9262 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
9263 self.out_stage_e.as_mut()
9264 }
9265 pub(crate) fn arm_stages(
9267 &mut self,
9268 e: &Engine,
9269 width: usize,
9270 n_sel: usize,
9271 ) -> Result<(), Box<dyn std::error::Error>> {
9272 let _main = e.gpu.enter_main()?;
9273 if self.in_stage_e.is_none() {
9274 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9275 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9276 }
9277 if self.dev_route_e.is_none() {
9278 self.dev_route_e = Some((
9279 e.htod_i32(&vec![0i32; n_sel])?,
9280 e.htod(&vec![0.0f32; n_sel])?,
9281 ));
9282 }
9283 Ok(())
9284 }
9285
9286 pub(crate) fn in_and_out_stages_mut(
9288 &mut self,
9289 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9290 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9291 (Some(input), Some(output)) => Some((input, output)),
9292 _ => None,
9293 }
9294 }
9295 pub(crate) fn dev_route_e_mut(
9296 &mut self,
9297 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9298 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9299 }
9300}
9301
9302pub struct Nvfp4DeviceRoutesWorkspace {
9303 gate_out: Vec<crate::CudaSlice<f32>>,
9306 up_out: Vec<crate::CudaSlice<f32>>,
9307 act_q: Vec<crate::CudaSlice<i8>>,
9308 act_d: Vec<crate::CudaSlice<f32>>,
9309 sel: Vec<crate::CudaSlice<i32>>,
9310 partial: Vec<crate::CudaSlice<f32>>,
9311 accumulator: Vec<crate::CudaSlice<f32>>,
9312 combine_w: Vec<crate::CudaSlice<f32>>,
9314 route_w: Vec<crate::CudaSlice<f32>>,
9317 in_q: Vec<crate::CudaSlice<i8>>,
9320 in_d: Vec<crate::CudaSlice<f32>>,
9321 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9325 prestaged: bool,
9328 rank1_routed: bool,
9331 fence_flags_raw: u64,
9335 fence_ticket: u32,
9336 ev_input: Option<(CudaEvent, usize)>,
9338 in_stage_e: Option<crate::CudaSlice<f32>>,
9341 out_stage_e: Option<crate::CudaSlice<f32>>,
9342 routes_graph: Option<RoutesGraph>,
9343 raw_dev_route_e: Option<(u64, u64)>,
9345 raw_combine: Option<(u64, u64, u64, u64)>,
9346 raw_input: Vec<u64>,
9347 raw_sel: Vec<u64>,
9348 raw_route_w: Vec<u64>,
9349 remote: crate::CudaSlice<f32>,
9350 combined: crate::CudaSlice<f32>,
9351 n_sel: usize,
9352 input: Vec<crate::CudaSlice<f32>>,
9356 ev_rank: Vec<CudaEvent>,
9357 ev_done: Option<CudaEvent>,
9358 ev_entry: Option<(CudaEvent, usize)>,
9359}
9360
9361struct ResidentNvfp4EpRank {
9363 gate: Vec<crate::CudaSlice<u8>>,
9364 up: Vec<crate::CudaSlice<u8>>,
9365 down: Vec<crate::CudaSlice<u8>>,
9366 #[allow(dead_code)]
9367 expert_range: Range<usize>,
9368}
9369
9370pub struct ResidentNvfp4ExpertParallel {
9371 ranks: Vec<ResidentNvfp4EpRank>,
9372 macros_gate: Vec<f32>,
9373 macros_up: Vec<f32>,
9374 macros_down: Vec<f32>,
9375 pub expert_count: usize,
9376 pub input_width: usize,
9377 pub expert_width: usize,
9378 gate_row_bytes: usize,
9379 down_row_bytes: usize,
9380}
9381
9382fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9383 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9384 matrix.codes,
9385 matrix.scales,
9386 matrix.out_features,
9387 matrix.in_features,
9388 )
9389}
9390
9391fn nvfp4_row_bytes(in_features: usize) -> usize {
9392 in_features / 64 * 36 }
9394
9395pub(crate) fn fuse_rope_append_on() -> bool {
9403 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9404 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9405}
9406
9407pub(crate) fn no_local_shadow_on() -> bool {
9408 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9409 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9410}
9411
9412pub(crate) fn nvfp4_bank_v2_on() -> bool {
9413 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9414 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
9415}
9416
9417fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9421 let row_bytes = nvfp4_row_bytes(in_features);
9422 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
9423 let n_slots = in_features / 32;
9424 let mut out = Vec::with_capacity(v1.len());
9425 for row in 0..out_features {
9426 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
9427 for g in 0..n_slots {
9428 let (sblk, h) = (g / 2, g % 2);
9429 let b = &r[sblk * 36..sblk * 36 + 36];
9430 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
9431 }
9432 for g in 0..n_slots {
9433 let (sblk, h) = (g / 2, g % 2);
9434 let b = &r[sblk * 36..sblk * 36 + 36];
9435 out.push(b[2 * h]);
9436 out.push(b[2 * h + 1]);
9437 }
9438 }
9439 out
9440}
9441
9442fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9444 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9445 let v1 = nvfp4_repack_matrix(matrix);
9446 if nvfp4_bank_v2_on() {
9447 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9448 } else {
9449 v1
9450 }
9451}
9452
9453fn nvfp4_column_shard<'a>(
9456 matrix: Nvfp4BlockMatrix<'a>,
9457 tp: usize,
9458 rank: usize,
9459) -> Result<Nvfp4BlockMatrix<'a>, String> {
9460 if matrix.out_features % tp != 0 {
9461 return Err(format!(
9462 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
9463 matrix.out_features
9464 ));
9465 }
9466 let local_out = matrix.out_features / tp;
9467 let code_row = matrix.in_features / 2;
9468 let scale_row = matrix.in_features / 16;
9469 Ok(Nvfp4BlockMatrix {
9470 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
9471 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
9472 macro_scale: matrix.macro_scale,
9473 out_features: local_out,
9474 in_features: matrix.in_features,
9475 })
9476}
9477
9478fn nvfp4_row_shard(
9481 matrix: Nvfp4BlockMatrix<'_>,
9482 tp: usize,
9483 rank: usize,
9484) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
9485 if matrix.in_features % tp != 0 {
9486 return Err(format!(
9487 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
9488 matrix.in_features
9489 ));
9490 }
9491 let local_in = matrix.in_features / tp;
9492 if local_in % 64 != 0 {
9493 return Err(format!(
9494 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
9495 ));
9496 }
9497 let code_row = matrix.in_features / 2;
9498 let scale_row = matrix.in_features / 16;
9499 let local_code = local_in / 2;
9500 let local_scale = local_in / 16;
9501 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
9502 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
9503 for row in 0..matrix.out_features {
9504 let code_start = row * code_row + rank * local_code;
9505 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
9506 let scale_start = row * scale_row + rank * local_scale;
9507 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
9508 }
9509 Ok((codes, scales, local_in))
9510}
9511
9512fn run_rank_nvfp4(
9516 engine: &Engine,
9517 matrix: Nvfp4BlockMatrix<'_>,
9518 activations: &[f32],
9519 tokens: usize,
9520) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9521 matrix.validate()?;
9522 validate_activations(activations, tokens, matrix.in_features)?;
9523 let _main = engine.gpu.enter_main()?;
9524 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
9525 let activations = engine.htod(activations)?;
9526 let output = engine.qmatvec_nvfp4_fast(
9527 &blocks.slice(0..blocks.len()),
9528 &activations,
9529 tokens,
9530 matrix.in_features,
9531 matrix.out_features,
9532 nvfp4_row_bytes(matrix.in_features),
9533 )?;
9534 engine.dtoh(&output)
9535}
9536
9537fn upload_rank_nvfp4(
9538 engine: &Engine,
9539 matrix: Nvfp4BlockMatrix<'_>,
9540) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
9541 matrix.validate()?;
9542 let _main = engine.gpu.enter_main()?;
9543 Ok(ResidentNvfp4Rank {
9544 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
9545 macro_scale: matrix.macro_scale,
9546 out_features: matrix.out_features,
9547 in_features: matrix.in_features,
9548 row_bytes: nvfp4_row_bytes(matrix.in_features),
9549 })
9550}
9551
9552fn run_resident_rank_nvfp4(
9553 engine: &Engine,
9554 rank: &ResidentNvfp4Rank,
9555 activations: &[f32],
9556 tokens: usize,
9557) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9558 validate_activations(activations, tokens, rank.in_features)?;
9559 let _main = engine.gpu.enter_main()?;
9560 let activations = engine.htod(activations)?;
9561 let output = engine.qmatvec_nvfp4_fast(
9562 &rank.blocks.slice(0..rank.blocks.len()),
9563 &activations,
9564 tokens,
9565 rank.in_features,
9566 rank.out_features,
9567 rank.row_bytes,
9568 )?;
9569 engine.dtoh(&output)
9570}
9571
9572fn apply_macro(values: &mut [f32], macro_scale: f32) {
9573 for value in values.iter_mut() {
9574 *value *= macro_scale;
9575 }
9576}
9577
9578impl TpE4m3HostBounce {
9579 pub fn full_nvfp4(
9581 &self,
9582 matrix: Nvfp4BlockMatrix<'_>,
9583 activations: &[f32],
9584 tokens: usize,
9585 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9586 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
9587 apply_macro(&mut output, matrix.macro_scale);
9588 Ok(output)
9589 }
9590
9591 pub fn column_parallel_nvfp4(
9594 &self,
9595 matrix: Nvfp4BlockMatrix<'_>,
9596 activations: &[f32],
9597 tokens: usize,
9598 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
9599 matrix.validate()?;
9600 validate_activations(activations, tokens, matrix.in_features)?;
9601 let tp = self.ranks.len();
9602 let local_out = matrix.out_features / tp;
9603 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9604 let mut rank_outputs = Vec::with_capacity(tp);
9605 for (rank_index, rank) in self.ranks.iter().enumerate() {
9606 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
9607 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
9608 let row_start = rank_index * local_out;
9609 for token in 0..tokens {
9610 gathered[token * matrix.out_features + row_start
9611 ..token * matrix.out_features + row_start + local_out]
9612 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9613 }
9614 rank_outputs.push(output);
9615 }
9616 apply_macro(&mut gathered, matrix.macro_scale);
9617 Ok(ColumnParallelResult {
9618 gathered,
9619 rank_outputs,
9620 })
9621 }
9622
9623 pub fn row_parallel_nvfp4(
9626 &self,
9627 matrix: Nvfp4BlockMatrix<'_>,
9628 activations: &[f32],
9629 tokens: usize,
9630 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
9631 matrix.validate()?;
9632 validate_activations(activations, tokens, matrix.in_features)?;
9633 let tp = self.ranks.len();
9634 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9635 let mut rank_partials = Vec::with_capacity(tp);
9636 for (rank_index, rank) in self.ranks.iter().enumerate() {
9637 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
9638 let local_activations =
9639 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9640 let shard = Nvfp4BlockMatrix {
9641 codes: &codes,
9642 scales: &scales,
9643 macro_scale: matrix.macro_scale,
9644 out_features: matrix.out_features,
9645 in_features: local_in,
9646 };
9647 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
9648 for (sum, value) in reduced.iter_mut().zip(&partial) {
9649 *sum += *value;
9650 }
9651 rank_partials.push(partial);
9652 }
9653 apply_macro(&mut reduced, matrix.macro_scale);
9654 Ok(RowParallelResult {
9655 reduced,
9656 rank_partials,
9657 })
9658 }
9659
9660 pub fn upload_expert_nvfp4(
9661 &self,
9662 gate: Nvfp4BlockMatrix<'_>,
9663 up: Nvfp4BlockMatrix<'_>,
9664 down: Nvfp4BlockMatrix<'_>,
9665 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
9666 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9667 return Err("NVFP4 TP expert gate/up dimensions differ".into());
9668 }
9669 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9670 return Err(format!(
9671 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
9672 down.out_features, down.in_features, gate.out_features, gate.in_features
9673 )
9674 .into());
9675 }
9676 let tp = self.ranks.len();
9677 let mut gate_ranks = Vec::with_capacity(tp);
9678 let mut up_ranks = Vec::with_capacity(tp);
9679 let mut down_ranks = Vec::with_capacity(tp);
9680 for (rank_index, engine) in self.ranks.iter().enumerate() {
9681 gate_ranks.push(upload_rank_nvfp4(
9682 engine,
9683 nvfp4_column_shard(gate, tp, rank_index)?,
9684 )?);
9685 up_ranks.push(upload_rank_nvfp4(
9686 engine,
9687 nvfp4_column_shard(up, tp, rank_index)?,
9688 )?);
9689 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
9690 down_ranks.push(upload_rank_nvfp4(
9691 engine,
9692 Nvfp4BlockMatrix {
9693 codes: &codes,
9694 scales: &scales,
9695 macro_scale: down.macro_scale,
9696 out_features: down.out_features,
9697 in_features: local_in,
9698 },
9699 )?);
9700 }
9701 Ok(ResidentTpNvfp4Expert {
9702 gate: ResidentNvfp4ColumnParallel {
9703 ranks: gate_ranks,
9704 out_features: gate.out_features,
9705 in_features: gate.in_features,
9706 },
9707 up: ResidentNvfp4ColumnParallel {
9708 ranks: up_ranks,
9709 out_features: up.out_features,
9710 in_features: up.in_features,
9711 },
9712 down: ResidentNvfp4RowParallel {
9713 ranks: down_ranks,
9714 out_features: down.out_features,
9715 in_features: down.in_features,
9716 },
9717 input_width: gate.in_features,
9718 expert_width: gate.out_features,
9719 })
9720 }
9721
9722 fn column_parallel_resident_nvfp4(
9723 &self,
9724 matrix: &ResidentNvfp4ColumnParallel,
9725 activations: &[f32],
9726 tokens: usize,
9727 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9728 validate_activations(activations, tokens, matrix.in_features)?;
9729 let local_out = matrix.out_features / self.ranks.len();
9730 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9731 let mut macro_scale = None;
9732 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9733 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
9734 let row_start = rank_index * local_out;
9735 for token in 0..tokens {
9736 gathered[token * matrix.out_features + row_start
9737 ..token * matrix.out_features + row_start + local_out]
9738 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9739 }
9740 macro_scale = Some(shard.macro_scale);
9741 }
9742 apply_macro(
9743 &mut gathered,
9744 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
9745 );
9746 Ok(gathered)
9747 }
9748
9749 fn row_parallel_resident_nvfp4(
9750 &self,
9751 matrix: &ResidentNvfp4RowParallel,
9752 activations: &[f32],
9753 tokens: usize,
9754 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9755 validate_activations(activations, tokens, matrix.in_features)?;
9756 let tp = self.ranks.len();
9757 let local_in = matrix.in_features / tp;
9758 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9759 let mut macro_scale = None;
9760 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9761 if shard.in_features != local_in {
9762 return Err(format!(
9763 "NVFP4 resident row shard in_features {} != expected {local_in}",
9764 shard.in_features
9765 )
9766 .into());
9767 }
9768 let local_activations =
9769 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9770 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9771 for (sum, value) in reduced.iter_mut().zip(&partial) {
9772 *sum += *value;
9773 }
9774 macro_scale = Some(shard.macro_scale);
9775 }
9776 apply_macro(
9777 &mut reduced,
9778 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9779 );
9780 Ok(reduced)
9781 }
9782
9783 pub fn run_expert_nvfp4(
9784 &self,
9785 expert: &ResidentTpNvfp4Expert,
9786 input: &[f32],
9787 tokens: usize,
9788 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9789 validate_activations(input, tokens, expert.input_width)?;
9790 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9791 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9792 let activated: Vec<f32> = gate
9793 .iter()
9794 .zip(&up)
9795 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9796 .collect();
9797 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9798 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9799 }
9800
9801 pub fn upload_tensor_parallel_nvfp4(
9803 &self,
9804 gate: Nvfp4ExpertBank<'_>,
9805 up: Nvfp4ExpertBank<'_>,
9806 down: Nvfp4ExpertBank<'_>,
9807 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9808 gate.validate()?;
9809 up.validate()?;
9810 down.validate()?;
9811 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9812 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9813 }
9814 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9815 return Err("NVFP4 TP gate/up dimensions differ".into());
9816 }
9817 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9818 return Err(format!(
9819 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9820 down.out_features, down.in_features, gate.out_features, gate.in_features
9821 )
9822 .into());
9823 }
9824 let tp = self.ranks.len();
9825 if gate.out_features % tp != 0 {
9826 return Err(format!(
9827 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9828 gate.out_features
9829 )
9830 .into());
9831 }
9832 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9833 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9834 {
9835 return Err(format!(
9836 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9837 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9838 down.in_features
9839 )
9840 .into());
9841 }
9842 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9843 return Err(format!(
9844 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9845 ({NVFP4_CANONICAL_ROW_SHARDS})"
9846 )
9847 .into());
9848 }
9849
9850 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9851 let mut gate_ranks = Vec::with_capacity(tp);
9852 let mut up_ranks = Vec::with_capacity(tp);
9853 let mut macros_gate_dev = Vec::with_capacity(tp);
9854 let mut macros_up_dev = Vec::with_capacity(tp);
9855 let mut macros_down_dev = Vec::with_capacity(tp);
9856 for (rank_index, engine) in self.ranks.iter().enumerate() {
9857 let _main = engine.gpu.enter_main()?;
9858 let mut gate_host: Vec<u8> = Vec::new();
9864 let mut up_host: Vec<u8> = Vec::new();
9865 let mut owned = 0usize;
9866 for expert in 0..gate.expert_count {
9867 if ep2 {
9868 if expert % 2 != rank_index {
9869 continue;
9870 }
9871 owned += 1;
9872 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
9873 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
9874 } else {
9875 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9876 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
9877 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9878 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
9879 }
9880 }
9881 let bank_experts = if ep2 { owned } else { gate.expert_count };
9882 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9883 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9884 let local_out = if ep2 {
9885 gate.out_features
9886 } else {
9887 gate.out_features / tp
9888 };
9889 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9890 bank: engine.htod_bytes(&gate_host)?,
9891 expert_bytes: gate_expert_bytes,
9892 local_out,
9893 in_features: gate.in_features,
9894 row_bytes: nvfp4_row_bytes(gate.in_features),
9895 });
9896 up_ranks.push(ResidentNvfp4ColumnBankRank {
9897 bank: engine.htod_bytes(&up_host)?,
9898 expert_bytes: up_expert_bytes,
9899 local_out,
9900 in_features: up.in_features,
9901 row_bytes: nvfp4_row_bytes(up.in_features),
9902 });
9903 macros_gate_dev.push(engine.htod(gate.macros)?);
9904 macros_up_dev.push(engine.htod(up.macros)?);
9905 macros_down_dev.push(engine.htod(down.macros)?);
9906 }
9907 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9911 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9912 let device_rank = shard_index % tp;
9913 let engine = &self.ranks[device_rank];
9914 let _main = engine.gpu.enter_main()?;
9915 let mut down_host: Vec<u8> = Vec::new();
9916 let mut owned = 0usize;
9917 for expert in 0..down.expert_count {
9918 let down_matrix = down.expert(expert)?;
9919 if ep2 {
9920 if expert % 2 != device_rank {
9923 continue;
9924 }
9925 owned += 1;
9926 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
9927 } else {
9928 let (codes, scales, local_in) =
9929 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9930 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
9931 codes: &codes,
9932 scales: &scales,
9933 macro_scale: down_matrix.macro_scale,
9934 out_features: down_matrix.out_features,
9935 in_features: local_in,
9936 }));
9937 }
9938 }
9939 let bank_experts = if ep2 { owned } else { down.expert_count };
9940 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9941 let local_in = if ep2 {
9942 down.in_features
9943 } else {
9944 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9945 };
9946 down_ranks.push(ResidentNvfp4RowBankRank {
9947 bank: engine.htod_bytes(&down_host)?,
9948 expert_bytes: down_expert_bytes,
9949 device_rank,
9950 out_features: down.out_features,
9951 local_in,
9952 row_bytes: nvfp4_row_bytes(local_in),
9953 });
9954 }
9955 Ok(ResidentNvfp4TensorParallel {
9956 gate: gate_ranks,
9957 up: up_ranks,
9958 down: down_ranks,
9959 macros_gate: gate.macros.to_vec(),
9960 macros_up: up.macros.to_vec(),
9961 macros_down: down.macros.to_vec(),
9962 macros_gate_dev,
9963 macros_up_dev,
9964 macros_down_dev,
9965 expert_count: gate.expert_count,
9966 input_width: gate.in_features,
9967 expert_width: gate.out_features,
9968 device_workspace: std::sync::Mutex::new(None),
9969 prime_tables: std::sync::Mutex::new(Vec::new()),
9970 t2_workspace: std::sync::Mutex::new(None),
9971 ep2,
9972 })
9973 }
9974
9975 fn run_full_bank_expert_nvfp4(
9979 &self,
9980 ranks: &[ResidentNvfp4ColumnBankRank],
9981 macros: &[f32],
9982 expert: usize,
9983 input: &[f32],
9984 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9985 let owner = expert & 1;
9986 let slot = expert >> 1;
9987 let bank = ranks
9988 .get(owner)
9989 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9990 let engine = &self.ranks[owner];
9991 let _main = engine.gpu.enter_main()?;
9992 let activations = engine.htod(input)?;
9993 let output = if nvfp4_bank_v2_on() {
9994 engine.qmatvec_nvfp4_fast_v2(
9995 &bank.expert(slot),
9996 &activations,
9997 1,
9998 bank.in_features,
9999 bank.local_out,
10000 bank.row_bytes,
10001 )?
10002 } else {
10003 engine.qmatvec_nvfp4_fast(
10004 &bank.expert(slot),
10005 &activations,
10006 1,
10007 bank.in_features,
10008 bank.local_out,
10009 bank.row_bytes,
10010 )?
10011 };
10012 let mut out = engine.dtoh(&output)?;
10013 apply_macro(&mut out, macros[expert]);
10014 Ok(out)
10015 }
10016
10017 fn run_full_down_expert_nvfp4(
10020 &self,
10021 shards: &[ResidentNvfp4RowBankRank],
10022 macros: &[f32],
10023 expert: usize,
10024 input: &[f32],
10025 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10026 let owner = expert & 1;
10027 let slot = expert >> 1;
10028 let shard = shards
10029 .get(owner)
10030 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
10031 let engine = &self.ranks[owner];
10032 let _main = engine.gpu.enter_main()?;
10033 let activations = engine.htod(input)?;
10034 let output = if nvfp4_bank_v2_on() {
10035 engine.qmatvec_nvfp4_fast_v2(
10036 &shard.expert(slot),
10037 &activations,
10038 1,
10039 shard.local_in,
10040 shard.out_features,
10041 shard.row_bytes,
10042 )?
10043 } else {
10044 engine.qmatvec_nvfp4_fast(
10045 &shard.expert(slot),
10046 &activations,
10047 1,
10048 shard.local_in,
10049 shard.out_features,
10050 shard.row_bytes,
10051 )?
10052 };
10053 let mut out = engine.dtoh(&output)?;
10054 apply_macro(&mut out, macros[expert]);
10055 Ok(out)
10056 }
10057
10058 fn run_column_bank_expert_nvfp4(
10059 &self,
10060 ranks: &[ResidentNvfp4ColumnBankRank],
10061 macros: &[f32],
10062 expert: usize,
10063 input: &[f32],
10064 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10065 let local_out = ranks
10066 .first()
10067 .ok_or("NVFP4 TP column bank has no ranks")?
10068 .local_out;
10069 let mut gathered = vec![0.0f32; local_out * ranks.len()];
10070 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
10071 let _main = engine.gpu.enter_main()?;
10072 let activations = engine.htod(input)?;
10073 let output = if nvfp4_bank_v2_on() {
10074 engine.qmatvec_nvfp4_fast_v2(
10075 &bank.expert(expert),
10076 &activations,
10077 1,
10078 bank.in_features,
10079 bank.local_out,
10080 bank.row_bytes,
10081 )?
10082 } else {
10083 engine.qmatvec_nvfp4_fast(
10084 &bank.expert(expert),
10085 &activations,
10086 1,
10087 bank.in_features,
10088 bank.local_out,
10089 bank.row_bytes,
10090 )?
10091 };
10092 let output = engine.dtoh(&output)?;
10093 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
10094 }
10095 apply_macro(&mut gathered, macros[expert]);
10096 Ok(gathered)
10097 }
10098
10099 fn run_row_bank_expert_nvfp4(
10103 &self,
10104 shards: &[ResidentNvfp4RowBankRank],
10105 macros: &[f32],
10106 expert: usize,
10107 input: &[f32],
10108 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10109 let out_features = shards
10110 .first()
10111 .ok_or("NVFP4 TP row bank has no canonical shards")?
10112 .out_features;
10113 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
10114 let mut reduced = vec![0.0f32; out_features];
10115 for (shard_index, shard) in shards.iter().enumerate() {
10116 let engine = self
10117 .ranks
10118 .get(shard.device_rank)
10119 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
10120 let _main = engine.gpu.enter_main()?;
10121 let local_activations =
10122 activation_shard(input, 1, in_features, shards.len(), shard_index);
10123 let activations = engine.htod(&local_activations)?;
10124 let output = if nvfp4_bank_v2_on() {
10125 engine.qmatvec_nvfp4_fast_v2(
10126 &shard.expert(expert),
10127 &activations,
10128 1,
10129 shard.local_in,
10130 shard.out_features,
10131 shard.row_bytes,
10132 )?
10133 } else {
10134 engine.qmatvec_nvfp4_fast(
10135 &shard.expert(expert),
10136 &activations,
10137 1,
10138 shard.local_in,
10139 shard.out_features,
10140 shard.row_bytes,
10141 )?
10142 };
10143 let partial = engine.dtoh(&output)?;
10144 for (sum, value) in reduced.iter_mut().zip(&partial) {
10145 *sum += *value;
10146 }
10147 }
10148 apply_macro(&mut reduced, macros[expert]);
10149 Ok(reduced)
10150 }
10151
10152 pub fn upload_expert_parallel_nvfp4(
10156 &self,
10157 gate: Nvfp4ExpertBank<'_>,
10158 up: Nvfp4ExpertBank<'_>,
10159 down: Nvfp4ExpertBank<'_>,
10160 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
10161 gate.validate()?;
10162 up.validate()?;
10163 down.validate()?;
10164 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
10165 return Err("NVFP4 EP gate/up/down expert counts differ".into());
10166 }
10167 if gate.in_features != up.in_features || gate.out_features != up.out_features {
10168 return Err("NVFP4 EP gate/up dimensions differ".into());
10169 }
10170 if down.in_features != gate.out_features || down.out_features != gate.in_features {
10171 return Err(format!(
10172 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
10173 down.out_features, down.in_features, gate.out_features, gate.in_features
10174 )
10175 .into());
10176 }
10177 let world = self.ranks.len();
10178 if gate.expert_count % world != 0 {
10179 return Err(format!(
10180 "NVFP4 EP expert count {} is not divisible by {world} ranks",
10181 gate.expert_count
10182 )
10183 .into());
10184 }
10185 let experts_per_rank = gate.expert_count / world;
10186 let mut ranks = Vec::with_capacity(world);
10187 for (rank_index, engine) in self.ranks.iter().enumerate() {
10188 let _main = engine.gpu.enter_main()?;
10189 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
10190 let mut gate_experts = Vec::with_capacity(experts_per_rank);
10191 let mut up_experts = Vec::with_capacity(experts_per_rank);
10192 let mut down_experts = Vec::with_capacity(experts_per_rank);
10193 for expert in expert_range.clone() {
10194 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
10195 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
10196 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
10197 }
10198 ranks.push(ResidentNvfp4EpRank {
10199 gate: gate_experts,
10200 up: up_experts,
10201 down: down_experts,
10202 expert_range,
10203 });
10204 }
10205 Ok(ResidentNvfp4ExpertParallel {
10206 ranks,
10207 macros_gate: gate.macros.to_vec(),
10208 macros_up: up.macros.to_vec(),
10209 macros_down: down.macros.to_vec(),
10210 expert_count: gate.expert_count,
10211 input_width: gate.in_features,
10212 expert_width: gate.out_features,
10213 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
10214 down_row_bytes: nvfp4_row_bytes(down.in_features),
10215 })
10216 }
10217
10218 #[allow(clippy::too_many_arguments)]
10224 pub fn run_routed_experts_nvfp4(
10225 &self,
10226 experts: &ResidentNvfp4ExpertParallel,
10227 input: &[f32],
10228 tokens: usize,
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, tokens, experts.input_width)?;
10235 let pairs = tokens
10236 .checked_mul(experts_per_token)
10237 .ok_or("NVFP4 EP route count overflow")?;
10238 if selected.len() != pairs || route_weights.len() != pairs {
10239 return Err(format!(
10240 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
10241 {experts_per_token} ({pairs})",
10242 selected.len(),
10243 route_weights.len(),
10244 )
10245 .into());
10246 }
10247 if !route_weights.iter().all(|weight| weight.is_finite()) {
10248 return Err("NVFP4 EP route weights contain a non-finite value".into());
10249 }
10250 let experts_per_rank = experts.expert_count / experts.ranks.len();
10251 let mut output = vec![0.0f32; tokens * experts.input_width];
10252 for token in 0..tokens {
10253 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
10254 for slot in 0..experts_per_token {
10255 let pair = token * experts_per_token + slot;
10256 let expert = selected[pair];
10257 if expert >= experts.expert_count {
10258 return Err(format!(
10259 "NVFP4 EP selected expert {expert} outside 0..{}",
10260 experts.expert_count
10261 )
10262 .into());
10263 }
10264 let owner = expert / experts_per_rank;
10265 let local = expert - owner * experts_per_rank;
10266 let rank = &experts.ranks[owner];
10267 let engine = &self.ranks[owner];
10268 let _main = engine.gpu.enter_main()?;
10269 let device_input = engine.htod(input_row)?;
10270 let gate_out = engine.qmatvec_nvfp4_fast(
10271 &rank.gate[local].slice(0..rank.gate[local].len()),
10272 &device_input,
10273 1,
10274 experts.input_width,
10275 experts.expert_width,
10276 experts.gate_row_bytes,
10277 )?;
10278 let up_out = engine.qmatvec_nvfp4_fast(
10279 &rank.up[local].slice(0..rank.up[local].len()),
10280 &device_input,
10281 1,
10282 experts.input_width,
10283 experts.expert_width,
10284 experts.gate_row_bytes,
10285 )?;
10286 let mut gate_host = engine.dtoh(&gate_out)?;
10287 let mut up_host = engine.dtoh(&up_out)?;
10288 apply_macro(&mut gate_host, experts.macros_gate[expert]);
10289 apply_macro(&mut up_host, experts.macros_up[expert]);
10290 let activated: Vec<f32> = gate_host
10291 .iter()
10292 .zip(&up_host)
10293 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
10294 .collect();
10295 let device_activated = engine.htod(&activated)?;
10296 let down_out = engine.qmatvec_nvfp4_fast(
10297 &rank.down[local].slice(0..rank.down[local].len()),
10298 &device_activated,
10299 1,
10300 experts.expert_width,
10301 experts.input_width,
10302 experts.down_row_bytes,
10303 )?;
10304 let mut down_host = engine.dtoh(&down_out)?;
10305 apply_macro(&mut down_host, experts.macros_down[expert]);
10306 let weight = route_weights[pair];
10307 for (sum, value) in output
10308 [token * experts.input_width..(token + 1) * experts.input_width]
10309 .iter_mut()
10310 .zip(down_host)
10311 {
10312 *sum += weight * value;
10313 }
10314 }
10315 }
10316 Ok(output)
10317 }
10318
10319 pub fn run_tensor_parallel_routes_nvfp4_device(
10333 &self,
10334 experts: &ResidentNvfp4TensorParallel,
10335 input: &[f32],
10336 selected: &[usize],
10337 route_weights: &[f32],
10338 experts_per_token: usize,
10339 activation_limit: Option<f32>,
10340 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10341 validate_activations(input, 1, experts.input_width)?;
10342 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10343 return Err(format!(
10344 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
10345 selected.len(),
10346 route_weights.len(),
10347 )
10348 .into());
10349 }
10350 if !route_weights.iter().all(|weight| weight.is_finite()) {
10351 return Err("NVFP4 device route weights contain a non-finite value".into());
10352 }
10353 let world = self.ranks.len();
10354 if world != NVFP4_CANONICAL_ROW_SHARDS {
10355 return Err(format!(
10356 "NVFP4 device routes require world == canonical shard grid \
10357 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10358 )
10359 .into());
10360 }
10361 let local_out = if experts.ep2 {
10362 experts.expert_width
10363 } else {
10364 experts.expert_width / world
10365 };
10366
10367 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10371 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10372 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10373 let started = timing.then(std::time::Instant::now);
10374
10375 let n_sel = experts_per_token;
10376 let mut workspace_guard = experts
10377 .device_workspace
10378 .lock()
10379 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10380 if workspace_guard.is_none() {
10381 let mut gate_out = Vec::with_capacity(world);
10382 let mut up_out = Vec::with_capacity(world);
10383 let mut act_q = Vec::with_capacity(world);
10384 let mut act_d = Vec::with_capacity(world);
10385 let mut sel = Vec::with_capacity(world);
10386 let mut partial = Vec::with_capacity(world);
10387 let mut accumulator = Vec::with_capacity(world);
10388 let mut combine_w = Vec::with_capacity(world);
10389 let mut route_w = Vec::with_capacity(world);
10390 let mut in_q = Vec::with_capacity(world);
10391 let mut in_d = Vec::with_capacity(world);
10392 let mut input = Vec::with_capacity(world);
10393 let mut ev_rank = Vec::with_capacity(world);
10394 let moe_direct = moe_direct_on();
10395 for (rank, engine) in self.ranks.iter().enumerate() {
10396 let _main = engine.gpu.enter_main()?;
10397 gate_out.push(engine.uninit(n_sel * local_out)?);
10398 up_out.push(engine.uninit(n_sel * local_out)?);
10399 act_q.push(engine.uninit_i8(n_sel * local_out)?);
10400 act_d.push(engine.uninit(n_sel * local_out / 32)?);
10401 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
10402 partial.push(engine.uninit(n_sel * experts.input_width)?);
10403 if moe_direct && rank != 0 {
10405 let root = &self.ranks[0];
10406 let _root_main = root.gpu.enter_main()?;
10407 accumulator.push(root.zeros(experts.input_width)?);
10408 } else {
10409 accumulator.push(engine.zeros(experts.input_width)?);
10410 }
10411 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10412 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10413 in_q.push(engine.uninit_i8(experts.input_width)?);
10414 in_d.push(engine.uninit(experts.input_width / 32)?);
10415 input.push(engine.uninit(experts.input_width)?);
10416 ev_rank.push(engine.ctx().new_event(None)?);
10417 }
10418 let root = &self.ranks[0];
10419 let _main = root.gpu.enter_main()?;
10420 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
10421 prestaged: false,
10422 rank1_routed: false,
10423 ev_input: None,
10424 fence_flags_raw: 0,
10425 fence_ticket: 0,
10426 gate_out,
10427 up_out,
10428 act_q,
10429 act_d,
10430 sel,
10431 partial,
10432 accumulator,
10433 combine_w,
10434 route_w,
10435 in_q,
10436 in_d,
10437 dev_route_e: None,
10438 in_stage_e: None,
10439 out_stage_e: None,
10440 routes_graph: None,
10441 raw_dev_route_e: None,
10442 raw_combine: None,
10443 raw_input: Vec::new(),
10444 raw_sel: Vec::new(),
10445 raw_route_w: Vec::new(),
10446 remote: root.uninit(experts.input_width)?,
10447 combined: root.uninit(experts.input_width)?,
10448 n_sel,
10449 input,
10450 ev_rank,
10451 ev_done: Some(root.ctx().new_event(None)?),
10452 ev_entry: None,
10453 });
10454 }
10455 let workspace = workspace_guard
10456 .as_mut()
10457 .expect("NVFP4 device routes workspace initialized above");
10458 if experts.ep2 {
10461 return Ok(vec![0.0f32; experts.input_width]);
10462 }
10463 if workspace.n_sel != n_sel {
10464 return Err(format!(
10465 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10466 workspace.n_sel
10467 )
10468 .into());
10469 }
10470 for &expert in selected {
10471 if expert >= experts.expert_count {
10472 return Err(format!(
10473 "NVFP4 device selected expert {expert} outside 0..{}",
10474 experts.expert_count
10475 )
10476 .into());
10477 }
10478 }
10479 let sel_i32 = selected
10480 .iter()
10481 .map(|&expert| expert as i32)
10482 .collect::<Vec<_>>();
10483
10484 for (rank_index, engine) in self.ranks.iter().enumerate() {
10491 let _main = engine.gpu.enter_main()?;
10492 let device_input = engine.htod(input)?;
10493 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
10494 engine.quantize_q8_1_into(
10495 &device_input,
10496 1,
10497 experts.input_width,
10498 &mut in_q[rank_index],
10499 &mut in_d[rank_index],
10500 )?;
10501 }
10503 self.nvfp4_routes_batched_sweeps(
10504 experts,
10505 workspace,
10506 selected,
10507 route_weights,
10508 &sel_i32,
10509 local_out,
10510 n_sel,
10511 activation_limit,
10512 false,
10513 )?;
10514
10515 let root = &self.ranks[0];
10518 for engine in &self.ranks[1..] {
10519 let _main = engine.gpu.enter_main()?;
10520 engine.stream().synchronize()?;
10521 }
10522 let _main = root.gpu.enter_main()?;
10523 root.stream()
10524 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10525 root.add(
10526 &workspace.accumulator[0],
10527 &workspace.remote,
10528 &mut workspace.combined,
10529 experts.input_width,
10530 )?;
10531 let output = root.dtoh(&workspace.combined)?;
10532 if let Some(started) = started {
10533 use std::sync::atomic::Ordering;
10534 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10535 + started.elapsed().as_nanos() as u64;
10536 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10537 if calls % 430 == 0 {
10538 eprintln!(
10539 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10540 ns as f64 / 1.0e6,
10541 ns as f64 / calls as f64 / 1.0e3,
10542 );
10543 }
10544 }
10545 Ok(output)
10546 }
10547
10548 #[allow(clippy::too_many_arguments)]
10553 fn nvfp4_routes_batched_sweeps(
10554 &self,
10555 experts: &ResidentNvfp4TensorParallel,
10556 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10557 selected: &[usize],
10558 route_weights: &[f32],
10559 sel_i32: &[i32],
10560 local_out: usize,
10561 n_sel: usize,
10562 activation_limit: Option<f32>,
10563 device_routed: bool,
10564 ) -> Result<(), Box<dyn std::error::Error>> {
10565 for rank_index in 0..self.ranks.len() {
10566 self.nvfp4_routes_batched_sweeps_rank(
10567 experts,
10568 workspace,
10569 selected,
10570 route_weights,
10571 sel_i32,
10572 local_out,
10573 n_sel,
10574 activation_limit,
10575 device_routed,
10576 rank_index,
10577 )?;
10578 }
10579 Ok(())
10580 }
10581
10582 #[allow(clippy::too_many_arguments)]
10585 fn nvfp4_routes_batched_sweeps_rank(
10586 &self,
10587 experts: &ResidentNvfp4TensorParallel,
10588 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10589 selected: &[usize],
10590 route_weights: &[f32],
10591 sel_i32: &[i32],
10592 local_out: usize,
10593 n_sel: usize,
10594 activation_limit: Option<f32>,
10595 device_routed: bool,
10596 rank_index: usize,
10597 ) -> Result<(), Box<dyn std::error::Error>> {
10598 {
10599 let engine = &self.ranks[rank_index];
10600 let _main = engine.gpu.enter_main()?;
10601 if experts.ep2 {
10606 if !device_routed {
10607 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
10608 }
10609 let gate_bank = &experts.gate[rank_index];
10610 let up_bank = &experts.up[rank_index];
10611 if gate_bank.local_out != experts.expert_width
10612 || gate_bank.expert_bytes != up_bank.expert_bytes
10613 {
10614 return Err("NVFP4 EP2 bank geometry drifted".into());
10615 }
10616 {
10617 let Nvfp4DeviceRoutesWorkspace {
10618 sel,
10619 gate_out,
10620 up_out,
10621 in_q,
10622 in_d,
10623 ..
10624 } = &mut *workspace;
10625 engine.qmatvec_nvfp4_sel_gu_ep_into(
10626 &gate_bank.bank,
10627 &up_bank.bank,
10628 &sel[rank_index],
10629 &in_q[rank_index],
10630 &in_d[rank_index],
10631 &mut gate_out[rank_index],
10632 &mut up_out[rank_index],
10633 n_sel,
10634 gate_bank.in_features,
10635 gate_bank.local_out,
10636 gate_bank.row_bytes,
10637 gate_bank.expert_bytes,
10638 rank_index,
10639 )?;
10640 }
10641 {
10642 let Nvfp4DeviceRoutesWorkspace {
10643 gate_out,
10644 up_out,
10645 sel,
10646 act_q,
10647 act_d,
10648 ..
10649 } = &mut *workspace;
10650 engine.silu_mul_scaled_q8_1_sel_ep_into(
10651 &gate_out[rank_index],
10652 &up_out[rank_index],
10653 &experts.macros_gate_dev[rank_index],
10654 &experts.macros_up_dev[rank_index],
10655 &sel[rank_index],
10656 activation_limit,
10657 &mut act_q[rank_index],
10658 &mut act_d[rank_index],
10659 local_out,
10660 n_sel,
10661 rank_index,
10662 )?;
10663 }
10664 let shard = &experts.down[rank_index];
10665 if shard.device_rank != rank_index || shard.local_in != local_out {
10666 return Err("NVFP4 EP2 down bank placement drifted".into());
10667 }
10668 {
10669 let Nvfp4DeviceRoutesWorkspace {
10670 sel,
10671 act_q,
10672 act_d,
10673 route_w,
10674 accumulator,
10675 ..
10676 } = &mut *workspace;
10677 engine.qmatvec_nvfp4_sel_down8_ep_into(
10678 &shard.bank,
10679 &sel[rank_index],
10680 &act_q[rank_index],
10681 &act_d[rank_index],
10682 &route_w[rank_index],
10683 &experts.macros_down_dev[rank_index],
10684 &mut accumulator[rank_index],
10685 n_sel,
10686 shard.local_in,
10687 shard.out_features,
10688 shard.row_bytes,
10689 shard.expert_bytes,
10690 local_out,
10691 local_out / 32,
10692 rank_index,
10693 )?;
10694 }
10695 return Ok(());
10696 }
10697 if !device_routed {
10698 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
10699 let folded = (0..n_sel)
10702 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
10703 .collect::<Vec<_>>();
10704 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
10705 engine.stream().memcpy_htod(&folded, &mut view)?;
10706 }
10707 let gate_bank = &experts.gate[rank_index];
10708 let up_bank = &experts.up[rank_index];
10709 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
10710 let gu_fused = nvfp4_bank_v2_on()
10713 && gate_bank.in_features == up_bank.in_features
10714 && gate_bank.local_out == up_bank.local_out
10715 && gate_bank.row_bytes == up_bank.row_bytes
10716 && gate_bank.expert_bytes == up_bank.expert_bytes;
10717 if gu_fused {
10718 let Nvfp4DeviceRoutesWorkspace {
10719 sel,
10720 gate_out,
10721 up_out,
10722 in_q,
10723 in_d,
10724 ..
10725 } = &mut *workspace;
10726 engine.qmatvec_nvfp4_sel_gu_into(
10727 &gate_bank.bank,
10728 &up_bank.bank,
10729 &sel[rank_index],
10730 &in_q[rank_index],
10731 &in_d[rank_index],
10732 &mut gate_out[rank_index],
10733 &mut up_out[rank_index],
10734 n_sel,
10735 gate_bank.in_features,
10736 gate_bank.local_out,
10737 gate_bank.row_bytes,
10738 gate_bank.expert_bytes,
10739 )?;
10740 } else {
10741 engine.qmatvec_nvfp4_sel_into(
10742 &gate_bank.bank,
10743 &workspace.sel[rank_index],
10744 aq,
10745 ad,
10746 &mut workspace.gate_out[rank_index],
10747 n_sel,
10748 gate_bank.in_features,
10749 gate_bank.local_out,
10750 gate_bank.row_bytes,
10751 gate_bank.expert_bytes,
10752 0,
10753 0,
10754 )?;
10755 engine.qmatvec_nvfp4_sel_into(
10756 &up_bank.bank,
10757 &workspace.sel[rank_index],
10758 aq,
10759 ad,
10760 &mut workspace.up_out[rank_index],
10761 n_sel,
10762 up_bank.in_features,
10763 up_bank.local_out,
10764 up_bank.row_bytes,
10765 up_bank.expert_bytes,
10766 0,
10767 0,
10768 )?;
10769 }
10770 {
10774 let Nvfp4DeviceRoutesWorkspace {
10775 gate_out,
10776 up_out,
10777 sel,
10778 act_q,
10779 act_d,
10780 ..
10781 } = &mut *workspace;
10782 engine.silu_mul_scaled_q8_1_sel_into(
10783 &gate_out[rank_index],
10784 &up_out[rank_index],
10785 &experts.macros_gate_dev[rank_index],
10786 &experts.macros_up_dev[rank_index],
10787 &sel[rank_index],
10788 activation_limit,
10789 &mut act_q[rank_index],
10790 &mut act_d[rank_index],
10791 local_out,
10792 n_sel,
10793 )?;
10794 }
10795 let shard = &experts.down[rank_index];
10796 if shard.device_rank != rank_index || shard.local_in != local_out {
10797 return Err(
10798 "NVFP4 device routes: down canonical shard placement drifted from \
10799 the gate/up column split"
10800 .into(),
10801 );
10802 }
10803 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
10810 {
10811 static SEEN: std::sync::Mutex<Vec<(bool, bool)>> =
10815 std::sync::Mutex::new(Vec::new());
10816 if std::env::var("MEMRA_SWEEP_TRACE").as_deref() == Ok("1") {
10817 let mut seen = SEEN.lock().unwrap();
10818 if !seen.contains(&(down8, device_routed)) {
10819 seen.push((down8, device_routed));
10820 eprintln!(
10821 "[sweep-trace] down8={down8} device_routed={device_routed} \
10822 sel_down8_on={} local_in={} n_sel={n_sel}",
10823 sel_down8_on(),
10824 shard.local_in
10825 );
10826 }
10827 }
10828 }
10829 if down8 {
10830 let Nvfp4DeviceRoutesWorkspace {
10831 sel,
10832 act_q,
10833 act_d,
10834 route_w,
10835 accumulator,
10836 ..
10837 } = &mut *workspace;
10838 engine.qmatvec_nvfp4_sel_down8_into(
10839 &shard.bank,
10840 &sel[rank_index],
10841 &act_q[rank_index],
10842 &act_d[rank_index],
10843 &route_w[rank_index],
10844 &experts.macros_down_dev[rank_index],
10845 &mut accumulator[rank_index],
10846 n_sel,
10847 shard.local_in,
10848 shard.out_features,
10849 shard.row_bytes,
10850 shard.expert_bytes,
10851 local_out,
10852 local_out / 32,
10853 )?;
10854 } else {
10855 let Nvfp4DeviceRoutesWorkspace {
10856 sel,
10857 act_q,
10858 act_d,
10859 partial,
10860 ..
10861 } = &mut *workspace;
10862 engine.qmatvec_nvfp4_sel_into(
10863 &shard.bank,
10864 &sel[rank_index],
10865 &act_q[rank_index],
10866 &act_d[rank_index],
10867 &mut partial[rank_index],
10868 n_sel,
10869 shard.local_in,
10870 shard.out_features,
10871 shard.row_bytes,
10872 shard.expert_bytes,
10873 local_out,
10874 local_out / 32,
10875 )?;
10876 }
10877 if !down8 {
10882 let Nvfp4DeviceRoutesWorkspace {
10883 partial,
10884 combine_w,
10885 route_w,
10886 sel,
10887 accumulator,
10888 ..
10889 } = &mut *workspace;
10890 if device_routed {
10891 engine.axpy_rows_seq_md_into(
10892 &partial[rank_index],
10893 &route_w[rank_index],
10894 &experts.macros_down_dev[rank_index],
10895 &sel[rank_index],
10896 &mut accumulator[rank_index],
10897 experts.input_width,
10898 n_sel,
10899 )?;
10900 } else {
10901 engine.axpy_rows_seq_into(
10902 &partial[rank_index],
10903 &combine_w[rank_index],
10904 &mut accumulator[rank_index],
10905 experts.input_width,
10906 n_sel,
10907 )?;
10908 }
10909 }
10910 }
10911 Ok(())
10912 }
10913
10914 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10922 &self,
10923 experts: &ResidentNvfp4TensorParallel,
10924 e: &Engine,
10925 input_dev: &crate::CudaSlice<f32>,
10926 selected: &[usize],
10927 route_weights: &[f32],
10928 experts_per_token: usize,
10929 activation_limit: Option<f32>,
10930 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10931 if input_dev.len() != experts.input_width {
10932 return Err(format!(
10933 "NVFP4 device-io routes input {} != width {}",
10934 input_dev.len(),
10935 experts.input_width
10936 )
10937 .into());
10938 }
10939 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10940 return Err(format!(
10941 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10942 selected.len(),
10943 route_weights.len(),
10944 )
10945 .into());
10946 }
10947 if !route_weights.iter().all(|weight| weight.is_finite()) {
10948 return Err("NVFP4 device route weights contain a non-finite value".into());
10949 }
10950 let world = self.ranks.len();
10951 if world != NVFP4_CANONICAL_ROW_SHARDS {
10952 return Err(format!(
10953 "NVFP4 device routes require world == canonical shard grid \
10954 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10955 )
10956 .into());
10957 }
10958 let local_out = experts.expert_width / world;
10959 let n_sel = experts_per_token;
10960
10961 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10962 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10963 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10964 let started = timing.then(std::time::Instant::now);
10965
10966 let mut workspace_guard = experts
10967 .device_workspace
10968 .lock()
10969 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10970 if workspace_guard.is_none() {
10971 drop(workspace_guard);
10972 let zero = vec![0.0f32; experts.input_width];
10975 let zero_sel = vec![0usize; n_sel];
10976 let zero_w = vec![0.0f32; n_sel];
10977 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10978 experts,
10979 &zero,
10980 &zero_sel,
10981 &zero_w,
10982 n_sel,
10983 activation_limit,
10984 )?;
10985 workspace_guard = experts
10986 .device_workspace
10987 .lock()
10988 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10989 }
10990 let workspace = workspace_guard
10991 .as_mut()
10992 .expect("NVFP4 device routes workspace initialized above");
10993 if workspace.n_sel != n_sel {
10994 return Err(format!(
10995 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10996 workspace.n_sel
10997 )
10998 .into());
10999 }
11000 for &expert in selected {
11001 if expert >= experts.expert_count {
11002 return Err(format!(
11003 "NVFP4 device selected expert {expert} outside 0..{}",
11004 experts.expert_count
11005 )
11006 .into());
11007 }
11008 }
11009 let sel_i32 = selected
11010 .iter()
11011 .map(|&expert| expert as i32)
11012 .collect::<Vec<_>>();
11013
11014 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11018 if *device != e.ctx().ordinal() {
11019 return Err("NVFP4 device-io routes engine changed".into());
11020 }
11021 } else {
11022 let _main = e.gpu.enter_main()?;
11023 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11024 }
11025 {
11026 let _main = e.gpu.enter_main()?;
11027 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11028 ev_entry.record(&e.stream())?;
11029 }
11030 for (rank_index, engine) in self.ranks.iter().enumerate() {
11031 let _main = engine.gpu.enter_main()?;
11032 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11033 engine.stream().wait(ev_entry)?;
11034 {
11035 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11036 engine
11037 .stream()
11038 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11039 }
11040 {
11041 let Nvfp4DeviceRoutesWorkspace {
11042 input, in_q, in_d, ..
11043 } = &mut *workspace;
11044 engine.quantize_q8_1_into(
11045 &input[rank_index],
11046 1,
11047 experts.input_width,
11048 &mut in_q[rank_index],
11049 &mut in_d[rank_index],
11050 )?;
11051 }
11052 }
11053 self.nvfp4_routes_batched_sweeps(
11054 experts,
11055 workspace,
11056 selected,
11057 route_weights,
11058 &sel_i32,
11059 local_out,
11060 n_sel,
11061 activation_limit,
11062 false,
11063 )?;
11064
11065 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11071 let _main = engine.gpu.enter_main()?;
11072 workspace.ev_rank[rank_index].record(&engine.stream())?;
11073 }
11074 if moe_direct_on() && self.ranks.len() == 2 {
11075 {
11082 let root = &self.ranks[0];
11083 let _main = root.gpu.enter_main()?;
11084 workspace
11085 .ev_done
11086 .as_ref()
11087 .expect("device routes done event")
11088 .record(&root.stream())?;
11089 }
11090 let _main = e.gpu.enter_main()?;
11091 e.stream().wait(
11092 workspace
11093 .ev_done
11094 .as_ref()
11095 .expect("device routes done event"),
11096 )?;
11097 for ev in workspace.ev_rank.iter().skip(1) {
11098 e.stream().wait(ev)?;
11099 }
11100 let mut output = e.uninit(experts.input_width)?;
11101 e.add(
11102 &workspace.accumulator[0],
11103 &workspace.accumulator[1],
11104 &mut output,
11105 experts.input_width,
11106 )?;
11107 let output = output;
11108 if let Some(started) = started {
11109 use std::sync::atomic::Ordering;
11110 let ns = TIMING_NS
11111 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11112 + started.elapsed().as_nanos() as u64;
11113 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11114 if calls % 430 == 0 {
11115 eprintln!(
11116 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11117 ns as f64 / 1.0e6,
11118 ns as f64 / calls as f64 / 1.0e3,
11119 );
11120 }
11121 }
11122 return Ok(output);
11123 }
11124 {
11125 let root = &self.ranks[0];
11126 let _main = root.gpu.enter_main()?;
11127 for ev in workspace.ev_rank.iter().skip(1) {
11128 root.stream().wait(ev)?;
11129 }
11130 root.stream()
11131 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11132 {
11133 let Nvfp4DeviceRoutesWorkspace {
11134 accumulator,
11135 remote,
11136 combined,
11137 ..
11138 } = &mut *workspace;
11139 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11140 }
11141 workspace
11142 .ev_done
11143 .as_ref()
11144 .expect("device routes done event")
11145 .record(&root.stream())?;
11146 }
11147 let output = {
11148 let _main = e.gpu.enter_main()?;
11149 e.stream().wait(
11150 workspace
11151 .ev_done
11152 .as_ref()
11153 .expect("device routes done event"),
11154 )?;
11155 let mut output = e.uninit(experts.input_width)?;
11158 e.stream().memcpy_dtod(
11159 &workspace.combined.slice(0..experts.input_width),
11160 &mut output.slice_mut(0..experts.input_width),
11161 )?;
11162 output
11163 };
11164 if let Some(started) = started {
11165 use std::sync::atomic::Ordering;
11166 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11167 + started.elapsed().as_nanos() as u64;
11168 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11169 if calls % 430 == 0 {
11170 eprintln!(
11171 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11172 ns as f64 / 1.0e6,
11173 ns as f64 / calls as f64 / 1.0e3,
11174 );
11175 }
11176 }
11177 Ok(output)
11178 }
11179
11180 #[allow(clippy::too_many_arguments)]
11186 pub fn nvfp4_routes_prestage(
11191 &self,
11192 experts: &ResidentNvfp4TensorParallel,
11193 e: &Engine,
11194 input_dev: &crate::CudaSlice<f32>,
11195 ) -> Result<bool, Box<dyn std::error::Error>> {
11196 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
11197 }
11198
11199 pub fn nvfp4_routes_prestage_with(
11205 &self,
11206 experts: &ResidentNvfp4TensorParallel,
11207 e: &Engine,
11208 input_dev: &crate::CudaSlice<f32>,
11209 rank1_router: impl FnOnce(
11210 &Engine,
11211 &crate::CudaSlice<f32>,
11212 &mut crate::CudaSlice<i32>,
11213 &mut crate::CudaSlice<f32>,
11214 ) -> Result<bool, Box<dyn std::error::Error>>,
11215 ) -> Result<bool, Box<dyn std::error::Error>> {
11216 if !routes_prestage_on() || step_tp_graph_enabled()? {
11217 return Ok(false);
11218 }
11219 if input_dev.len() != experts.input_width {
11220 return Err("NVFP4 prestage input width mismatch".into());
11221 }
11222 let mut workspace_guard = experts
11223 .device_workspace
11224 .lock()
11225 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11226 let Some(workspace) = workspace_guard.as_mut() else {
11227 return Ok(false);
11228 };
11229 if workspace.ev_input.is_none() {
11230 let _main = e.gpu.enter_main()?;
11231 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11232 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
11233 return Err("NVFP4 prestage engine changed".into());
11234 }
11235 {
11236 let _main = e.gpu.enter_main()?;
11237 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
11238 ev.record(&e.stream())?;
11239 }
11240 for (rank_index, engine) in self.ranks.iter().enumerate() {
11241 let _main = engine.gpu.enter_main()?;
11242 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
11243 engine.stream().wait(ev)?;
11244 {
11245 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11246 engine
11247 .stream()
11248 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11249 }
11250 {
11251 let Nvfp4DeviceRoutesWorkspace {
11252 input, in_q, in_d, ..
11253 } = &mut *workspace;
11254 engine.quantize_q8_1_into(
11255 &input[rank_index],
11256 1,
11257 experts.input_width,
11258 &mut in_q[rank_index],
11259 &mut in_d[rank_index],
11260 )?;
11261 }
11262 }
11263 if self.ranks.len() == 2 {
11264 let rank1 = &self.ranks[1];
11265 let _r1 = rank1.gpu.enter_main()?;
11266 let Nvfp4DeviceRoutesWorkspace {
11267 input,
11268 sel,
11269 route_w,
11270 ..
11271 } = &mut *workspace;
11272 let (in1, rest_sel) = (&input[1], &mut sel[1]);
11273 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
11274 workspace.rank1_routed = true;
11275 }
11276 }
11277 workspace.prestaged = true;
11278 Ok(true)
11279 }
11280
11281 #[allow(clippy::too_many_arguments)]
11292 pub fn run_tensor_parallel_routes_nvfp4_device_routed_tn(
11293 &self,
11294 experts: &ResidentNvfp4TensorParallel,
11295 e: &Engine,
11296 z_t: &crate::CudaSlice<f32>,
11297 sel_d: &crate::CudaSlice<i32>,
11298 w_d: &crate::CudaSlice<f32>,
11299 t: usize,
11300 n_sel_col: usize,
11301 activation_limit: Option<f32>,
11302 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11303 self.run_tensor_parallel_routes_nvfp4_device_routed_tn_prejoin(
11304 experts,
11305 e,
11306 z_t,
11307 sel_d,
11308 w_d,
11309 t,
11310 n_sel_col,
11311 activation_limit,
11312 || Ok(()),
11313 )
11314 }
11315
11316 #[allow(clippy::too_many_arguments)]
11322 pub fn run_tensor_parallel_routes_nvfp4_device_routed_tn_prejoin(
11323 &self,
11324 experts: &ResidentNvfp4TensorParallel,
11325 e: &Engine,
11326 z_t: &crate::CudaSlice<f32>,
11327 sel_d: &crate::CudaSlice<i32>,
11328 w_d: &crate::CudaSlice<f32>,
11329 t: usize,
11330 n_sel_col: usize,
11331 activation_limit: Option<f32>,
11332 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11333 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11334 let world = self.ranks.len();
11335 if world != NVFP4_CANONICAL_ROW_SHARDS {
11336 return Err("NVFP4 t-row routes require the canonical 2-shard grid".into());
11337 }
11338 let width = experts.input_width;
11339 let n_sel = t * n_sel_col;
11340 if t == 0 || t > 32 || z_t.len() < t * width || sel_d.len() < n_sel || w_d.len() < n_sel {
11341 return Err("NVFP4 t-row routes geometry".into());
11342 }
11343 if !nvfp4_bank_v2_on() {
11344 return Err("NVFP4 t-row routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
11345 }
11346 let local_out = experts.expert_width / world;
11347 let mut guard = experts
11348 .t2_workspace
11349 .lock()
11350 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
11351 if nvfp4_trow_workspace_needs_grow(guard.as_ref().map(|ws| (ws.t_cap, ws.n_sel)), t, n_sel)
11352 {
11353 let t_cap = guard.as_ref().map_or(t, |ws| ws.t_cap.max(t));
11354 let n_sel_cap = guard.as_ref().map_or(n_sel, |ws| ws.n_sel.max(n_sel));
11355 let mut input2 = Vec::new();
11356 let mut in_q2 = Vec::new();
11357 let mut in_d2 = Vec::new();
11358 let mut sel2 = Vec::new();
11359 let mut route_w2 = Vec::new();
11360 let mut gate_out2 = Vec::new();
11361 let mut up_out2 = Vec::new();
11362 let mut act_q2 = Vec::new();
11363 let mut act_d2 = Vec::new();
11364 let mut partial2 = Vec::new();
11365 let mut acc_a = Vec::new();
11366 let mut acc_b = Vec::new();
11367 let mut acc2 = Vec::new();
11368 let mut ev_rank = Vec::new();
11369 for engine in &self.ranks {
11370 let _m = engine.gpu.enter_main()?;
11371 input2.push(engine.uninit(t_cap * width)?);
11372 in_q2.push(engine.alloc_i8_uninit(t_cap * width)?);
11373 in_d2.push(engine.uninit(t_cap * (width / 32))?);
11374 sel2.push(engine.htod_i32(&vec![0i32; n_sel_cap])?);
11375 route_w2.push(engine.uninit(n_sel_cap)?);
11376 gate_out2.push(engine.uninit(n_sel_cap * local_out)?);
11377 up_out2.push(engine.uninit(n_sel_cap * local_out)?);
11378 act_q2.push(engine.alloc_i8_uninit(n_sel_cap * local_out)?);
11379 act_d2.push(engine.uninit(n_sel_cap * (local_out / 32))?);
11380 partial2.push(engine.uninit(n_sel_cap * width)?);
11381 acc_a.push(engine.uninit(width)?);
11382 acc_b.push(engine.uninit(width)?);
11383 acc2.push(engine.uninit(t_cap * width)?);
11384 ev_rank.push(engine.ctx().new_event(None)?);
11385 }
11386 let root = &self.ranks[0];
11387 let (peer_a, peer_b, omix_a, omix_b, peer2, omix2, ev_root) = {
11388 let _m = root.gpu.enter_main()?;
11389 (
11390 root.uninit(width)?,
11391 root.uninit(width)?,
11392 root.uninit(width)?,
11393 root.uninit(width)?,
11394 root.uninit(t_cap * width)?,
11395 root.uninit(t_cap * width)?,
11396 root.ctx().new_event(None)?,
11397 )
11398 };
11399 let ev_entry = {
11400 let _m = e.gpu.enter_main()?;
11401 e.ctx().new_event(None)?
11402 };
11403 *guard = Some(Nvfp4T2Workspace {
11404 input2,
11405 in_q2,
11406 in_d2,
11407 sel2,
11408 route_w2,
11409 gate_out2,
11410 up_out2,
11411 act_q2,
11412 act_d2,
11413 partial2,
11414 acc_a,
11415 acc_b,
11416 acc2,
11417 peer2,
11418 omix2,
11419 peer_a,
11420 peer_b,
11421 omix_a,
11422 omix_b,
11423 ev_entry,
11424 ev_rank,
11425 ev_root,
11426 t_cap,
11427 n_sel: n_sel_cap,
11428 e_device: e.ctx().ordinal(),
11429 });
11430 }
11431 let ws = guard.as_mut().expect("armed above");
11432 if ws.e_device != e.ctx().ordinal() {
11433 return Err("NVFP4 t2 routes engine changed".into());
11434 }
11435 {
11436 let _main = e.gpu.enter_main()?;
11437 ws.ev_entry.record(&e.stream())?;
11438 }
11439 let down8 = sel_down8_on() && (local_out >> 5) <= 32 && n_sel_col <= 8;
11443 if !down8 && t != 2 {
11444 return Err(
11445 "NVFP4 t-row routes at t != 2 require MEMRA_SEL_DOWN8=1 (fused rows kernel)".into(),
11446 );
11447 }
11448 for rank in 0..world {
11449 let engine = &self.ranks[rank];
11450 let _main = engine.gpu.enter_main()?;
11451 engine.stream().wait(&ws.ev_entry)?;
11452 {
11453 let mut dst = ws.input2[rank].slice_mut(0..t * width);
11454 engine
11455 .stream()
11456 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
11457 }
11458 {
11459 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
11460 engine
11461 .stream()
11462 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11463 }
11464 {
11465 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
11466 engine
11467 .stream()
11468 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11469 }
11470 {
11471 let Nvfp4T2Workspace {
11472 input2,
11473 in_q2,
11474 in_d2,
11475 ..
11476 } = &mut *ws;
11477 engine.quantize_q8_1_into(
11478 &input2[rank],
11479 t,
11480 width,
11481 &mut in_q2[rank],
11482 &mut in_d2[rank],
11483 )?;
11484 }
11485 let gate_bank = &experts.gate[rank];
11486 let up_bank = &experts.up[rank];
11487 if gate_bank.in_features != up_bank.in_features
11488 || gate_bank.local_out != up_bank.local_out
11489 || gate_bank.row_bytes != up_bank.row_bytes
11490 || gate_bank.expert_bytes != up_bank.expert_bytes
11491 {
11492 return Err("NVFP4 t-row routes need matched gate/up bank geometry".into());
11493 }
11494 {
11495 let Nvfp4T2Workspace {
11496 sel2,
11497 in_q2,
11498 in_d2,
11499 gate_out2,
11500 up_out2,
11501 ..
11502 } = &mut *ws;
11503 engine.qmatvec_nvfp4_sel_gu_tcol_into(
11504 &gate_bank.bank,
11505 &up_bank.bank,
11506 &sel2[rank],
11507 &in_q2[rank],
11508 &in_d2[rank],
11509 &mut gate_out2[rank],
11510 &mut up_out2[rank],
11511 n_sel,
11512 n_sel_col,
11513 gate_bank.in_features,
11514 gate_bank.local_out,
11515 gate_bank.row_bytes,
11516 gate_bank.expert_bytes,
11517 width,
11518 width / 32,
11519 )?;
11520 }
11521 {
11522 let Nvfp4T2Workspace {
11523 gate_out2,
11524 up_out2,
11525 sel2,
11526 act_q2,
11527 act_d2,
11528 ..
11529 } = &mut *ws;
11530 engine.silu_mul_scaled_q8_1_sel_into(
11531 &gate_out2[rank],
11532 &up_out2[rank],
11533 &experts.macros_gate_dev[rank],
11534 &experts.macros_up_dev[rank],
11535 &sel2[rank],
11536 activation_limit,
11537 &mut act_q2[rank],
11538 &mut act_d2[rank],
11539 local_out,
11540 n_sel,
11541 )?;
11542 }
11543 let shard = &experts.down[rank];
11544 if shard.device_rank != rank || shard.local_in != local_out {
11545 return Err("NVFP4 t-row routes: down shard placement drifted".into());
11546 }
11547 if down8 {
11551 let Nvfp4T2Workspace {
11552 sel2,
11553 act_q2,
11554 act_d2,
11555 route_w2,
11556 acc2,
11557 ..
11558 } = &mut *ws;
11559 engine.qmatvec_nvfp4_sel_down8_rows_into(
11560 &shard.bank,
11561 &sel2[rank],
11562 &act_q2[rank],
11563 &act_d2[rank],
11564 &route_w2[rank],
11565 &experts.macros_down_dev[rank],
11566 &mut acc2[rank],
11567 t,
11568 n_sel_col,
11569 shard.local_in,
11570 shard.out_features,
11571 shard.row_bytes,
11572 shard.expert_bytes,
11573 local_out,
11574 local_out / 32,
11575 )?;
11576 } else {
11577 {
11578 let Nvfp4T2Workspace {
11579 sel2,
11580 act_q2,
11581 act_d2,
11582 partial2,
11583 ..
11584 } = &mut *ws;
11585 engine.qmatvec_nvfp4_sel_into(
11586 &shard.bank,
11587 &sel2[rank],
11588 &act_q2[rank],
11589 &act_d2[rank],
11590 &mut partial2[rank],
11591 n_sel,
11592 shard.local_in,
11593 shard.out_features,
11594 shard.row_bytes,
11595 shard.expert_bytes,
11596 local_out,
11597 local_out / 32,
11598 )?;
11599 }
11600 let Nvfp4T2Workspace {
11601 partial2,
11602 route_w2,
11603 sel2,
11604 acc_a,
11605 acc_b,
11606 ..
11607 } = &mut *ws;
11608 engine.axpy_rows_seq_md_off_into(
11609 &partial2[rank],
11610 &route_w2[rank],
11611 &experts.macros_down_dev[rank],
11612 &sel2[rank],
11613 &mut acc_a[rank],
11614 width,
11615 n_sel_col,
11616 0,
11617 )?;
11618 engine.axpy_rows_seq_md_off_into(
11619 &partial2[rank],
11620 &route_w2[rank],
11621 &experts.macros_down_dev[rank],
11622 &sel2[rank],
11623 &mut acc_b[rank],
11624 width,
11625 n_sel_col,
11626 n_sel_col,
11627 )?;
11628 }
11629 if rank != 0 {
11630 ws.ev_rank[rank].record(&engine.stream())?;
11631 }
11632 }
11633 {
11643 let _main = e.gpu.enter_main()?;
11644 pre_join()?;
11645 }
11646 let root = &self.ranks[0];
11647 {
11648 let _main = root.gpu.enter_main()?;
11649 for ev in ws.ev_rank.iter().skip(1) {
11650 root.stream().wait(ev)?;
11651 }
11652 if down8 {
11653 let Nvfp4T2Workspace {
11656 acc2, peer2, omix2, ..
11657 } = &mut *ws;
11658 {
11659 let mut dst = peer2.slice_mut(0..t * width);
11660 root.stream()
11661 .memcpy_dtod(&acc2[1].slice(0..t * width), &mut dst)?;
11662 }
11663 root.add(&acc2[0], peer2, omix2, t * width)?;
11664 } else {
11665 let Nvfp4T2Workspace {
11666 acc_a,
11667 acc_b,
11668 peer_a,
11669 peer_b,
11670 omix_a,
11671 omix_b,
11672 ..
11673 } = &mut *ws;
11674 {
11675 let mut dst = peer_a.slice_mut(0..width);
11676 root.stream()
11677 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
11678 }
11679 {
11680 let mut dst = peer_b.slice_mut(0..width);
11681 root.stream()
11682 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
11683 }
11684 root.add(&acc_a[0], peer_a, omix_a, width)?;
11685 root.add(&acc_b[0], peer_b, omix_b, width)?;
11686 }
11687 ws.ev_root.record(&root.stream())?;
11688 }
11689 let _main = e.gpu.enter_main()?;
11690 e.stream().wait(&ws.ev_root)?;
11691 let mut out = e.uninit(t * width)?;
11692 if down8 {
11693 e.stream().memcpy_dtod(
11694 &ws.omix2.slice(0..t * width),
11695 &mut out.slice_mut(0..t * width),
11696 )?;
11697 } else {
11698 e.stream()
11699 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
11700 e.stream().memcpy_dtod(
11701 &ws.omix_b.slice(0..width),
11702 &mut out.slice_mut(width..2 * width),
11703 )?;
11704 }
11705 Ok(out)
11706 }
11707
11708 #[allow(clippy::too_many_arguments)]
11725 fn determ_stage_bytes(v: &[u8]) -> u64 {
11734 v.iter().fold(0u64, |a, b| {
11735 a.wrapping_mul(1_000_003).wrapping_add(*b as u64)
11736 })
11737 }
11738
11739 fn determ_stage_i32(v: &[i32]) -> u64 {
11744 v.iter().fold(0u64, |a, b| {
11745 a.wrapping_mul(1_000_003).wrapping_add(*b as u32 as u64)
11746 })
11747 }
11748
11749 fn determ_stage_sum(v: &[f32]) -> u64 {
11750 v.iter().fold(0u64, |a, x| {
11751 a.wrapping_mul(1_000_003).wrapping_add(x.to_bits() as u64)
11752 })
11753 }
11754
11755 pub fn run_tensor_parallel_routes_nvfp4_prime_grouped(
11756 &self,
11757 experts: &ResidentNvfp4TensorParallel,
11758 e: &Engine,
11759 z_t: &crate::CudaSlice<f32>,
11760 t: usize,
11761 sel: &[i32],
11762 w: &[f32],
11763 n_used: usize,
11764 activation_limit: Option<f32>,
11765 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11766 let world = self.ranks.len();
11767 if world != NVFP4_CANONICAL_ROW_SHARDS {
11768 return Err("NVFP4 grouped prime requires the canonical 2-shard grid".into());
11769 }
11770 let bank_qt = if nvfp4_bank_v2_on() {
11774 crate::QT_NVFP4_V2
11775 } else {
11776 crate::QT_NVFP4
11777 };
11778 let width = experts.input_width;
11779 let n_expert = experts.expert_count;
11780 let n_pairs = t * n_used;
11781 if sel.len() < n_pairs || w.len() < n_pairs || z_t.len() < t * width {
11782 return Err("NVFP4 grouped prime geometry".into());
11783 }
11784 let gprof = std::env::var("MEMRA_PRIME_PROF").as_deref() == Ok("1") && t >= 16;
11793 let g_t0 = std::time::Instant::now();
11794 let mut buckets: Vec<Vec<i32>> = vec![Vec::new(); n_expert];
11797 for (p, &s_id) in sel.iter().take(n_pairs).enumerate() {
11798 let s_id = s_id as usize;
11799 if s_id >= n_expert {
11800 return Err(format!("grouped prime selection {s_id} >= {n_expert}").into());
11801 }
11802 buckets[s_id].push(p as i32);
11803 }
11804 let mut ex_ids: Vec<i32> = Vec::new();
11805 let mut ex_off: Vec<i32> = vec![0];
11806 let mut ex_pairs: Vec<i32> = Vec::new();
11807 for (e_id, b) in buckets.iter().enumerate() {
11808 if !b.is_empty() {
11809 ex_ids.push(e_id as i32);
11810 ex_pairs.extend_from_slice(b);
11811 ex_off.push(ex_pairs.len() as i32);
11812 }
11813 }
11814 let n_active = ex_ids.len();
11815 if n_active == 0 {
11816 return Ok(e.zeros(t * width)?);
11817 }
11818 if n_active > 512 {
11819 return Err("grouped prime n_active > 512 (direct lane cap)".into());
11820 }
11821 let csr_tok: Vec<i32> = ex_pairs.iter().map(|&p| p / n_used as i32).collect();
11822 let mut inv = vec![0i32; n_pairs];
11825 for (row, &pair) in ex_pairs.iter().enumerate() {
11826 inv[pair as usize] = row as i32;
11827 }
11828 let mg: Vec<f32> = ex_pairs
11830 .iter()
11831 .map(|&p| experts.macros_gate[sel[p as usize] as usize])
11832 .collect();
11833 let mu: Vec<f32> = ex_pairs
11834 .iter()
11835 .map(|&p| experts.macros_up[sel[p as usize] as usize])
11836 .collect();
11837 let wd: Vec<f32> = (0..n_pairs)
11838 .map(|p| w[p] * experts.macros_down[sel[p] as usize])
11839 .collect();
11840 {
11844 let mut tabs = experts
11845 .prime_tables
11846 .lock()
11847 .map_err(|_| "grouped prime table cache is poisoned")?;
11848 if tabs.len() != world {
11849 tabs.clear();
11850 for rank in 0..world {
11851 let engine = &self.ranks[rank];
11852 let _main = engine.gpu.enter_main()?;
11853 let (gb, ub, db) =
11854 (&experts.gate[rank], &experts.up[rank], &experts.down[rank]);
11855 let mut tab = vec![0u64; 3 * n_expert];
11856 {
11857 use cudarc::driver::DevicePtr;
11858 let stream = engine.stream();
11859 let (pg, _g0) = gb.bank.device_ptr(&stream);
11860 let (pu, _g1) = ub.bank.device_ptr(&stream);
11861 let (pd, _g2) = db.bank.device_ptr(&stream);
11862 for ex in 0..n_expert {
11863 tab[ex] = pg as u64 + (ex * gb.expert_bytes) as u64;
11864 tab[n_expert + ex] = pu as u64 + (ex * ub.expert_bytes) as u64;
11865 tab[2 * n_expert + ex] = pd as u64 + (ex * db.expert_bytes) as u64;
11866 }
11867 }
11868 tabs.push(engine.htod_u64(&tab)?);
11869 }
11870 }
11871 }
11872 let g_csr = g_t0.elapsed().as_secs_f64() * 1e3;
11873 let g_t1 = std::time::Instant::now();
11874 {
11881 static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
11882 if gprof && !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
11883 for rank in 0..world {
11884 let e_r = &self.ranks[rank];
11885 let _m = e_r.gpu.enter_main();
11886 eprintln!(
11887 "[rank-id] rank={rank} ordinal={} ctx={:?} stream={:?} root_ordinal={} \
11888 root_stream={:?}",
11889 e_r.ctx().ordinal(),
11890 std::sync::Arc::as_ptr(&e_r.ctx()),
11891 e_r.stream().cu_stream(),
11892 e.ctx().ordinal(),
11893 e.stream().cu_stream(),
11894 );
11895 }
11896 }
11897 }
11898
11899 let mut partials: Vec<crate::CudaSlice<f32>> = Vec::with_capacity(world);
11900 let mut ev_rank: Vec<CudaEvent> = Vec::with_capacity(world);
11901 let mut ev_head: Vec<CudaEvent> = Vec::with_capacity(world);
11902 let mut ev_tail_prof: Vec<CudaEvent> = Vec::with_capacity(world);
11903 for rank in 0..world {
11904 let engine = &self.ranks[rank];
11905 let _main = engine.gpu.enter_main()?;
11906 if gprof {
11907 let h = engine
11912 .ctx()
11913 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
11914 h.record(&engine.stream())?;
11915 ev_head.push(h);
11916 }
11917 engine.bind_runtime_device(engine.ctx().ordinal() as i32)?;
11920 let gb = &experts.gate[rank];
11921 let ub = &experts.up[rank];
11922 let db = &experts.down[rank];
11923 if db.device_rank != rank {
11924 return Err("grouped prime: down shard placement drifted".into());
11925 }
11926 let local_ff = gb.local_out;
11927 if ub.local_out != local_ff || db.local_in != local_ff || db.out_features != width {
11928 return Err("grouped prime: bank width mismatch".into());
11929 }
11930 let csr_tok_d = engine.htod_i32(&csr_tok)?;
11933 let exi_d = engine.htod_i32(&ex_ids)?;
11934 let exoff_d = engine.htod_i32(&ex_off)?;
11935 let mg_d = engine.htod(&mg)?;
11936 let mu_d = engine.htod(&mu)?;
11937 let tabs_guard = experts
11939 .prime_tables
11940 .lock()
11941 .map_err(|_| "grouped prime table cache is poisoned")?;
11942 let tab_d = &tabs_guard[rank];
11943 let mut z_r = engine.uninit(t * width)?;
11944 {
11945 let mut dst = z_r.slice_mut(0..t * width);
11946 engine
11947 .stream()
11948 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
11949 }
11950 let dstage = std::env::var("MEMRA_MOE_DETERM_STAGE").as_deref() == Ok("1") && t >= 16;
11951 let (z16, zs) = engine.moe_f16g_act(&z_r, Some(&csr_tok_d), width, n_pairs)?;
11952 if dstage {
11953 let zr = engine.dtoh(&z_r)?;
11957 let zsv = engine.dtoh(&zs)?;
11958 let z16v = engine.dtoh_u8(&z16)?;
11959 eprintln!(
11960 "[determ-stage] rank={rank} t={t} z_r={:016x} zs={:016x} z16={:016x}",
11961 Self::determ_stage_sum(&zr),
11962 Self::determ_stage_sum(&zsv),
11963 Self::determ_stage_bytes(&z16v)
11964 );
11965 }
11966 if dstage {
11967 engine.stream().synchronize()?;
11975 let csr_v = engine.dtoh_i32(&csr_tok_d)?;
11976 let exi_v = engine.dtoh_i32(&exi_d)?;
11977 let exo_v = engine.dtoh_i32(&exoff_d)?;
11978 let mg_v = engine.dtoh(&mg_d)?;
11979 let mu_v = engine.dtoh(&mu_d)?;
11980 let tab_v = engine.dtoh_u64(tab_d)?;
11981 eprintln!(
11982 "[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={}",
11983 Self::determ_stage_i32(&csr_v),
11984 Self::determ_stage_i32(&exi_v),
11985 Self::determ_stage_i32(&exo_v),
11986 Self::determ_stage_i32(&ex_off),
11987 Self::determ_stage_sum(&mg_v),
11988 Self::determ_stage_sum(&mu_v),
11989 tab_v
11990 .iter()
11991 .fold(0u64, |a, b| a.wrapping_mul(1_000_003).wrapping_add(*b)),
11992 gb.row_bytes
11993 );
11994 if std::env::var("MEMRA_MOE_DETERM_BANK").as_deref() == Ok("1") {
11997 let bank_v = engine.dtoh_u8(&gb.bank)?;
11998 eprintln!(
11999 "[determ-closure] rank={rank} t={t} gate_bank={:016x} bytes={}",
12000 Self::determ_stage_bytes(&bank_v),
12001 bank_v.len()
12002 );
12003 }
12004 }
12005 let mut g = engine.moe_f16_grouped(
12006 tab_d,
12007 0,
12008 n_expert,
12009 &exi_d,
12010 &ex_off,
12011 &exoff_d,
12012 &z16,
12013 &zs,
12014 width,
12015 local_ff,
12016 n_active,
12017 n_pairs,
12018 bank_qt,
12019 gb.row_bytes,
12020 )?;
12021 engine.scale_rows(&mut g, &mg_d, local_ff, n_pairs)?;
12022 let mut u = engine.moe_f16_grouped(
12023 tab_d,
12024 1,
12025 n_expert,
12026 &exi_d,
12027 &ex_off,
12028 &exoff_d,
12029 &z16,
12030 &zs,
12031 width,
12032 local_ff,
12033 n_active,
12034 n_pairs,
12035 bank_qt,
12036 ub.row_bytes,
12037 )?;
12038 engine.scale_rows(&mut u, &mu_d, local_ff, n_pairs)?;
12039 let act = match activation_limit.filter(|l| *l > 1e-6) {
12043 Some(lim) => {
12044 let mut a = engine.uninit(n_pairs * local_ff)?;
12045 engine.swiglu_clamped_mul_scaled(
12046 &g,
12047 &u,
12048 1.0,
12049 1.0,
12050 lim,
12051 &mut a,
12052 n_pairs * local_ff,
12053 )?;
12054 a
12055 }
12056 None => engine.moe_pairs_silu_mul(&g, &u, n_pairs * local_ff)?,
12057 };
12058 if dstage {
12059 let gv = engine.dtoh(&g)?;
12060 let uv = engine.dtoh(&u)?;
12061 let av = engine.dtoh(&act)?;
12062 let key = (rank, t);
12067 let mut prev_map = DETERM_PREV
12068 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))
12069 .lock()
12070 .map_err(|_| "determ prev map poisoned")?;
12071 let shape = match prev_map.get(&key) {
12072 Some(prev) if prev.len() == gv.len() => {
12073 let mut md = 0.0f32;
12074 let mut n_diff = 0usize;
12075 let mut n_big = 0usize;
12076 for (a, b) in prev.iter().zip(gv.iter()) {
12077 let d = (a - b).abs();
12078 if d > 0.0 {
12079 n_diff += 1;
12080 }
12081 if d > 1e-3 {
12082 n_big += 1;
12083 }
12084 if d > md {
12085 md = d;
12086 }
12087 }
12088 format!(
12089 " | vs_prev maxdiff={md:.3e} differing={n_diff}/{} big(>1e-3)={n_big}",
12090 gv.len()
12091 )
12092 }
12093 _ => String::new(),
12094 };
12095 prev_map.insert(key, gv.clone());
12096 drop(prev_map);
12097 eprintln!(
12098 "[determ-stage] rank={rank} t={t} gate={:016x} up={:016x} silu={:016x}{shape}",
12099 Self::determ_stage_sum(&gv),
12100 Self::determ_stage_sum(&uv),
12101 Self::determ_stage_sum(&av)
12102 );
12103 }
12104 let (a16, a_s) = engine.moe_f16g_act(&act, None, local_ff, n_pairs)?;
12105 let d_csr = engine.moe_f16_grouped(
12106 tab_d,
12107 2,
12108 n_expert,
12109 &exi_d,
12110 &ex_off,
12111 &exoff_d,
12112 &a16,
12113 &a_s,
12114 local_ff,
12115 width,
12116 n_active,
12117 n_pairs,
12118 bank_qt,
12119 db.row_bytes,
12120 )?;
12121
12122 if dstage {
12125 engine.stream().synchronize()?;
12126 let a16v = engine.dtoh_u8(&a16)?;
12127 let dv = engine.dtoh(&d_csr)?;
12128 eprintln!(
12129 "[determ-stage] rank={rank} t={t} a16={:016x} down_partial={:016x}",
12130 Self::determ_stage_bytes(&a16v),
12131 Self::determ_stage_sum(&dv)
12132 );
12133 }
12134 let ev = engine.ctx().new_event(None)?;
12135 ev.record(&engine.stream())?;
12136 if gprof {
12137 let tp = engine
12146 .ctx()
12147 .new_event(Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT))?;
12148 tp.record(&engine.stream())?;
12149 ev_tail_prof.push(tp);
12150 }
12151 ev_rank.push(ev);
12152 partials.push(d_csr);
12153 }
12154 let _main = e.gpu.enter_main()?;
12155 e.bind_runtime_device(e.ctx().ordinal() as i32)?;
12156 let g_issue = g_t1.elapsed().as_secs_f64() * 1e3;
12158 let g_t2 = std::time::Instant::now();
12159 for ev in &ev_rank {
12160 e.stream().wait(ev)?;
12161 }
12162 let mut y0 = e.uninit(n_pairs * width)?;
12165 {
12166 let mut dst = y0.slice_mut(0..n_pairs * width);
12167 e.stream()
12168 .memcpy_dtod(&partials[0].slice(0..n_pairs * width), &mut dst)?;
12169 }
12170 let mut y1 = e.uninit(n_pairs * width)?;
12171 {
12172 let mut dst = y1.slice_mut(0..n_pairs * width);
12173 e.stream()
12174 .memcpy_dtod(&partials[1].slice(0..n_pairs * width), &mut dst)?;
12175 }
12176 let inv_d = e.htod_i32(&inv)?;
12177 let wd_d = e.htod(&wd)?;
12178 let mut out = e.uninit(t * width)?;
12179 e.moe_prime_join_scatter(&y0, &y1, &inv_d, &wd_d, &mut out, width, n_used, t)?;
12180 if gprof {
12181 let _ = e.stream().synchronize();
12182 let g_join = g_t2.elapsed().as_secs_f64() * 1e3;
12183 let mut span_ms: Vec<f32> = Vec::with_capacity(world);
12191 for (rank, (h, tp)) in ev_head.iter().zip(ev_tail_prof.iter()).enumerate() {
12192 let guard = self.ranks[rank].gpu.enter_main();
12193 match guard.and_then(|_g| h.elapsed_ms(tp).map_err(|e| e.into())) {
12194 Ok(v) => span_ms.push(v),
12195 Err(err) => {
12196 static SAID: std::sync::atomic::AtomicBool =
12197 std::sync::atomic::AtomicBool::new(false);
12198 if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
12199 eprintln!("[grp-prof] span query failed on rank {rank}: {err}");
12200 }
12201 span_ms.push(-1.0);
12202 }
12203 }
12204 }
12205 eprintln!(
12206 "[grp-prof] t={t} n_active={n_active} csr={g_csr:.1}ms issue={g_issue:.1}ms \
12207 join={g_join:.1}ms spans={span_ms:?} span_sum={:.1}ms span_max={:.1}ms",
12208 span_ms.iter().sum::<f32>(),
12209 span_ms.iter().cloned().fold(0.0f32, f32::max)
12210 );
12211 }
12212 Ok(out)
12213 }
12214
12215 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
12216 &self,
12217 experts: &ResidentNvfp4TensorParallel,
12218 e: &Engine,
12219 input_dev: &crate::CudaSlice<f32>,
12220 sel_d: &crate::CudaSlice<i32>,
12221 w_d: &crate::CudaSlice<f32>,
12222 experts_per_token: usize,
12223 activation_limit: Option<f32>,
12224 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12225 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
12226 experts,
12227 e,
12228 input_dev,
12229 sel_d,
12230 w_d,
12231 experts_per_token,
12232 activation_limit,
12233 || Ok(()),
12234 )
12235 }
12236
12237 #[allow(clippy::too_many_arguments)]
12243 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
12244 &self,
12245 experts: &ResidentNvfp4TensorParallel,
12246 e: &Engine,
12247 input_dev: &crate::CudaSlice<f32>,
12248 sel_d: &crate::CudaSlice<i32>,
12249 w_d: &crate::CudaSlice<f32>,
12250 experts_per_token: usize,
12251 activation_limit: Option<f32>,
12252 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
12253 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12254 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
12255 experts,
12256 e,
12257 input_dev,
12258 sel_d,
12259 w_d,
12260 experts_per_token,
12261 activation_limit,
12262 pre_join,
12263 None,
12264 )
12265 }
12266
12267 #[allow(clippy::too_many_arguments)]
12272 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
12273 &self,
12274 experts: &ResidentNvfp4TensorParallel,
12275 e: &Engine,
12276 input_dev: &crate::CudaSlice<f32>,
12277 sel_d: &crate::CudaSlice<i32>,
12278 w_d: &crate::CudaSlice<f32>,
12279 experts_per_token: usize,
12280 activation_limit: Option<f32>,
12281 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
12282 post_add: Option<(u64, u64)>,
12283 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
12284 if input_dev.len() != experts.input_width {
12285 return Err(format!(
12286 "NVFP4 device-routed input {} != width {}",
12287 input_dev.len(),
12288 experts.input_width
12289 )
12290 .into());
12291 }
12292 let n_sel = experts_per_token;
12293 if sel_d.len() < n_sel || w_d.len() < n_sel {
12294 return Err(format!(
12295 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
12296 sel_d.len(),
12297 w_d.len()
12298 )
12299 .into());
12300 }
12301 let world = self.ranks.len();
12302 if world != NVFP4_CANONICAL_ROW_SHARDS {
12303 return Err(format!(
12304 "NVFP4 device routes require world == canonical shard grid \
12305 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
12306 )
12307 .into());
12308 }
12309 let local_out = if experts.ep2 {
12310 experts.expert_width
12311 } else {
12312 experts.expert_width / world
12313 };
12314
12315 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12316 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
12317 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
12318 let started = timing.then(std::time::Instant::now);
12319
12320 let mut workspace_guard = experts
12321 .device_workspace
12322 .lock()
12323 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12324 if workspace_guard.is_none() {
12325 drop(workspace_guard);
12326 let zero = vec![0.0f32; experts.input_width];
12327 let zero_sel = vec![0usize; n_sel];
12328 let zero_w = vec![0.0f32; n_sel];
12329 let _ = self.run_tensor_parallel_routes_nvfp4_device(
12330 experts,
12331 &zero,
12332 &zero_sel,
12333 &zero_w,
12334 n_sel,
12335 activation_limit,
12336 )?;
12337 workspace_guard = experts
12338 .device_workspace
12339 .lock()
12340 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
12341 }
12342 let workspace = workspace_guard
12343 .as_mut()
12344 .expect("NVFP4 device routes workspace initialized above");
12345 if workspace.n_sel != n_sel {
12346 return Err(format!(
12347 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
12348 workspace.n_sel
12349 )
12350 .into());
12351 }
12352
12353 if step_tp_graph_enabled()? {
12358 if experts.ep2 {
12359 return Err(
12360 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
12361 co-gated; unset one"
12362 .into(),
12363 );
12364 }
12365 if workspace.dev_route_e.is_none() {
12366 let _main = e.gpu.enter_main()?;
12367 workspace.dev_route_e = Some((
12368 e.htod_i32(&vec![0i32; n_sel])?,
12369 e.htod(&vec![0.0f32; n_sel])?,
12370 ));
12371 }
12372 if workspace.in_stage_e.is_none() {
12373 let _main = e.gpu.enter_main()?;
12374 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
12375 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
12376 }
12377 if workspace.routes_graph.is_none() {
12378 let graph = self.nvfp4_routes_build_graph(
12379 experts,
12380 workspace,
12381 local_out,
12382 n_sel,
12383 activation_limit,
12384 )?;
12385 workspace.routes_graph = Some(graph);
12386 eprintln!(
12387 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
12388 children=3 updates=none performance_claim=false"
12389 );
12390 }
12391 let output = {
12392 let _main = e.gpu.enter_main()?;
12393 {
12394 let (sel_e, w_e) = workspace
12395 .dev_route_e
12396 .as_mut()
12397 .expect("device route staging set above");
12398 {
12399 let mut dst = sel_e.slice_mut(0..n_sel);
12400 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
12401 }
12402 {
12403 let mut dst = w_e.slice_mut(0..n_sel);
12404 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
12405 }
12406 }
12407 {
12408 let in_stage = workspace
12409 .in_stage_e
12410 .as_mut()
12411 .expect("graph staging set above");
12412 let mut dst = in_stage.slice_mut(0..experts.input_width);
12413 e.stream()
12414 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
12415 }
12416 unsafe {
12417 let r = cudarc::driver::sys::cuGraphLaunch(
12418 workspace
12419 .routes_graph
12420 .as_ref()
12421 .expect("routes graph built above")
12422 .exec,
12423 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
12424 );
12425 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
12426 return Err(format!("routes graph launch: {r:?}").into());
12427 }
12428 }
12429 let mut output = e.uninit(experts.input_width)?;
12430 {
12431 let out_stage = workspace
12432 .out_stage_e
12433 .as_ref()
12434 .expect("graph staging set above");
12435 e.stream().memcpy_dtod(
12436 &out_stage.slice(0..experts.input_width),
12437 &mut output.slice_mut(0..experts.input_width),
12438 )?;
12439 }
12440 output
12441 };
12442 if let Some(started) = started {
12443 use std::sync::atomic::Ordering;
12444 let ns = TIMING_NS
12445 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12446 + started.elapsed().as_nanos() as u64;
12447 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12448 if calls % 430 == 0 {
12449 eprintln!(
12450 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12451 ns as f64 / 1.0e6,
12452 ns as f64 / calls as f64 / 1.0e3,
12453 );
12454 }
12455 }
12456 return Ok(output);
12457 }
12458
12459 if let Some((_, device)) = workspace.ev_entry.as_ref() {
12463 if *device != e.ctx().ordinal() {
12464 return Err("NVFP4 device-routed routes engine changed".into());
12465 }
12466 } else {
12467 let _main = e.gpu.enter_main()?;
12468 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
12469 }
12470 if workspace.dev_route_e.is_none() {
12471 let _main = e.gpu.enter_main()?;
12472 workspace.dev_route_e = Some((
12473 e.htod_i32(&vec![0i32; n_sel])?,
12474 e.htod(&vec![0.0f32; n_sel])?,
12475 ));
12476 }
12477 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
12483 let e_device = e.ctx().ordinal();
12484 let rank1_routed_peek = workspace.rank1_routed;
12486 let stage_needed = !mirror
12487 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
12488 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
12489 });
12490 {
12491 let _main = e.gpu.enter_main()?;
12492 if stage_needed {
12493 let (sel_e, w_e) = workspace
12494 .dev_route_e
12495 .as_mut()
12496 .expect("device route staging set above");
12497 {
12498 let mut dst = sel_e.slice_mut(0..n_sel);
12499 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
12500 }
12501 {
12502 let mut dst = w_e.slice_mut(0..n_sel);
12503 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
12504 }
12505 }
12506 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
12507 ev_entry.record(&e.stream())?;
12508 }
12509 let prestaged = std::mem::take(&mut workspace.prestaged);
12512 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
12513 for (rank_index, engine) in self.ranks.iter().enumerate() {
12514 let _main = engine.gpu.enter_main()?;
12515 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
12516 engine.stream().wait(ev_entry)?;
12517 if !prestaged {
12518 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
12519 engine
12520 .stream()
12521 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
12522 }
12523 if !(rank1_routed && rank_index == 1) {
12524 let same_dev = engine.ctx().ordinal() == e_device;
12528 if mirror {
12529 let Nvfp4DeviceRoutesWorkspace {
12532 sel,
12533 route_w,
12534 dev_route_e,
12535 ..
12536 } = &mut *workspace;
12537 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
12538 if same_dev {
12539 (sel_d, w_d)
12540 } else {
12541 let (sel_e, w_e) = dev_route_e
12542 .as_ref()
12543 .expect("device route staging set above");
12544 (sel_e, w_e)
12545 };
12546 engine.moe_sel_w_mirror(
12547 src_sel,
12548 src_w,
12549 &mut sel[rank_index],
12550 &mut route_w[rank_index],
12551 n_sel,
12552 )?;
12553 } else {
12554 let (sel_e, w_e) = workspace
12555 .dev_route_e
12556 .as_ref()
12557 .expect("device route staging set above");
12558 {
12559 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
12560 engine
12561 .stream()
12562 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
12563 }
12564 {
12565 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
12566 engine
12567 .stream()
12568 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
12569 }
12570 }
12571 }
12572 if !prestaged {
12573 let Nvfp4DeviceRoutesWorkspace {
12574 input, in_q, in_d, ..
12575 } = &mut *workspace;
12576 engine.quantize_q8_1_into(
12577 &input[rank_index],
12578 1,
12579 experts.input_width,
12580 &mut in_q[rank_index],
12581 &mut in_d[rank_index],
12582 )?;
12583 }
12584 }
12585 self.nvfp4_routes_batched_sweeps(
12586 experts,
12587 workspace,
12588 &[],
12589 &[],
12590 &[],
12591 local_out,
12592 n_sel,
12593 activation_limit,
12594 true,
12595 )?;
12596
12597 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
12600 let _main = engine.gpu.enter_main()?;
12601 workspace.ev_rank[rank_index].record(&engine.stream())?;
12602 }
12603 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
12606 let mut ticket = 0u32;
12607 if memops {
12608 use cudarc::driver::sys;
12609 if workspace.fence_flags_raw == 0 {
12610 let root = &self.ranks[0];
12611 let _main = root.gpu.enter_main()?;
12612 let mut ptr: sys::CUdeviceptr = 0;
12613 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
12614 if r != sys::CUresult::CUDA_SUCCESS {
12615 return Err(format!("fence flag alloc: {r:?}").into());
12616 }
12617 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
12618 if r != sys::CUresult::CUDA_SUCCESS {
12619 return Err(format!("fence flag memset: {r:?}").into());
12620 }
12621 workspace.fence_flags_raw = ptr as u64;
12622 }
12623 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
12624 ticket = workspace.fence_ticket;
12625 let base = workspace.fence_flags_raw;
12626 if fence_rank1_on() {
12632 let peer = &self.ranks[1];
12633 let _pmain = peer.gpu.enter_main()?;
12634 peer.ring_flag_raw(base, ticket)?;
12635 }
12636 {
12637 let root = &self.ranks[0];
12638 let _main = root.gpu.enter_main()?;
12639 let r = unsafe {
12640 sys::cuStreamWriteValue32_v2(
12641 root.stream().cu_stream() as sys::CUstream,
12642 (base + 4) as sys::CUdeviceptr,
12643 ticket,
12644 0,
12645 )
12646 };
12647 if r != sys::CUresult::CUDA_SUCCESS {
12648 return Err(format!("fence write root: {r:?}").into());
12649 }
12650 }
12651 }
12652 pre_join()?;
12655
12656 if moe_direct_on() && self.ranks.len() == 2 {
12657 let _main = e.gpu.enter_main()?;
12664 if memops {
12665 use cudarc::driver::sys;
12666 let base = workspace.fence_flags_raw;
12667 let r = unsafe {
12668 sys::cuStreamWaitValue32_v2(
12669 e.stream().cu_stream() as sys::CUstream,
12670 (base + 4) as sys::CUdeviceptr,
12671 ticket,
12672 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
12673 )
12674 };
12675 if r != sys::CUresult::CUDA_SUCCESS {
12676 return Err(format!("fence wait: {r:?}").into());
12677 }
12678 if fence_rank1_on() {
12679 let r = unsafe {
12681 sys::cuStreamWaitValue32_v2(
12682 e.stream().cu_stream() as sys::CUstream,
12683 base as sys::CUdeviceptr,
12684 ticket,
12685 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
12686 )
12687 };
12688 if r != sys::CUresult::CUDA_SUCCESS {
12689 return Err(format!("fence wait rank1: {r:?}").into());
12690 }
12691 } else {
12692 for ev in workspace.ev_rank.iter().skip(1) {
12693 e.stream().wait(ev)?;
12694 }
12695 }
12696 } else {
12697 {
12698 let root = &self.ranks[0];
12699 let _rmain = root.gpu.enter_main()?;
12700 workspace
12701 .ev_done
12702 .as_ref()
12703 .expect("device routes done event")
12704 .record(&root.stream())?;
12705 }
12706 e.stream().wait(
12707 workspace
12708 .ev_done
12709 .as_ref()
12710 .expect("device routes done event"),
12711 )?;
12712 for ev in workspace.ev_rank.iter().skip(1) {
12713 e.stream().wait(ev)?;
12714 }
12715 }
12716 let mut output = e.uninit(experts.input_width)?;
12717 if let Some((sh_raw, scale_raw)) = post_add {
12718 e.add3_raw(
12721 &workspace.accumulator[0],
12722 &workspace.accumulator[1],
12723 sh_raw,
12724 scale_raw,
12725 &mut output,
12726 experts.input_width,
12727 )?;
12728 } else {
12729 e.add(
12730 &workspace.accumulator[0],
12731 &workspace.accumulator[1],
12732 &mut output,
12733 experts.input_width,
12734 )?;
12735 }
12736 let output = output;
12737 if let Some(started) = started {
12738 use std::sync::atomic::Ordering;
12739 let ns = TIMING_NS
12740 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12741 + started.elapsed().as_nanos() as u64;
12742 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12743 if calls % 430 == 0 {
12744 eprintln!(
12745 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12746 ns as f64 / 1.0e6,
12747 ns as f64 / calls as f64 / 1.0e3,
12748 );
12749 }
12750 }
12751 return Ok(output);
12752 }
12753 {
12754 let root = &self.ranks[0];
12755 let _main = root.gpu.enter_main()?;
12756 for ev in workspace.ev_rank.iter().skip(1) {
12757 root.stream().wait(ev)?;
12758 }
12759 root.stream()
12760 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
12761 {
12762 let Nvfp4DeviceRoutesWorkspace {
12763 accumulator,
12764 remote,
12765 combined,
12766 ..
12767 } = &mut *workspace;
12768 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12769 }
12770 workspace
12771 .ev_done
12772 .as_ref()
12773 .expect("device routes done event")
12774 .record(&root.stream())?;
12775 }
12776 let output = {
12777 let _main = e.gpu.enter_main()?;
12778 e.stream().wait(
12779 workspace
12780 .ev_done
12781 .as_ref()
12782 .expect("device routes done event"),
12783 )?;
12784 let mut output = e.uninit(experts.input_width)?;
12787 e.stream().memcpy_dtod(
12788 &workspace.combined.slice(0..experts.input_width),
12789 &mut output.slice_mut(0..experts.input_width),
12790 )?;
12791 output
12792 };
12793 if let Some(started) = started {
12794 use std::sync::atomic::Ordering;
12795 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
12796 + started.elapsed().as_nanos() as u64;
12797 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
12798 if calls % 430 == 0 {
12799 eprintln!(
12800 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
12801 ns as f64 / 1.0e6,
12802 ns as f64 / calls as f64 / 1.0e3,
12803 );
12804 }
12805 }
12806 Ok(output)
12807 }
12808
12809 pub(crate) fn decode_v2_finish_root_fused(
12813 &self,
12814 ws: &mut StepTpDecodeV2Ws,
12815 ) -> Result<(), Box<dyn std::error::Error>> {
12816 let root = &self.ranks[0];
12817 let _main = root.gpu.enter_main()?;
12818 if ws.raw_peer_partial != 0 {
12819 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
12821 } else {
12822 root.stream()
12823 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
12824 }
12825 {
12826 let StepTpDecodeV2Ws {
12827 o_partials,
12828 peer_partial,
12829 reduce_a,
12830 o_out,
12831 ..
12832 } = &mut *ws;
12833 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
12834 }
12835 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
12836 if shadows {
12837 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
12840 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
12841 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
12842 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
12843 }
12844 if shadows && ws.raw_peer_partial != 0 {
12845 raw_copy_bytes(
12846 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
12847 ws.raw_k1,
12848 ws.local_kv_dim * 4,
12849 root,
12850 )?;
12851 raw_copy_bytes(
12852 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
12853 ws.raw_v1,
12854 ws.local_kv_dim * 4,
12855 root,
12856 )?;
12857 } else if shadows {
12858 let start = ws.local_kv_dim;
12859 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
12860 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
12861 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
12862 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
12863 }
12864 if ws.raw_mixed_stage_e != 0 {
12865 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
12868 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
12869 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
12870 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
12871 }
12872 Ok(())
12873 }
12874
12875 pub(crate) fn decode_v2_arm_token_mirrors(
12878 &self,
12879 ws: &mut StepTpDecodeV2Ws,
12880 mixed_stage_e: u64,
12881 shadow_stage_e: (u64, u64),
12882 ) -> Result<(), Box<dyn std::error::Error>> {
12883 use cudarc::driver::DevicePtr;
12884 let root = &self.ranks[0];
12885 let _main = root.gpu.enter_main()?;
12886 let stream = root.stream();
12887 let (a, _g) = ws.reduce_a.device_ptr(&stream);
12888 ws.raw_reduce_a = a as u64;
12889 ws.raw_mixed_stage_e = mixed_stage_e;
12890 ws.raw_shadow_stage_e = shadow_stage_e;
12891 Ok(())
12892 }
12893
12894 fn nvfp4_routes_build_graph(
12900 &self,
12901 experts: &ResidentNvfp4TensorParallel,
12902 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12903 local_out: usize,
12904 n_sel: usize,
12905 activation_limit: Option<f32>,
12906 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12907 use cudarc::driver::DevicePtr;
12908 use cudarc::driver::sys;
12909 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12910 if r == sys::CUresult::CUDA_SUCCESS {
12911 Ok(())
12912 } else {
12913 Err(format!("{what}: {r:?}").into())
12914 }
12915 }
12916 let world = self.ranks.len();
12917 if world != 2 {
12918 return Err("routes graph door is built for the TP2 pair".into());
12919 }
12920 let width = experts.input_width;
12921
12922 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
12924 let stream = engine.stream();
12925 let (ptr, _g) = buf.device_ptr(&stream);
12926 ptr as u64
12927 };
12928 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
12929 let stream = engine.stream();
12930 let (ptr, _g) = buf.device_ptr(&stream);
12931 ptr as u64
12932 };
12933 let (sel_e, w_e) = workspace
12934 .dev_route_e
12935 .as_ref()
12936 .expect("device route staging set before graph build");
12937 let root_engine = &self.ranks[0];
12938 let p_in_stage = ptr_f32(
12939 workspace.in_stage_e.as_ref().expect("graph staging"),
12940 root_engine,
12941 );
12942 let p_out_stage = ptr_f32(
12943 workspace.out_stage_e.as_ref().expect("graph staging"),
12944 root_engine,
12945 );
12946 let p_sel_e = ptr_i32(sel_e, root_engine);
12947 let p_w_e = ptr_f32(w_e, root_engine);
12948 let p_input: Vec<u64> = (0..world)
12949 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
12950 .collect();
12951 let p_sel: Vec<u64> = (0..world)
12952 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
12953 .collect();
12954 let p_route_w: Vec<u64> = (0..world)
12955 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
12956 .collect();
12957 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
12958 let p_remote = ptr_f32(&workspace.remote, root_engine);
12959 let p_combined = ptr_f32(&workspace.combined, root_engine);
12960
12961 let raw_copy = |dst: u64,
12962 src: u64,
12963 bytes: usize,
12964 engine: &Engine|
12965 -> Result<(), Box<dyn std::error::Error>> {
12966 unsafe {
12967 cu_try(
12968 sys::cuMemcpyAsync(
12969 dst as sys::CUdeviceptr,
12970 src as sys::CUdeviceptr,
12971 bytes,
12972 engine.stream().cu_stream() as sys::CUstream,
12973 ),
12974 "routes graph cuMemcpyAsync",
12975 )
12976 }
12977 };
12978
12979 let mut children = Vec::with_capacity(3);
12980 for rank in 0..world {
12981 let engine = &self.ranks[rank];
12982 let _main = engine.gpu.enter_main()?;
12983 let (child, _retained) = engine.capture_graph_retained(|_| {
12984 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
12985 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
12986 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
12987 {
12988 let Nvfp4DeviceRoutesWorkspace {
12989 input, in_q, in_d, ..
12990 } = &mut *workspace;
12991 engine.quantize_q8_1_into(
12992 &input[rank],
12993 1,
12994 width,
12995 &mut in_q[rank],
12996 &mut in_d[rank],
12997 )?;
12998 }
12999 self.nvfp4_routes_batched_sweeps_rank(
13000 experts,
13001 workspace,
13002 &[],
13003 &[],
13004 &[],
13005 local_out,
13006 n_sel,
13007 activation_limit,
13008 true,
13009 rank,
13010 )?;
13011 Ok(())
13012 })?;
13013 children.push(child);
13014 }
13015 {
13016 let root = &self.ranks[0];
13017 let _main = root.gpu.enter_main()?;
13018 let (child, _retained) = root.capture_graph_retained(|_| {
13019 raw_copy(p_remote, p_acc1, width * 4, root)?;
13020 {
13021 let Nvfp4DeviceRoutesWorkspace {
13022 accumulator,
13023 remote,
13024 combined,
13025 ..
13026 } = &mut *workspace;
13027 root.add(&accumulator[0], remote, combined, width)?;
13028 }
13029 raw_copy(p_out_stage, p_combined, width * 4, root)?;
13030 Ok(())
13031 })?;
13032 children.push(child);
13033 }
13034
13035 let mut parent: sys::CUgraph = std::ptr::null_mut();
13036 unsafe {
13037 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
13038 }
13039 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
13040 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
13041 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
13042 unsafe {
13043 cu_try(
13044 sys::cuGraphAddChildGraphNode(
13045 &mut n0,
13046 parent,
13047 std::ptr::null(),
13048 0,
13049 children[0].cu_graph(),
13050 ),
13051 "routes child r0",
13052 )?;
13053 cu_try(
13054 sys::cuGraphAddChildGraphNode(
13055 &mut n1,
13056 parent,
13057 std::ptr::null(),
13058 0,
13059 children[1].cu_graph(),
13060 ),
13061 "routes child r1",
13062 )?;
13063 let deps = [n0, n1];
13064 cu_try(
13065 sys::cuGraphAddChildGraphNode(
13066 &mut n2,
13067 parent,
13068 deps.as_ptr(),
13069 2,
13070 children[2].cu_graph(),
13071 ),
13072 "routes child root",
13073 )?;
13074 }
13075 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
13076 unsafe {
13077 cu_try(
13078 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
13079 "routes instantiate",
13080 )?;
13081 }
13082 Ok(RoutesGraph {
13083 exec,
13084 parent,
13085 _children: children,
13086 })
13087 }
13088
13089 #[allow(clippy::too_many_arguments)]
13093 pub(crate) fn routes_rank_section(
13094 &self,
13095 experts: &ResidentNvfp4TensorParallel,
13096 workspace: &mut Nvfp4DeviceRoutesWorkspace,
13097 raw_input_src: u64,
13098 local_out: usize,
13099 n_sel: usize,
13100 activation_limit: Option<f32>,
13101 rank_index: usize,
13102 ) -> Result<(), Box<dyn std::error::Error>> {
13103 let engine = &self.ranks[rank_index];
13104 {
13105 let _main = engine.gpu.enter_main()?;
13106 let (sel_e_ptr, w_e_ptr) = workspace
13108 .raw_dev_route_e
13109 .ok_or("routes rank section requires armed staging pointers")?;
13110 raw_copy_bytes(
13111 workspace.raw_input[rank_index],
13112 raw_input_src,
13113 experts.input_width * 4,
13114 engine,
13115 )?;
13116 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
13117 raw_copy_bytes(
13118 workspace.raw_route_w[rank_index],
13119 w_e_ptr,
13120 n_sel * 4,
13121 engine,
13122 )?;
13123 {
13124 let Nvfp4DeviceRoutesWorkspace {
13125 input, in_q, in_d, ..
13126 } = &mut *workspace;
13127 engine.quantize_q8_1_into(
13128 &input[rank_index],
13129 1,
13130 experts.input_width,
13131 &mut in_q[rank_index],
13132 &mut in_d[rank_index],
13133 )?;
13134 }
13135 }
13136 self.nvfp4_routes_batched_sweeps_rank(
13137 experts,
13138 workspace,
13139 &[],
13140 &[],
13141 &[],
13142 local_out,
13143 n_sel,
13144 activation_limit,
13145 true,
13146 rank_index,
13147 )
13148 }
13149
13150 pub(crate) fn routes_root_section(
13153 &self,
13154 experts: &ResidentNvfp4TensorParallel,
13155 workspace: &mut Nvfp4DeviceRoutesWorkspace,
13156 ) -> Result<(), Box<dyn std::error::Error>> {
13157 let root = &self.ranks[0];
13158 let _main = root.gpu.enter_main()?;
13159 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
13160 .raw_combine
13161 .ok_or("routes root section requires armed combine pointers")?;
13162 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
13163 {
13164 let Nvfp4DeviceRoutesWorkspace {
13165 accumulator,
13166 remote,
13167 combined,
13168 ..
13169 } = &mut *workspace;
13170 root.add(&accumulator[0], remote, combined, experts.input_width)?;
13171 }
13172 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
13173 Ok(())
13174 }
13175
13176 pub(crate) fn routes_arm_raw(
13179 &self,
13180 experts: &ResidentNvfp4TensorParallel,
13181 workspace: &mut Nvfp4DeviceRoutesWorkspace,
13182 ) -> Result<(), Box<dyn std::error::Error>> {
13183 use cudarc::driver::DevicePtr;
13184 if workspace.raw_dev_route_e.is_some() {
13185 return Ok(());
13186 }
13187 let _ = experts;
13188 let (sel_e, w_e) = workspace
13189 .dev_route_e
13190 .as_ref()
13191 .ok_or("routes staging not armed")?;
13192 let root = &self.ranks[0];
13193 {
13194 let _main = root.gpu.enter_main()?;
13195 let stream = root.stream();
13196 let (a, _g) = sel_e.device_ptr(&stream);
13197 let (b, _g) = w_e.device_ptr(&stream);
13198 workspace.raw_dev_route_e = Some((a as u64, b as u64));
13199 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
13200 let (d, _g) = workspace.remote.device_ptr(&stream);
13201 let (f, _g) = workspace.combined.device_ptr(&stream);
13202 let out_stage = workspace
13203 .out_stage_e
13204 .as_ref()
13205 .ok_or("routes out stage not armed")?;
13206 let (g_, _g) = out_stage.device_ptr(&stream);
13207 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
13208 }
13209 for rank in 0..self.ranks.len() {
13210 let engine = &self.ranks[rank];
13211 let _main = engine.gpu.enter_main()?;
13212 let stream = engine.stream();
13213 let (a, _g) = workspace.input[rank].device_ptr(&stream);
13214 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
13215 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
13216 workspace.raw_input.push(a as u64);
13217 workspace.raw_sel.push(b as u64);
13218 workspace.raw_route_w.push(c as u64);
13219 }
13220 Ok(())
13221 }
13222
13223 pub fn run_tensor_parallel_routes_nvfp4(
13227 &self,
13228 experts: &ResidentNvfp4TensorParallel,
13229 input: &[f32],
13230 tokens: usize,
13231 selected: &[usize],
13232 route_weights: &[f32],
13233 experts_per_token: usize,
13234 activation_limit: Option<f32>,
13235 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
13236 validate_activations(input, tokens, experts.input_width)?;
13237 let pairs = tokens
13238 .checked_mul(experts_per_token)
13239 .ok_or("NVFP4 TP route count overflow")?;
13240 if selected.len() != pairs || route_weights.len() != pairs {
13241 return Err(format!(
13242 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
13243 {experts_per_token} ({pairs})",
13244 selected.len(),
13245 route_weights.len(),
13246 )
13247 .into());
13248 }
13249 if !route_weights.iter().all(|weight| weight.is_finite()) {
13250 return Err("NVFP4 TP route weights contain a non-finite value".into());
13251 }
13252
13253 let mut output = vec![0.0f32; tokens * experts.input_width];
13254 for token in 0..tokens {
13255 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
13256 for slot in 0..experts_per_token {
13257 let pair = token * experts_per_token + slot;
13258 let expert = selected[pair];
13259 if expert >= experts.expert_count {
13260 return Err(format!(
13261 "NVFP4 TP selected expert {expert} outside 0..{}",
13262 experts.expert_count
13263 )
13264 .into());
13265 }
13266 let gate = if experts.ep2 {
13272 self.run_full_bank_expert_nvfp4(
13273 &experts.gate,
13274 &experts.macros_gate,
13275 expert,
13276 input_row,
13277 )?
13278 } else {
13279 self.run_column_bank_expert_nvfp4(
13280 &experts.gate,
13281 &experts.macros_gate,
13282 expert,
13283 input_row,
13284 )?
13285 };
13286 let up = if experts.ep2 {
13287 self.run_full_bank_expert_nvfp4(
13288 &experts.up,
13289 &experts.macros_up,
13290 expert,
13291 input_row,
13292 )?
13293 } else {
13294 self.run_column_bank_expert_nvfp4(
13295 &experts.up,
13296 &experts.macros_up,
13297 expert,
13298 input_row,
13299 )?
13300 };
13301 let activated: Vec<f32> = gate
13302 .iter()
13303 .zip(&up)
13304 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
13305 .collect();
13306 debug_assert_eq!(activated.len(), experts.expert_width);
13307 let down = if experts.ep2 {
13308 self.run_full_down_expert_nvfp4(
13309 &experts.down,
13310 &experts.macros_down,
13311 expert,
13312 &activated,
13313 )?
13314 } else {
13315 self.run_row_bank_expert_nvfp4(
13316 &experts.down,
13317 &experts.macros_down,
13318 expert,
13319 &activated,
13320 )?
13321 };
13322 let weight = route_weights[pair];
13323 for (sum, value) in output
13324 [token * experts.input_width..(token + 1) * experts.input_width]
13325 .iter_mut()
13326 .zip(down)
13327 {
13328 *sum += weight * value;
13329 }
13330 }
13331 }
13332 Ok(output)
13333 }
13334}
13335
13336#[cfg(test)]
13337mod bank_v2_layout_tests {
13338 use super::{nvfp4_matrix_v2_permute, nvfp4_row_bytes};
13339
13340 #[test]
13351 fn the_v2_bank_row_is_the_documented_slot_major_permutation() {
13352 let (out_f, in_f) = (2usize, 128usize);
13354 let row_bytes = nvfp4_row_bytes(in_f);
13355 assert_eq!(row_bytes, 72);
13356 let v1: Vec<u8> = (0..out_f * row_bytes).map(|i| (i % 251) as u8).collect();
13357 let v2 = nvfp4_matrix_v2_permute(&v1, out_f, in_f);
13358 assert_eq!(v2.len(), v1.len(), "a permutation cannot change the size");
13359 let n_slots = in_f / 32;
13360 for row in 0..out_f {
13361 let src = &v1[row * row_bytes..(row + 1) * row_bytes];
13362 let dst = &v2[row * row_bytes..(row + 1) * row_bytes];
13363 for g in 0..n_slots {
13364 let (sblk, h) = (g / 2, g % 2);
13365 let sb = &src[sblk * 36..sblk * 36 + 36];
13366 assert_eq!(
13367 &dst[g * 16..g * 16 + 16],
13368 &sb[4 + 16 * h..4 + 16 * h + 16],
13369 "row {row} slot {g} codes"
13370 );
13371 assert_eq!(
13372 &dst[n_slots * 16 + g * 2..n_slots * 16 + g * 2 + 2],
13373 &sb[2 * h..2 * h + 2],
13374 "row {row} slot {g} scales"
13375 );
13376 }
13377 let (mut a, mut b) = (src.to_vec(), dst.to_vec());
13379 a.sort_unstable();
13380 b.sort_unstable();
13381 assert_eq!(a, b, "row {row} is not a byte permutation");
13382 }
13383 }
13384}
13385
13386#[cfg(test)]
13387mod tests {
13388
13389 #[test]
13396 fn the_retired_rows_tab_key_cannot_see_the_v_and_len_pointers_it_hands_back() {
13397 let (kp, bp) = (0xdead_0000u64, 0u64);
13398 let live = [[kp, 0x00b1_0000u64, 0x00c1_0000u64, bp]];
13399 let recycled = [[kp, 0x00b2_0000u64, 0x00c2_0000u64, bp]];
13400 assert_eq!(
13401 super::retired_rows_tab_key(kp, bp, 20, 2),
13402 super::retired_rows_tab_key(kp, bp, 20, 2),
13403 "same layer and t must hash the same, or the test proves nothing"
13404 );
13405 let a = super::rows_tab_host(&live, 0x9000, true, 1);
13406 let b = super::rows_tab_host(&recycled, 0x9000, true, 1);
13407 assert_ne!(a, b, "the two generations write DIFFERENT tables");
13408 assert_eq!(
13410 super::retired_rows_tab_key(live[0][0], live[0][3], 20, 1),
13411 super::retired_rows_tab_key(recycled[0][0], recycled[0][3], 20, 1),
13412 "the retired key collides across allocation generations"
13413 );
13414 }
13415
13416 #[test]
13419 fn rows_tab_layout_is_the_same_bytes_the_memo_would_have_cached() {
13420 let parts = [
13421 [0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64],
13422 [0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64],
13423 ];
13424 let same = super::rows_tab_host(&parts, 0x7000, true, 2);
13425 assert_eq!(
13426 same,
13427 vec![
13428 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000,
13429 1, 0x00a1u64, 0x00b1u64, 0x00c1u64, 0x00d1u64, 0x7000, 0, ],
13432 "same-session rows share one counter cell and step back t-1-r"
13433 );
13434 let cross = super::rows_tab_host(&parts, 0x7000, false, 2);
13435 assert_eq!(
13436 cross,
13437 vec![
13438 0x00a0u64, 0x00b0u64, 0x00c0u64, 0x00d0u64, 0x7000, 0, 0x00a1u64, 0x00b1u64,
13439 0x00c1u64, 0x00d1u64, 0x7004, 0,
13440 ],
13441 "cross-session rows get their own counter cell and no step back"
13442 );
13443 }
13444 use super::*;
13445
13446 #[test]
13447 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
13448 let limit = Some(7.0);
13449 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
13450 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
13451 assert!(
13452 step_expert_activation_host(-20.0, 9.0, limit).abs()
13453 < step_expert_activation_host(-20.0, 9.0, None).abs()
13454 );
13455 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
13456 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
13457 assert!(validate_step_expert_activation_limit(limit).is_ok());
13458 }
13459
13460 #[test]
13461 fn moe_residual_host_preserves_official_add_order() {
13462 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
13463 assert_eq!(output, [0.0]);
13464 assert_eq!(
13465 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
13466 "MoE residual lengths residual=1 routed=2 shared=1"
13467 );
13468 }
13469
13470 #[test]
13471 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
13472 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
13473 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
13474 assert_eq!(owners.len(), 4);
13475 for (rank, owner) in owners.iter().enumerate() {
13476 assert_eq!(owner.rank, rank);
13477 assert_eq!(owner.selected, vec![0, 36]);
13478 assert_eq!(owner.token_rows, vec![0, 0]);
13479 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
13480 }
13481 }
13482
13483 #[test]
13484 fn expert_owner_routes_validate_geometry_and_selected_experts() {
13485 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
13486 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
13487 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
13488 assert!(error.contains("outside 0..288"));
13489 }
13490
13491 #[test]
13492 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
13493 let selected = [
13494 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
13495 ];
13496 assert_eq!(
13497 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
13498 16
13499 );
13500 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
13501 assert_eq!(
13502 owners
13503 .iter()
13504 .map(|owner| owner.selected.len())
13505 .collect::<Vec<_>>(),
13506 vec![2, 4, 6, 4]
13507 );
13508 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
13509 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
13510 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
13511 }
13512
13513 #[test]
13514 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
13515 let owner0 = [0usize, 3];
13516 let owner1 = [1usize, 2];
13517 let owners = [owner0.as_slice(), owner1.as_slice()];
13518 assert_eq!(
13519 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
13520 .unwrap(),
13521 WeightedRouteCombineShape {
13522 pairs: 4,
13523 max_pairs: 12,
13524 }
13525 );
13526 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
13527 assert!(
13528 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
13529 .is_err()
13530 );
13531 assert!(
13532 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
13533 .is_err()
13534 );
13535 assert!(
13536 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
13537 .is_err()
13538 );
13539 }
13540
13541 #[test]
13542 fn native_p2p_door_is_strict_and_default_off() {
13543 assert!(!parse_step_tp_native_p2p(None).unwrap());
13544 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
13545 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
13546 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
13547 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
13548 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
13549 }
13550
13551 #[test]
13552 fn bulk_p2p_door_is_strict_and_default_off() {
13553 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
13554 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
13555 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
13556 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
13557 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
13558 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
13559 }
13560
13561 #[test]
13562 fn ep_device_arithmetic_door_is_strict_and_default_off() {
13563 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
13564 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
13565 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
13566 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
13567 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
13568 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
13569 }
13570
13571 #[test]
13572 fn f32_mirror_door_is_strict_and_default_off() {
13573 assert!(!parse_step_tp_f32_mirror(None).unwrap());
13574 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
13575 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
13576 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
13577 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
13578 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
13579 }
13580
13581 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
13582 let codes = (0..out_features * in_features)
13583 .map(|index| (index % 251) as u8)
13584 .collect();
13585 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
13586 .map(|index| index as f32 + 1.0)
13587 .collect();
13588 (codes, scales)
13589 }
13590
13591 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
13592 (0..out_features * in_features)
13593 .flat_map(|value| (value as u16).to_le_bytes())
13594 .collect()
13595 }
13596
13597 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
13598 bytes
13599 .chunks_exact(2)
13600 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
13601 .collect()
13602 }
13603
13604 #[test]
13605 fn bf16_matrix_rejects_wrong_byte_count() {
13606 let bytes = vec![0u8; 4 * 4 * 2 - 1];
13607 let matrix = Bf16Matrix {
13608 bytes: &bytes,
13609 out_features: 4,
13610 in_features: 4,
13611 };
13612 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
13613 }
13614
13615 #[test]
13616 fn replicated_device_rows_require_exact_rank_local_shapes() {
13617 assert_eq!(
13618 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
13619 12_288
13620 );
13621 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
13622 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
13623 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
13624 assert!(
13625 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
13626 );
13627 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
13628 }
13629
13630 #[test]
13631 fn replicated_device_row_refresh_requires_exact_root_source() {
13632 assert_eq!(
13633 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
13634 12_288
13635 );
13636 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
13637 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
13638 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
13639 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
13640 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
13641 }
13642
13643 #[test]
13644 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
13645 for tp in [1, 2, 4, 8] {
13646 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
13647 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
13648 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
13649 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
13650 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
13651 }
13652 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
13653 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
13654 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
13655 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
13656 }
13657
13658 #[test]
13659 fn cache_rows_split_by_token_then_rank() {
13660 let rows = (0u8..24).collect::<Vec<_>>();
13661 assert_eq!(
13662 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
13663 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
13664 );
13665 assert_eq!(
13666 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
13667 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
13668 );
13669 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
13670 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
13671 }
13672
13673 #[test]
13674 fn bf16_column_shard_preserves_contiguous_output_rows() {
13675 let bytes = bf16_matrix_bytes(4, 4);
13676 let matrix = Bf16Matrix {
13677 bytes: &bytes,
13678 out_features: 4,
13679 in_features: 4,
13680 };
13681 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
13682 assert_eq!(shard.out_features, 2);
13683 assert_eq!(shard.in_features, 4);
13684 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
13685 }
13686
13687 #[test]
13688 fn bf16_row_shard_preserves_each_input_column_window() {
13689 let bytes = bf16_matrix_bytes(3, 4);
13690 let matrix = Bf16Matrix {
13691 bytes: &bytes,
13692 out_features: 3,
13693 in_features: 4,
13694 };
13695 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
13696 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
13697 }
13698
13699 #[test]
13700 fn bf16_row_block_preserves_global_column_order() {
13701 let bytes = bf16_matrix_bytes(3, 8);
13702 let matrix = Bf16Matrix {
13703 bytes: &bytes,
13704 out_features: 3,
13705 in_features: 8,
13706 };
13707 let block = bf16_row_block(matrix, 2, 3).unwrap();
13708 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
13709 }
13710
13711 #[test]
13712 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
13713 let (codes, scales) = matrix(1280, 4096);
13714 let matrix = E4m3BlockMatrix {
13715 codes: &codes,
13716 scales: &scales,
13717 out_features: 1280,
13718 in_features: 4096,
13719 };
13720 let shard = column_shard(matrix, 2, 1).unwrap();
13721 assert_eq!(shard.out_features, 640);
13722 assert_eq!(shard.codes, &codes[640 * 4096..]);
13723 assert_eq!(shard.scales, &scales[5 * 32..]);
13724 }
13725
13726 #[test]
13727 fn row_shard_preserves_each_weight_and_scale_column_window() {
13728 let (codes, scales) = matrix(4096, 1280);
13729 let matrix = E4m3BlockMatrix {
13730 codes: &codes,
13731 scales: &scales,
13732 out_features: 4096,
13733 in_features: 1280,
13734 };
13735 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
13736 assert_eq!(shard_codes.len(), 4096 * 640);
13737 assert_eq!(&shard_codes[..640], &codes[640..1280]);
13738 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
13739 assert_eq!(shard_scales.len(), 32 * 5);
13740 assert_eq!(&shard_scales[..5], &scales[5..10]);
13741 assert_eq!(&shard_scales[5..10], &scales[15..20]);
13742 }
13743
13744 #[test]
13745 fn activation_shards_keep_token_rows_separate() {
13746 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
13747 assert_eq!(
13748 activation_shard(&activations, 2, 8, 2, 1),
13749 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
13750 );
13751 }
13752
13753 #[test]
13754 fn expert_bank_selects_expert_major_code_and_scale_planes() {
13755 let expert_count = 2;
13756 let out_features = 128;
13757 let in_features = 128;
13758 let code_stride = out_features * in_features;
13759 let codes: Vec<u8> = (0..expert_count * code_stride)
13760 .map(|index| (index % 251) as u8)
13761 .collect();
13762 let scales = vec![1.0f32, 2.0];
13763 let bank = E4m3ExpertBank {
13764 codes: &codes,
13765 scales: &scales,
13766 expert_count,
13767 out_features,
13768 in_features,
13769 };
13770 bank.validate().unwrap();
13771 let expert = bank.expert(1).unwrap();
13772 assert_eq!(expert.codes, &codes[code_stride..]);
13773 assert_eq!(expert.scales, &[2.0]);
13774 }
13775
13776 #[test]
13777 fn expert_bank_rejects_non_positive_scale() {
13778 let codes = vec![0u8; 128 * 128];
13779 let scales = vec![0.0f32];
13780 let bank = E4m3ExpertBank {
13781 codes: &codes,
13782 scales: &scales,
13783 expert_count: 1,
13784 out_features: 128,
13785 in_features: 128,
13786 };
13787 assert!(bank.validate().unwrap_err().contains("non-positive"));
13788 }
13789
13790 #[test]
13791 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
13792 let expert_count = 2;
13793 let out_features = 256;
13794 let in_features = 128;
13795 let code_stride = out_features * in_features;
13796 let scale_stride = 2;
13797 let codes = (0..expert_count * code_stride)
13798 .map(|index| (index % 251) as u8)
13799 .collect::<Vec<_>>();
13800 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
13801 let bank = E4m3ExpertBank {
13802 codes: &codes,
13803 scales: &scales,
13804 expert_count,
13805 out_features,
13806 in_features,
13807 };
13808
13809 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
13810 assert_eq!(rank.out_features, 128);
13811 assert_eq!(rank.in_features, 128);
13812 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
13813 assert_eq!(rank.scales, vec![11.0, 21.0]);
13814 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
13815 assert_eq!(
13816 &rank.codes[128 * 128..],
13817 &codes[code_stride + 128 * 128..2 * code_stride]
13818 );
13819 assert_eq!(scale_stride, scales.len() / expert_count);
13820 }
13821
13822 #[test]
13823 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
13824 let expert_count = 2;
13825 let out_features = 128;
13826 let in_features = 256;
13827 let code_stride = out_features * in_features;
13828 let codes = (0..expert_count * code_stride)
13829 .map(|index| (index % 251) as u8)
13830 .collect::<Vec<_>>();
13831 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
13832 let bank = E4m3ExpertBank {
13833 codes: &codes,
13834 scales: &scales,
13835 expert_count,
13836 out_features,
13837 in_features,
13838 };
13839
13840 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
13841 assert_eq!(rank.out_features, 128);
13842 assert_eq!(rank.in_features, 128);
13843 assert_eq!(rank.k_blocks, Some(1));
13844 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
13845 assert_eq!(rank.scales, vec![11.0, 21.0]);
13846 assert_eq!(&rank.codes[..128], &codes[128..256]);
13847 assert_eq!(
13848 &rank.codes[128 * 128..128 * 128 + 128],
13849 &codes[code_stride + 128..code_stride + 256]
13850 );
13851 }
13852
13853 #[test]
13854 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
13855 let expert_count = 2;
13856 let out_features = 256;
13857 let in_features = 512;
13858 let code_stride = out_features * in_features;
13859 let mut codes = vec![0u8; expert_count * code_stride];
13860 for expert in 0..expert_count {
13861 for row in 0..out_features {
13862 for block in 0..4 {
13863 let value = (expert * 80 + block * 16 + row % 16) as u8;
13864 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
13865 codes[start..start + FP8_BLOCK].fill(value);
13866 }
13867 }
13868 }
13869 let scales = vec![
13870 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,
13871 112.0, 113.0, 114.0,
13872 ];
13873 let bank = E4m3ExpertBank {
13874 codes: &codes,
13875 scales: &scales,
13876 expert_count,
13877 out_features,
13878 in_features,
13879 };
13880
13881 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
13882 assert_eq!(rank.out_features, out_features);
13883 assert_eq!(rank.in_features, 256);
13884 assert_eq!(rank.k_blocks, Some(2));
13885 assert_eq!(rank.code_stride, out_features * 256);
13886 assert_eq!(rank.scale_stride, 4);
13887 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
13888 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
13889
13890 let block_stride = out_features * FP8_BLOCK;
13891 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
13892 assert!(
13893 rank.codes[block_stride..block_stride + FP8_BLOCK]
13894 .iter()
13895 .all(|&code| code == 48)
13896 );
13897 assert!(
13898 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
13899 .iter()
13900 .all(|&code| code == 112)
13901 );
13902 assert!(
13903 rank.codes
13904 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
13905 .iter()
13906 .all(|&code| code == 128)
13907 );
13908 }
13909
13910 #[test]
13911 fn step_ep_layer_specs_are_literal_and_fail_closed() {
13912 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
13913 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
13914 assert_eq!(
13915 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
13916 vec![StepEpLayerSpec {
13917 layer: 24,
13918 devices: vec![1, 2],
13919 }]
13920 );
13921 assert_eq!(
13922 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
13923 vec![
13924 StepEpLayerSpec {
13925 layer: 24,
13926 devices: vec![1, 2],
13927 },
13928 StepEpLayerSpec {
13929 layer: 25,
13930 devices: vec![1, 2],
13931 },
13932 StepEpLayerSpec {
13933 layer: 31,
13934 devices: vec![0, 2],
13935 },
13936 ]
13937 );
13938 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
13939 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
13940 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
13941 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
13942 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
13943 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13944 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
13945 }
13946
13947 #[test]
13948 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
13949 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
13950 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
13951 assert_eq!(
13952 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
13953 vec![
13954 StepTpLayerSpec {
13955 layer: 24,
13956 devices: vec![1, 2],
13957 },
13958 StepTpLayerSpec {
13959 layer: 25,
13960 devices: vec![1, 2],
13961 },
13962 ]
13963 );
13964 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
13965 assert!(error.contains("MEMRA_STEP_TP"));
13966 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
13967 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13968
13969 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
13970 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
13971 assert_eq!(all.first().unwrap().layer, 0);
13972 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
13973 let devices = (0..8).collect::<Vec<_>>();
13974 assert!(all.iter().all(|spec| spec.devices == devices));
13975 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
13976 }
13977}
13978
13979struct TokenGraphChild {
13991 graph: cudarc::driver::CudaGraph,
13992 node: cudarc::driver::sys::CUgraphNode,
13993 ctx: cudarc::driver::sys::CUcontext,
13994}
13995
13996struct TokenGraphFaSite {
14000 ctx: cudarc::driver::sys::CUcontext,
14001 memset_o: cudarc::driver::sys::CUgraphNode,
14002 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
14003 fa: cudarc::driver::sys::CUgraphNode,
14004 combine: cudarc::driver::sys::CUgraphNode,
14005 window: usize,
14006 n_head: usize,
14007 n_head_kv: usize,
14008 head_dim: usize,
14009}
14010
14011pub struct TokenGraphBuilder {
14012 parent: cudarc::driver::sys::CUgraph,
14013 children: Vec<TokenGraphChild>,
14014 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
14017 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
14020 group: Option<(
14023 u32,
14024 Vec<cudarc::driver::sys::CUgraphNode>,
14025 Vec<cudarc::driver::sys::CUgraphNode>,
14026 )>,
14027}
14028
14029unsafe impl Send for TokenGraphBuilder {}
14031
14032impl TokenGraphBuilder {
14033 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
14034 use cudarc::driver::sys;
14035 let mut parent: sys::CUgraph = std::ptr::null_mut();
14036 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
14037 if r != sys::CUresult::CUDA_SUCCESS {
14038 return Err(format!("token graph create: {r:?}").into());
14039 }
14040 Ok(Self {
14041 parent,
14042 children: Vec::new(),
14043 frontier: Vec::new(),
14044 pending_detached: Vec::new(),
14045 group: None,
14046 })
14047 }
14048
14049 fn push_child(
14050 &mut self,
14051 graph: cudarc::driver::CudaGraph,
14052 parallel_group: Option<u32>,
14053 detached: bool,
14054 absorb: bool,
14055 ctx: cudarc::driver::sys::CUcontext,
14056 ) -> Result<(), Box<dyn std::error::Error>> {
14057 use cudarc::driver::sys;
14058 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
14062 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
14063 (state, Some(group)) => {
14064 if let Some((_, _, members)) = state.take() {
14066 self.frontier = members;
14067 }
14068 let base = self.frontier.clone();
14069 *state = Some((group, base.clone(), Vec::new()));
14070 base
14071 }
14072 (state, None) if detached => match state.as_ref() {
14073 Some((_, base, _)) => base.clone(),
14074 None => self.frontier.clone(),
14075 },
14076 (state, None) => {
14077 if let Some((_, _, members)) = state.take() {
14078 self.frontier = members;
14079 }
14080 let mut deps = self.frontier.clone();
14081 if absorb {
14082 deps.append(&mut self.pending_detached);
14083 }
14084 deps
14085 }
14086 };
14087 let mut node: sys::CUgraphNode = std::ptr::null_mut();
14088 let r = unsafe {
14089 sys::cuGraphAddChildGraphNode(
14090 &mut node,
14091 self.parent,
14092 if deps.is_empty() {
14093 std::ptr::null()
14094 } else {
14095 deps.as_ptr()
14096 },
14097 deps.len(),
14098 graph.cu_graph(),
14099 )
14100 };
14101 if r != sys::CUresult::CUDA_SUCCESS {
14102 return Err(format!("token graph child: {r:?}").into());
14103 }
14104 match (&mut self.group, parallel_group, detached) {
14105 (_, None, true) => self.pending_detached.push(node),
14106 (Some((_, _, members)), Some(_), _) => members.push(node),
14107 _ => self.frontier = vec![node],
14108 }
14109 self.children.push(TokenGraphChild { graph, node, ctx });
14110 Ok(())
14111 }
14112
14113 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
14114 use cudarc::driver::sys;
14115 if let Some((_, _, members)) = self.group.take() {
14116 self.frontier = members;
14117 }
14118 let mut fa_sites = Vec::new();
14121 for child in &self.children {
14122 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
14123 fa_sites.push(site);
14124 }
14125 }
14126 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
14127 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
14128 if r != sys::CUresult::CUDA_SUCCESS {
14129 return Err(format!("token graph instantiate: {r:?}").into());
14130 }
14131 Ok(TokenGraph {
14132 exec,
14133 parent: self.parent,
14134 _children: self.children,
14135 fa_sites,
14136 })
14137 }
14138}
14139
14140fn discover_fa_site(
14143 child_node: cudarc::driver::sys::CUgraphNode,
14144 ctx: cudarc::driver::sys::CUcontext,
14145) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
14146 use cudarc::driver::sys;
14147 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14148 if r == sys::CUresult::CUDA_SUCCESS {
14149 Ok(())
14150 } else {
14151 Err(format!("{what}: {r:?}").into())
14152 }
14153 }
14154 let mut graph: sys::CUgraph = std::ptr::null_mut();
14155 unsafe {
14156 cu_try(
14157 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
14158 "fa-site child GetGraph",
14159 )?;
14160 }
14161 let mut count: usize = 0;
14162 unsafe {
14163 cu_try(
14164 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
14165 "fa-site GetNodes(count)",
14166 )?;
14167 }
14168 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
14169 unsafe {
14170 cu_try(
14171 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
14172 "fa-site GetNodes",
14173 )?;
14174 }
14175 nodes.truncate(count);
14176 let node_type =
14177 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
14178 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
14179 unsafe {
14180 cu_try(
14181 sys::cuGraphNodeGetType(node, &mut ty),
14182 "fa-site NodeGetType",
14183 )?;
14184 }
14185 Ok(ty)
14186 };
14187 let memsets: Vec<sys::CUgraphNode> = {
14188 let mut v = Vec::new();
14189 for &node in &nodes {
14190 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
14191 v.push(node);
14192 }
14193 }
14194 v
14195 };
14196 if memsets.len() != 3 {
14197 return Ok(None);
14198 }
14199 let dependents =
14201 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
14202 let mut n: usize = 0;
14203 unsafe {
14204 cu_try(
14205 sys::cuGraphNodeGetDependentNodes_v2(
14206 node,
14207 std::ptr::null_mut(),
14208 std::ptr::null_mut(),
14209 &mut n,
14210 ),
14211 "fa-site GetDependentNodes(count)",
14212 )?;
14213 }
14214 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
14215 unsafe {
14216 cu_try(
14217 sys::cuGraphNodeGetDependentNodes_v2(
14218 node,
14219 v.as_mut_ptr(),
14220 std::ptr::null_mut(),
14221 &mut n,
14222 ),
14223 "fa-site GetDependentNodes",
14224 )?;
14225 }
14226 v.truncate(n);
14227 Ok(v)
14228 };
14229 let mut fa: Option<sys::CUgraphNode> = None;
14232 let mut last_memset: Option<sys::CUgraphNode> = None;
14233 for &ms in &memsets {
14234 for dep in dependents(ms)? {
14235 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
14236 fa = Some(dep);
14237 last_memset = Some(ms);
14238 }
14239 }
14240 }
14241 let (Some(fa), Some(_last)) = (fa, last_memset) else {
14242 return Ok(None);
14243 };
14244 let mut combine: Option<sys::CUgraphNode> = None;
14245 for dep in dependents(fa)? {
14246 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
14247 combine = Some(dep);
14248 }
14249 }
14250 let Some(combine) = combine else {
14251 return Ok(None);
14252 };
14253 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
14256 unsafe {
14257 cu_try(
14258 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
14259 "fa-site KernelNodeGetParams",
14260 )?;
14261 }
14262 let arg_i32 =
14263 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
14264 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
14265 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
14267 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
14268 unsafe {
14269 cu_try(
14270 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
14271 "fa-site MemsetNodeGetParams",
14272 )?;
14273 }
14274 Ok(mp.width)
14275 };
14276 let mut widest = memsets[0];
14277 for &ms in &memsets[1..] {
14278 if width_of(ms)? > width_of(widest)? {
14279 widest = ms;
14280 }
14281 }
14282 let memset_m: Vec<sys::CUgraphNode> =
14283 memsets.iter().copied().filter(|&m| m != widest).collect();
14284 Ok(Some(TokenGraphFaSite {
14285 ctx,
14286 memset_o: widest,
14287 memset_m: [memset_m[0], memset_m[1]],
14288 fa,
14289 combine,
14290 window: win as usize,
14291 n_head: nh as usize,
14292 n_head_kv: nhkv as usize,
14293 head_dim: hd as usize,
14294 }))
14295}
14296
14297pub struct TokenGraph {
14298 exec: cudarc::driver::sys::CUgraphExec,
14299 parent: cudarc::driver::sys::CUgraph,
14300 _children: Vec<TokenGraphChild>,
14301 fa_sites: Vec<TokenGraphFaSite>,
14302}
14303
14304unsafe impl Send for TokenGraph {}
14305
14306impl TokenGraph {
14307 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
14312 use cudarc::driver::sys;
14313 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
14314 if r == sys::CUresult::CUDA_SUCCESS {
14315 Ok(())
14316 } else {
14317 Err(format!("{what}: {r:?}").into())
14318 }
14319 }
14320 for site in &self.fa_sites {
14321 let layer_bucket = if site.window > 0 {
14322 bucket.min(site.window)
14323 } else {
14324 bucket
14325 };
14326 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
14327 let nsp = layer_bucket.div_ceil(sp).max(1);
14328 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
14330 unsafe {
14331 cu_try(
14332 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
14333 "retarget fa GetParams",
14334 )?;
14335 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
14336 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
14337 params.gridDimY = nsp as u32;
14338 cu_try(
14339 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
14340 "retarget fa SetParams",
14341 )?;
14342 }
14343 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
14345 unsafe {
14346 cu_try(
14347 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
14348 "retarget combine GetParams",
14349 )?;
14350 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
14351 cu_try(
14352 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
14353 "retarget combine SetParams",
14354 )?;
14355 }
14356 let set_width =
14358 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
14359 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
14360 unsafe {
14361 cu_try(
14362 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
14363 "retarget memset GetParams",
14364 )?;
14365 }
14366 mp.width = width;
14367 unsafe {
14368 cu_try(
14369 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
14370 "retarget memset SetParams",
14371 )?;
14372 }
14373 Ok(())
14374 };
14375 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
14376 set_width(site.memset_m[0], site.n_head * nsp)?;
14377 set_width(site.memset_m[1], site.n_head * nsp)?;
14378 }
14379 Ok(())
14380 }
14381
14382 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
14383 use cudarc::driver::sys;
14384 let _main = e.gpu.enter_main()?;
14385 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
14386 if r != sys::CUresult::CUDA_SUCCESS {
14387 return Err(format!("token graph launch: {r:?}").into());
14388 }
14389 Ok(())
14390 }
14391}
14392
14393impl Drop for TokenGraph {
14394 fn drop(&mut self) {
14395 unsafe {
14396 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
14397 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
14398 }
14399 }
14400}
14401
14402std::thread_local! {
14403 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
14404 const { std::cell::RefCell::new(None) };
14405}
14406
14407pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
14409 let builder = TokenGraphBuilder::new()?;
14410 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
14411 Ok(())
14412}
14413
14414pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
14416 let builder = TOKEN_GRAPH_BUILDER
14417 .with(|cell| cell.borrow_mut().take())
14418 .ok_or("token graph build was not begun")?;
14419 builder.finish()
14420}
14421
14422pub fn token_graph_building() -> bool {
14424 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
14425}
14426
14427pub fn graph_section<F>(
14432 engine: &Engine,
14433 parallel_group: Option<u32>,
14434 f: F,
14435) -> Result<(), Box<dyn std::error::Error>>
14436where
14437 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
14438{
14439 graph_section_opts(engine, parallel_group, false, false, f)
14440}
14441
14442pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
14444where
14445 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
14446{
14447 graph_section_opts(engine, None, false, true, f)
14448}
14449
14450pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
14453where
14454 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
14455{
14456 graph_section_opts(engine, None, true, false, f)
14457}
14458
14459pub fn graph_section_opts<F>(
14460 engine: &Engine,
14461 parallel_group: Option<u32>,
14462 detached: bool,
14463 absorb: bool,
14464 f: F,
14465) -> Result<(), Box<dyn std::error::Error>>
14466where
14467 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
14468{
14469 let building = token_graph_building();
14470 if !building {
14471 let mut f = f;
14472 return f();
14473 }
14474 let (child, ctx) = {
14475 let _main = engine.gpu.enter_main()?;
14476 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
14477 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
14478 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
14479 return Err(format!("graph section ctx query: {r:?}").into());
14480 }
14481 let mut f = f;
14482 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
14485 (child, ctx)
14486 };
14487 TOKEN_GRAPH_BUILDER.with(|cell| {
14488 cell.borrow_mut()
14489 .as_mut()
14490 .expect("builder checked above")
14491 .push_child(child, parallel_group, detached, absorb, ctx)
14492 })
14493}