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
14const FP8_BLOCK: usize = 128;
15const NATIVE_P2P_PROBE_WORDS: usize = 4096;
16const STEP_GROUPED_FP8_EXPERTS: usize = 288;
17const STEP_GROUPED_FP8_TOP_K: usize = 8;
18const STEP_GROUPED_FP8_WIDTH: usize = 1280;
19
20fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
21 if let Some(limit) = limit {
22 if !limit.is_finite() || limit <= 0.0 {
23 return Err(format!(
24 "Step routed-expert activation limit must be positive and finite, got {limit}"
25 ));
26 }
27 }
28 Ok(())
29}
30
31pub(crate) fn routes_prestage_on() -> bool {
54 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
55 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
56}
57
58pub(crate) fn oproj_tail_on() -> bool {
76 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
77 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
78}
79thread_local! {
80 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
81 const { std::cell::Cell::new(None) };
82}
83thread_local! {
84 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
89}
90pub(crate) struct OprojTailScope(());
92pub(crate) fn oproj_tail_scope() -> OprojTailScope {
93 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
94 OprojTailScope(())
95}
96impl Drop for OprojTailScope {
97 fn drop(&mut self) {
98 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
99 OPROJ_TAIL_PENDING.with(|c| c.set(None));
101 }
102}
103thread_local! {
104 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
107}
108pub(crate) fn set_verify_tcol(c: Option<usize>) {
109 VERIFY_TCOL.with(|x| x.set(c));
110}
111pub(crate) fn take_verify_tcol() -> Option<usize> {
112 VERIFY_TCOL.with(|x| x.take())
113}
114
115pub(crate) fn tcol_oproj_on() -> bool {
122 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
123 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
124}
125thread_local! {
126 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
130 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
131}
132pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
133 TCOL_OPROJ_DEFER.with(|x| x.set(c));
134}
135pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
136 TCOL_OPROJ_DEFER.with(|x| x.take())
137}
138pub(crate) fn set_tcol_oproj_stashed() {
139 TCOL_OPROJ_STASHED.with(|x| x.set(true));
140}
141pub(crate) fn take_tcol_oproj_stashed() -> bool {
142 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
143}
144
145pub(crate) fn oproj_tail_eligible() -> bool {
146 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
147}
148pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
149 OPROJ_TAIL_PENDING.with(|c| c.take())
150}
151pub(crate) fn set_oproj_tail(v: (u64, u64)) {
152 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
153}
154
155pub(crate) fn rank0_merge_on() -> bool {
156 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
157 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
158}
159
160pub(crate) fn len_mirror_lazy_on() -> bool {
161 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
162 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
163}
164
165pub(crate) fn fence_memops_on() -> bool {
166 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
167 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
168}
169
170pub(crate) fn moe_direct_on() -> bool {
171 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
172 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
173}
174
175pub(crate) fn fence_rank1_on() -> bool {
190 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
191 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
192}
193
194pub(crate) fn spec_fa2_on() -> bool {
200 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
201 *ON.get_or_init(|| std::env::var("MEMRA_SPEC_FA2").as_deref() == Ok("1"))
202}
203thread_local! {
204 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
207 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
208}
209pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
210 SPEC_FA2_DEFER.with(|x| x.set(c));
211}
212pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
213 SPEC_FA2_DEFER.with(|x| x.take())
214}
215pub(crate) fn set_spec_fa2_stashed() {
216 SPEC_FA2_STASHED.with(|x| x.set(true));
217}
218pub(crate) fn take_spec_fa2_stashed() -> bool {
219 SPEC_FA2_STASHED.with(|x| x.replace(false))
220}
221
222pub(crate) fn sel_mirror_on() -> bool {
223 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
224 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
225}
226
227pub(crate) fn step_nvfp4_ep2_on() -> bool {
234 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
235 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
236}
237
238pub(crate) fn sel_down8_on() -> bool {
239 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
240 *ON.get_or_init(|| std::env::var("MEMRA_SEL_DOWN8").as_deref() == Ok("1"))
241}
242
243pub(crate) fn oproj_direct_on() -> bool {
244 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
245 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
246}
247
248pub(crate) fn raw_copy_bytes(
249 dst: u64,
250 src: u64,
251 bytes: usize,
252 engine: &Engine,
253) -> Result<(), Box<dyn std::error::Error>> {
254 use cudarc::driver::sys;
255 let r = unsafe {
256 sys::cuMemcpyAsync(
257 dst as sys::CUdeviceptr,
258 src as sys::CUdeviceptr,
259 bytes,
260 engine.stream().cu_stream() as sys::CUstream,
261 )
262 };
263 if r == sys::CUresult::CUDA_SUCCESS {
264 Ok(())
265 } else {
266 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
269 eprintln!(
270 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
271 std::backtrace::Backtrace::force_capture()
272 );
273 }
274 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
275 }
276}
277
278pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
279 let silu = gate / (1.0 + (-gate).exp());
280 match limit {
281 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
282 None => silu * up,
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
287struct ExpertOwnerRoutes {
288 rank: usize,
289 selected: Vec<usize>,
290 token_rows: Vec<usize>,
291 global_pairs: Vec<usize>,
292}
293
294fn partition_expert_owner_routes(
295 expert_count: usize,
296 ranks: usize,
297 tokens: usize,
298 experts_per_token: usize,
299 selected: &[usize],
300) -> Result<Vec<ExpertOwnerRoutes>, String> {
301 if expert_count == 0
302 || ranks == 0
303 || tokens == 0
304 || experts_per_token == 0
305 || expert_count % ranks != 0
306 {
307 return Err(format!(
308 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
309 tokens={tokens} experts_per_token={experts_per_token}"
310 ));
311 }
312 let pairs = tokens
313 .checked_mul(experts_per_token)
314 .ok_or("expert-owner route count overflow")?;
315 if selected.len() != pairs {
316 return Err(format!(
317 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
318 selected.len()
319 ));
320 }
321 let per_rank = expert_count / ranks;
322 let mut owners = (0..ranks)
323 .map(|rank| ExpertOwnerRoutes {
324 rank,
325 selected: Vec::new(),
326 token_rows: Vec::new(),
327 global_pairs: Vec::new(),
328 })
329 .collect::<Vec<_>>();
330 for (pair, &expert) in selected.iter().enumerate() {
331 if expert >= expert_count {
332 return Err(format!(
333 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
334 ));
335 }
336 let rank = expert / per_rank;
337 owners[rank].selected.push(expert - rank * per_rank);
338 owners[rank].token_rows.push(pair / experts_per_token);
339 owners[rank].global_pairs.push(pair);
340 }
341 Ok(owners)
342}
343
344fn validate_step_grouped_owner_routes(
345 expert_count: usize,
346 tokens: usize,
347 selected: &[usize],
348) -> Result<usize, String> {
349 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
350 return Err(format!(
351 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
352 experts={expert_count} tokens={tokens}",
353 STEP_GROUPED_FP8_EXPERTS
354 ));
355 }
356 let pairs = tokens
357 .checked_mul(STEP_GROUPED_FP8_TOP_K)
358 .ok_or("official Step owner-grouped FP8 route count overflow")?;
359 if selected.len() != pairs {
360 return Err(format!(
361 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
362 selected.len(),
363 STEP_GROUPED_FP8_TOP_K,
364 ));
365 }
366 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
367 let mut unique = routes.to_vec();
368 unique.sort_unstable();
369 unique.dedup();
370 if unique.len() != STEP_GROUPED_FP8_TOP_K {
371 return Err(format!(
372 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
373 {routes:?}"
374 ));
375 }
376 }
377 Ok(pairs)
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381struct WeightedRouteCombineShape {
382 pairs: usize,
383 max_pairs: usize,
384}
385
386fn validate_weighted_route_combine(
387 width: usize,
388 experts_per_token: usize,
389 max_tokens: usize,
390 tokens: usize,
391 owner_global_pairs: &[&[usize]],
392 route_weights: &[f32],
393) -> Result<WeightedRouteCombineShape, String> {
394 if width == 0
395 || experts_per_token == 0
396 || max_tokens == 0
397 || tokens == 0
398 || tokens > max_tokens
399 || width > i32::MAX as usize
400 || experts_per_token > i32::MAX as usize
401 || tokens > i32::MAX as usize
402 {
403 return Err(format!(
404 "invalid weighted route combine geometry width={width} experts_per_token=\
405 {experts_per_token} tokens={tokens}/{max_tokens}"
406 ));
407 }
408 let pairs = tokens
409 .checked_mul(experts_per_token)
410 .ok_or("weighted route combine pair count overflow")?;
411 let max_pairs = max_tokens
412 .checked_mul(experts_per_token)
413 .ok_or("weighted route combine capacity overflow")?;
414 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
415 return Err(format!(
416 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
417 route_weights.len()
418 ));
419 }
420 let mut seen = vec![false; pairs];
421 let mut observed = 0usize;
422 for pairs_for_owner in owner_global_pairs {
423 observed = observed
424 .checked_add(pairs_for_owner.len())
425 .ok_or("weighted route combine observed pair count overflow")?;
426 for &pair in *pairs_for_owner {
427 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
428 return Err(format!(
429 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
430 ));
431 }
432 }
433 }
434 if observed != pairs || seen.iter().any(|present| !present) {
435 return Err(format!(
436 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
437 ));
438 }
439 Ok(WeightedRouteCombineShape { pairs, max_pairs })
440}
441
442fn cache_rank_rows(
443 rows: &[u8],
444 tokens: usize,
445 local_token_bytes: usize,
446 ranks: usize,
447 rank: usize,
448) -> Result<Vec<u8>, String> {
449 if ranks == 0 || rank >= ranks {
450 return Err(format!(
451 "TP cache rank {rank} is outside a {ranks}-rank layout"
452 ));
453 }
454 let global_token_bytes = local_token_bytes
455 .checked_mul(ranks)
456 .ok_or("TP cache global token-byte overflow")?;
457 let expected = tokens
458 .checked_mul(global_token_bytes)
459 .ok_or("TP cache row-byte overflow")?;
460 if rows.len() != expected {
461 return Err(format!(
462 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
463 rows.len()
464 ));
465 }
466 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
467 for token in 0..tokens {
468 let start = token * global_token_bytes + rank * local_token_bytes;
469 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
470 }
471 Ok(shard)
472}
473
474fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
475 match value {
476 None | Some("") | Some("0") => Ok(false),
477 Some("1") => Ok(true),
478 Some(value) => Err(format!(
479 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
480 )),
481 }
482}
483
484pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
485 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
486}
487
488fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
489 match value {
490 None | Some("") | Some("0") => Ok(false),
491 Some("1") => Ok(true),
492 Some(value) => Err(format!(
493 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
494 )),
495 }
496}
497
498pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
499 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
500}
501
502fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
503 match value {
504 None | Some("") | Some("0") => Ok(false),
505 Some("1") => Ok(true),
506 Some(value) => Err(format!(
507 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
508 )),
509 }
510}
511
512fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
513 match value {
514 None | Some("") | Some("0") => Ok(false),
515 Some("1") => Ok(true),
516 Some(value) => Err(format!(
517 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
518 )),
519 }
520}
521
522pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
525 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
526}
527
528pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
529 parse_step_ep_device_arithmetic(
530 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
531 .ok()
532 .as_deref(),
533 )
534}
535
536fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
537 match value {
538 None | Some("") | Some("0") => Ok(false),
539 Some("1") => Ok(true),
540 Some(value) => Err(format!(
541 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
542 )),
543 }
544}
545
546pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
547 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
548}
549
550fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
551 match value {
552 None | Some("") | Some("0") => Ok(false),
553 Some("1") => Ok(true),
554 Some(value) => Err(format!(
555 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
556 )),
557 }
558}
559
560pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
565 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
566}
567
568fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
569 match value {
570 None | Some("") | Some("0") => Ok(false),
571 Some("1") => Ok(true),
572 Some(value) => Err(format!(
573 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
574 )),
575 }
576}
577
578fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
579 match value {
580 None | Some("") | Some("0") => Ok(false),
581 Some("1") => Ok(true),
582 Some(value) => Err(format!(
583 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
584 )),
585 }
586}
587
588pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
592 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
593}
594
595fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
596 match value {
597 None | Some("") | Some("0") => Ok(false),
598 Some("1") => Ok(true),
599 Some(value) => Err(format!(
600 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
601 )),
602 }
603}
604
605fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
606 match value {
607 None | Some("") | Some("0") => Ok(false),
608 Some("1") => Ok(true),
609 Some(value) => Err(format!(
610 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
611 )),
612 }
613}
614
615pub fn step_tp_dcw_enabled() -> Result<bool, String> {
620 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
621}
622
623pub fn step_tp_graph_enabled() -> Result<bool, String> {
628 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
629}
630
631pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
635 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
636}
637
638#[derive(Debug, Clone, PartialEq, Eq)]
639pub struct StepEpLayerSpec {
640 pub layer: usize,
641 pub devices: Vec<usize>,
642}
643
644pub type StepTpLayerSpec = StepEpLayerSpec;
645
646fn parse_step_layer_specs(
647 flag: &str,
648 value: Option<&str>,
649 allow_full_model: bool,
650) -> Result<Vec<StepEpLayerSpec>, String> {
651 let Some(value) = value else {
652 return Ok(Vec::new());
653 };
654 if value.is_empty() || value == "0" {
655 return Ok(Vec::new());
656 }
657
658 let mut specs = Vec::new();
659 for item in value.split(';') {
660 let (layers, devices) = item.split_once('@').ok_or_else(|| {
661 let layers = if allow_full_model {
662 "LAYER[-LAYER] or all"
663 } else {
664 "LAYER[-LAYER]"
665 };
666 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
667 })?;
668 let (first, last) = if layers == "all" {
669 if !allow_full_model {
670 return Err(format!(
671 "{flag} does not support the full-model shorthand; assign routed layers \
672 explicitly"
673 ));
674 }
675 (0, STEP37_TRUNK_LAYERS - 1)
676 } else {
677 match layers.split_once('-') {
678 Some((first, last)) => {
679 let first = first
680 .parse::<usize>()
681 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
682 let last = last
683 .parse::<usize>()
684 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
685 if first > last {
686 return Err(format!("{flag} layer range {first}-{last} is reversed"));
687 }
688 if last - first + 1 > 128 {
689 return Err(format!(
690 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
691 ));
692 }
693 (first, last)
694 }
695 None => {
696 let layer = layers
697 .parse::<usize>()
698 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
699 (layer, layer)
700 }
701 }
702 };
703 let devices = devices
704 .split(',')
705 .map(|device| {
706 device
707 .parse::<usize>()
708 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
709 })
710 .collect::<Result<Vec<_>, _>>()?;
711 if !(2..=8).contains(&devices.len()) {
712 return Err(format!(
713 "{flag} requires 2..=8 devices, got {}",
714 devices.len()
715 ));
716 }
717 let mut unique = devices.clone();
718 unique.sort_unstable();
719 unique.dedup();
720 if unique.len() != devices.len() {
721 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
722 }
723 for layer in first..=last {
724 if specs
725 .iter()
726 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
727 {
728 return Err(format!("{flag} assigns layer {layer} more than once"));
729 }
730 specs.push(StepEpLayerSpec {
731 layer,
732 devices: devices.clone(),
733 });
734 }
735 }
736 Ok(specs)
737}
738
739pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
740 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
741}
742
743pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
744 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
745}
746
747pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
748 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
749}
750
751pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
752 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
753}
754
755#[derive(Clone, Copy)]
756pub struct E4m3BlockMatrix<'a> {
757 pub codes: &'a [u8],
758 pub scales: &'a [f32],
759 pub out_features: usize,
760 pub in_features: usize,
761}
762
763impl E4m3BlockMatrix<'_> {
764 fn validate(&self) -> Result<(), String> {
765 let code_count = self
766 .out_features
767 .checked_mul(self.in_features)
768 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
769 if self.codes.len() != code_count {
770 return Err(format!(
771 "E4M3 code count {} != {}x{} ({code_count})",
772 self.codes.len(),
773 self.out_features,
774 self.in_features,
775 ));
776 }
777 let scale_count =
778 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
779 if self.scales.len() != scale_count {
780 return Err(format!(
781 "E4M3 scale count {} != {scale_count} for {}x{}",
782 self.scales.len(),
783 self.out_features,
784 self.in_features,
785 ));
786 }
787 if !self
788 .scales
789 .iter()
790 .all(|scale| scale.is_finite() && *scale > 0.0)
791 {
792 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
793 }
794 Ok(())
795 }
796}
797
798#[derive(Clone, Copy)]
799pub struct E4m3ExpertBank<'a> {
800 pub codes: &'a [u8],
801 pub scales: &'a [f32],
802 pub expert_count: usize,
803 pub out_features: usize,
804 pub in_features: usize,
805}
806
807impl E4m3ExpertBank<'_> {
808 fn validate(&self) -> Result<(), String> {
809 if self.expert_count == 0 {
810 return Err("E4M3 expert bank is empty".to_string());
811 }
812 let code_stride = self
813 .out_features
814 .checked_mul(self.in_features)
815 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
816 let code_count = self
817 .expert_count
818 .checked_mul(code_stride)
819 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
820 if self.codes.len() != code_count {
821 return Err(format!(
822 "E4M3 expert code count {} != {}x{} ({code_count})",
823 self.codes.len(),
824 self.expert_count,
825 code_stride,
826 ));
827 }
828 let scale_stride =
829 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
830 let scale_count = self
831 .expert_count
832 .checked_mul(scale_stride)
833 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
834 if self.scales.len() != scale_count {
835 return Err(format!(
836 "E4M3 expert scale count {} != {}x{} ({scale_count})",
837 self.scales.len(),
838 self.expert_count,
839 scale_stride,
840 ));
841 }
842 if !self
843 .scales
844 .iter()
845 .all(|scale| scale.is_finite() && *scale > 0.0)
846 {
847 return Err(
848 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
849 );
850 }
851 Ok(())
852 }
853
854 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
855 if expert >= self.expert_count {
856 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
857 }
858 let code_stride = self.out_features * self.in_features;
859 let scale_stride =
860 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
861 Ok(E4m3BlockMatrix {
862 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
863 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
864 out_features: self.out_features,
865 in_features: self.in_features,
866 })
867 }
868}
869
870pub struct ColumnParallelResult {
871 pub gathered: Vec<f32>,
872 pub rank_outputs: Vec<Vec<f32>>,
873}
874
875pub struct RowParallelResult {
876 pub reduced: Vec<f32>,
877 pub rank_partials: Vec<Vec<f32>>,
878}
879
880#[derive(Clone, Copy)]
881pub struct Bf16Matrix<'a> {
882 pub bytes: &'a [u8],
883 pub out_features: usize,
884 pub in_features: usize,
885}
886
887impl Bf16Matrix<'_> {
888 pub fn validate(&self) -> Result<(), String> {
889 if self.out_features == 0 || self.in_features == 0 {
890 return Err("BF16 matrix dimensions must be nonzero".into());
891 }
892 let expected = self
893 .out_features
894 .checked_mul(self.in_features)
895 .and_then(|values| values.checked_mul(2))
896 .ok_or("BF16 matrix byte count overflow")?;
897 if self.bytes.len() != expected {
898 return Err(format!(
899 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
900 self.bytes.len(),
901 self.out_features,
902 self.in_features,
903 ));
904 }
905 Ok(())
906 }
907}
908
909struct ResidentE4m3Rank {
910 codes: CudaSlice<u8>,
911 scales: CudaSlice<f32>,
912 out_features: usize,
913 in_features: usize,
914}
915
916enum ResidentBf16Weight {
917 Bf16(CudaSlice<u8>),
918 F32(CudaSlice<f32>),
919}
920
921impl ResidentBf16Weight {
922 fn ordinal(&self) -> usize {
923 match self {
924 Self::Bf16(bytes) => bytes.ordinal(),
925 Self::F32(values) => values.ordinal(),
926 }
927 }
928}
929
930struct ResidentBf16Rank {
931 weight: ResidentBf16Weight,
932 out_features: usize,
933 in_features: usize,
934 q8: Option<CudaSlice<u8>>,
937}
938
939pub struct ResidentColumnParallel {
940 ranks: Vec<ResidentE4m3Rank>,
941 out_features: usize,
942 in_features: usize,
943}
944
945pub struct ResidentRowParallel {
946 ranks: Vec<ResidentE4m3Rank>,
947 out_features: usize,
948 in_features: usize,
949}
950
951pub struct ResidentBf16ColumnParallel {
952 ranks: Vec<ResidentBf16Rank>,
953 out_features: usize,
954 in_features: usize,
955 canonical_chunk_rows: Option<usize>,
956}
957
958pub struct ResidentBf16RowParallel {
959 ranks: Vec<ResidentBf16Rank>,
960 out_features: usize,
961 in_features: usize,
962}
963
964pub struct ResidentStepBf16RowParallel {
965 ranks: Vec<Vec<ResidentBf16Rank>>,
966 out_features: usize,
967 in_features: usize,
968 canonical_chunk_cols: usize,
969}
970
971pub struct ResidentSigmoidTopKRouter {
973 weight: CudaSlice<f32>,
974 correction_bias: CudaSlice<f32>,
975 active: CudaSlice<u8>,
976 root_device: usize,
977 input_width: usize,
978 expert_count: usize,
979 experts_per_token: usize,
980 active_count: usize,
981 scaling_factor: f32,
982 route_norm: bool,
983}
984
985pub struct SigmoidTopKHostOutput {
986 pub logits: Vec<f32>,
987 pub selected: Vec<u32>,
988 pub weights: Vec<f32>,
989}
990
991pub struct ResidentReplicatedBf16SwiGlu {
993 gate: Vec<ResidentBf16Rank>,
994 up: Vec<ResidentBf16Rank>,
995 down: Vec<ResidentBf16Rank>,
996 input_width: usize,
997 intermediate_width: usize,
998}
999
1000pub struct ResidentReplicatedDeviceRows {
1005 ranks: Vec<CudaSlice<f32>>,
1006 tokens: usize,
1007 width: usize,
1008}
1009
1010impl ResidentReplicatedDeviceRows {
1011 pub fn tokens(&self) -> usize {
1012 self.tokens
1013 }
1014
1015 pub fn width(&self) -> usize {
1016 self.width
1017 }
1018
1019 pub fn ranks(&self) -> usize {
1020 self.ranks.len()
1021 }
1022}
1023
1024pub fn moe_residual_host(
1026 residual: &[f32],
1027 routed: &[f32],
1028 shared: &[f32],
1029) -> Result<Vec<f32>, String> {
1030 if residual.len() != routed.len() || residual.len() != shared.len() {
1031 return Err(format!(
1032 "MoE residual lengths residual={} routed={} shared={}",
1033 residual.len(),
1034 routed.len(),
1035 shared.len()
1036 ));
1037 }
1038 let ffn = routed
1039 .iter()
1040 .zip(shared)
1041 .map(|(&routed, &shared)| routed + shared)
1042 .collect::<Vec<_>>();
1043 Ok(residual
1044 .iter()
1045 .zip(ffn)
1046 .map(|(&residual, ffn)| residual + ffn)
1047 .collect())
1048}
1049
1050pub use memra_kv::{
1051 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1052};
1053
1054pub struct ResidentTpExpert {
1060 gate: ResidentColumnParallel,
1061 up: ResidentColumnParallel,
1062 down: ResidentRowParallel,
1063 input_width: usize,
1064 expert_width: usize,
1065}
1066
1067struct ResidentE4m3ExpertBankRank {
1068 codes: CudaSlice<u8>,
1069 scales: CudaSlice<f32>,
1070 expert_range: Range<usize>,
1071 out_features: usize,
1072 in_features: usize,
1073 code_stride: usize,
1074 scale_stride: usize,
1075 k_blocks: Option<usize>,
1078}
1079
1080struct PackedE4m3ExpertBankRank {
1081 codes: Vec<u8>,
1082 scales: Vec<f32>,
1083 expert_range: Range<usize>,
1084 out_features: usize,
1085 in_features: usize,
1086 code_stride: usize,
1087 scale_stride: usize,
1088 k_blocks: Option<usize>,
1089}
1090
1091struct ResidentEpRank {
1092 gate: ResidentE4m3ExpertBankRank,
1093 up: ResidentE4m3ExpertBankRank,
1094 down: ResidentE4m3ExpertBankRank,
1095}
1096
1097pub struct ResidentExpertParallel {
1104 ranks: Vec<ResidentEpRank>,
1105 expert_count: usize,
1106 input_width: usize,
1107 expert_width: usize,
1108}
1109
1110pub struct StepGroupedFp8ProjectionOutput {
1115 pub gate: Vec<f32>,
1116 pub up: Vec<f32>,
1117 pub down: Vec<f32>,
1118}
1119
1120pub struct PreparedStepGroupedFp8Gate {
1125 device: usize,
1126 gate: ResidentE4m3ExpertBankRank,
1127 up: ResidentE4m3ExpertBankRank,
1128 down: ResidentE4m3ExpertBankRank,
1129 input: CudaSlice<f32>,
1130 route_csr: DeviceExpertCsr,
1131 down_csr: DeviceExpertCsr,
1132 gate_workspace: Fp8GroupedWorkspace,
1133 up_workspace: Fp8GroupedWorkspace,
1134 down_workspace: Fp8GroupedWorkspace,
1135 activation: CudaSlice<f32>,
1136 activation_limit: Option<f32>,
1137 tokens: usize,
1138 pairs: usize,
1139}
1140
1141impl PreparedStepGroupedFp8Gate {
1142 pub fn tokens(&self) -> usize {
1143 self.tokens
1144 }
1145
1146 pub fn pairs(&self) -> usize {
1147 self.pairs
1148 }
1149}
1150
1151struct PreparedStepGroupedExpertOwner {
1152 rank: usize,
1153 global_pairs: Vec<usize>,
1154 route_csr: DeviceExpertCsr,
1155 down_csr: DeviceExpertCsr,
1156 gate_workspace: Fp8GroupedWorkspace,
1157 up_workspace: Fp8GroupedWorkspace,
1158 down_workspace: Fp8GroupedWorkspace,
1159 activation: CudaSlice<f32>,
1160}
1161
1162struct StepGroupedExpertOwnerSchedule {
1163 global_pairs: Vec<usize>,
1164 route_csr: ExpertCsr,
1165 down_csr: ExpertCsr,
1166}
1167
1168pub struct PreparedStepGroupedExpertParallelGate {
1174 rank_inputs: Vec<CudaSlice<f32>>,
1175 owners: Vec<PreparedStepGroupedExpertOwner>,
1176 activation_limit: Option<f32>,
1177 tokens: usize,
1178 pairs: usize,
1179 max_tokens: usize,
1180 max_pairs: usize,
1181 input_width: usize,
1182 expert_width: usize,
1183 generation: u64,
1184 executed_generation: Option<u64>,
1185 ready: bool,
1186}
1187
1188impl PreparedStepGroupedExpertParallelGate {
1189 pub fn tokens(&self) -> usize {
1190 self.tokens
1191 }
1192
1193 pub fn pairs(&self) -> usize {
1194 self.pairs
1195 }
1196
1197 pub fn max_tokens(&self) -> usize {
1198 self.max_tokens
1199 }
1200
1201 pub fn input_width(&self) -> usize {
1202 self.input_width
1203 }
1204
1205 pub fn expert_width(&self) -> usize {
1206 self.expert_width
1207 }
1208
1209 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1210 validate_step_expert_activation_limit(limit)?;
1211 self.activation_limit = limit;
1212 self.executed_generation = None;
1213 Ok(())
1214 }
1215
1216 pub fn active_owners(&self) -> usize {
1217 self.owners
1218 .iter()
1219 .filter(|owner| !owner.global_pairs.is_empty())
1220 .count()
1221 }
1222
1223 pub fn owner_pair_counts(&self) -> Vec<usize> {
1224 self.owners
1225 .iter()
1226 .map(|owner| owner.global_pairs.len())
1227 .collect()
1228 }
1229
1230 pub fn generation(&self) -> u64 {
1231 self.generation
1232 }
1233}
1234
1235struct PreparedPeerWeightedRouteOwner {
1236 token_rows: CudaSlice<i32>,
1237 slots: CudaSlice<i32>,
1238 weights: CudaSlice<f32>,
1239 active_pairs: usize,
1240}
1241
1242pub struct PreparedPeerWeightedRouteCombine {
1248 root_device: usize,
1249 owners: Vec<PreparedPeerWeightedRouteOwner>,
1250 peer_staging: CudaSlice<f32>,
1251 slots: CudaSlice<f32>,
1252 weights: CudaSlice<f32>,
1253 output: CudaSlice<f32>,
1254 peer_devices: Vec<usize>,
1255 peer_outputs: Vec<CudaSlice<f32>>,
1256 width: usize,
1257 experts_per_token: usize,
1258 max_tokens: usize,
1259 max_pairs: usize,
1260 tokens: usize,
1261 pairs: usize,
1262 projection_generation: u64,
1263 output_generation: Option<u64>,
1264 broadcast_generation: Option<u64>,
1265 ready: bool,
1266}
1267
1268impl PreparedPeerWeightedRouteCombine {
1269 pub fn tokens(&self) -> usize {
1270 self.tokens
1271 }
1272
1273 pub fn pairs(&self) -> usize {
1274 self.pairs
1275 }
1276
1277 pub fn owner_pair_counts(&self) -> Vec<usize> {
1278 self.owners.iter().map(|owner| owner.active_pairs).collect()
1279 }
1280
1281 pub fn distributed_ranks(&self) -> usize {
1282 1 + self.peer_outputs.len()
1283 }
1284}
1285
1286struct ResidentTpExpertBank {
1287 gate: Vec<ResidentE4m3ExpertBankRank>,
1288 up: Vec<ResidentE4m3ExpertBankRank>,
1289 down: Vec<ResidentE4m3ExpertBankRank>,
1290 expert_count: usize,
1291 input_width: usize,
1292 expert_width: usize,
1293}
1294
1295pub struct ResidentTensorParallel {
1301 bank: ResidentTpExpertBank,
1302}
1303
1304pub struct TpE4m3HostBounce {
1310 devices: Vec<usize>,
1311 ranks: Vec<Engine>,
1312 native_p2p: bool,
1313 ep_device_arithmetic: bool,
1314 bulk_p2p: bool,
1315 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1318}
1319
1320pub enum StepTpGateShards<'a> {
1329 F32(&'a [crate::CudaSlice<f32>]),
1330 Bf16(&'a [crate::CudaSlice<u8>]),
1331}
1332
1333pub struct StepTpDecodeV2Ws {
1334 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1338 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1339 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1340 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1341 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1342 pub(crate) tcol_cap: usize,
1343 w8_aq: Vec<CudaSlice<i8>>,
1347 w8_ad: Vec<CudaSlice<f32>>,
1348 w8_in: usize,
1349 w8o_aq: Vec<CudaSlice<i8>>,
1352 w8o_ad: Vec<CudaSlice<f32>>,
1353 w8o_in: usize,
1354 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1361 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1362 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1363 pub(crate) fa2_cap: usize,
1364 rope_k_t: Vec<CudaSlice<f32>>,
1368 rope_ctr_t: Vec<CudaSlice<u32>>,
1369 rope_pos_t: Vec<CudaSlice<i32>>,
1370 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1373 tcol_gated: Vec<CudaSlice<f32>>,
1374 tcol_opart: Vec<CudaSlice<f32>>,
1375 tcol_opeer: Option<CudaSlice<f32>>,
1376 tcol_omix: Option<CudaSlice<f32>>,
1377 tcol_ocap: usize,
1378 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1381 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1382 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1383 pub(crate) q: Vec<CudaSlice<f32>>,
1384 pub(crate) k: Vec<CudaSlice<f32>>,
1385 pub(crate) pos: Vec<CudaSlice<i32>>,
1386 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1388 pub(crate) gate: Vec<CudaSlice<f32>>,
1389 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1390 pub(crate) gated: Vec<CudaSlice<f32>>,
1391 o_partials: Vec<Vec<CudaSlice<f32>>>,
1393 ev_rank: Vec<CudaEvent>,
1395 peer_partial: CudaSlice<f32>,
1397 reduce_a: CudaSlice<f32>,
1398 reduce_b: CudaSlice<f32>,
1399 zeros: CudaSlice<f32>,
1401 pub(crate) k_shadow: CudaSlice<f32>,
1402 pub(crate) v_shadow: CudaSlice<f32>,
1403 ev_refresh: CudaEvent,
1404 ev_oproj: CudaEvent,
1405 gate_e: CudaSlice<f32>,
1407 pub(crate) h_stage: Option<CudaSlice<f32>>,
1410 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1411 attn_in: Vec<CudaSlice<f32>>,
1415 raw_h_stage: u64,
1417 raw_pos_stage: u64,
1418 raw_attn_in: Vec<u64>,
1419 raw_pos: Vec<u64>,
1420 raw_o_partial1: u64,
1421 raw_peer_partial: u64,
1422 raw_k1: u64,
1423 raw_v1: u64,
1424 raw_k_shadow: u64,
1425 raw_v_shadow: u64,
1426 raw_mixed_stage_e: u64,
1430 raw_reduce_a: u64,
1431 raw_shadow_stage_e: (u64, u64),
1432 ev_entry: CudaEvent,
1433 e_device: usize,
1434 local_q_dim: usize,
1436 local_kv_dim: usize,
1437 heads: usize,
1438 pub(crate) o_out: usize,
1439 o_block_cols: usize,
1440 blocks_per_rank: usize,
1441}
1442
1443impl TpE4m3HostBounce {
1444 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1445 Self::new_inner(devices, false, false, false, false)
1446 }
1447
1448 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1449 Self::new_inner(devices, false, true, false, false)
1450 }
1451
1452 pub fn new_native_p2p_device_arithmetic(
1453 devices: &[usize],
1454 ) -> Result<Self, Box<dyn std::error::Error>> {
1455 Self::new_inner(devices, false, true, true, false)
1456 }
1457
1458 pub(crate) fn new_configured(
1459 devices: &[usize],
1460 native_p2p: bool,
1461 ep_device_arithmetic: bool,
1462 bulk_p2p: bool,
1463 ) -> Result<Self, Box<dyn std::error::Error>> {
1464 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1465 }
1466
1467 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1472 Self::new_inner(&[device], true, false, false, false)
1473 }
1474
1475 fn new_inner(
1476 devices: &[usize],
1477 allow_single_rank: bool,
1478 native_p2p: bool,
1479 ep_device_arithmetic: bool,
1480 bulk_p2p: bool,
1481 ) -> Result<Self, Box<dyn std::error::Error>> {
1482 if ep_device_arithmetic && !native_p2p {
1483 return Err("device-resident EP arithmetic requires native P2P".into());
1484 }
1485 if bulk_p2p && !native_p2p {
1486 return Err("bulk TP transport requires native P2P".into());
1487 }
1488 let minimum = if allow_single_rank { 1 } else { 2 };
1489 if !(minimum..=8).contains(&devices.len()) {
1490 return Err(format!(
1491 "TP reference requires {minimum}..=8 devices, got {}",
1492 devices.len()
1493 )
1494 .into());
1495 }
1496 let mut unique = devices.to_vec();
1497 unique.sort_unstable();
1498 unique.dedup();
1499 if unique.len() != devices.len() {
1500 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1501 }
1502 let ranks = devices
1503 .iter()
1504 .map(|&device| Engine::new(device))
1505 .collect::<Result<Vec<_>, _>>()?;
1506 if native_p2p {
1507 configure_native_p2p(&ranks, devices)?;
1508 }
1509 if allow_single_rank {
1510 eprintln!(
1511 "[tp] canonical oracle transport=local device={} performance_claim=false",
1512 devices[0]
1513 );
1514 } else if native_p2p {
1515 if ep_device_arithmetic {
1516 eprintln!(
1517 "[tp] correctness transport=native-p2p devices={devices:?} \
1518 native_p2p=true activation=device-host-exact \
1519 accumulation=device-host-exact output=root-readback \
1520 bulk_p2p={bulk_p2p} performance_claim=false"
1521 );
1522 } else {
1523 eprintln!(
1524 "[tp] correctness transport=native-p2p devices={devices:?} \
1525 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1526 performance_claim=false"
1527 );
1528 }
1529 } else {
1530 eprintln!(
1531 "[tp] correctness transport=host-bounce devices={devices:?} \
1532 native_p2p=false performance_claim=false"
1533 );
1534 }
1535 Ok(Self {
1536 devices: devices.to_vec(),
1537 ranks,
1538 native_p2p,
1539 ep_device_arithmetic,
1540 bulk_p2p,
1541 decode_v2: std::sync::Mutex::new(Vec::new()),
1542 })
1543 }
1544
1545 pub fn devices(&self) -> &[usize] {
1546 &self.devices
1547 }
1548
1549 pub fn native_p2p(&self) -> bool {
1550 self.native_p2p
1551 }
1552
1553 pub fn bulk_p2p(&self) -> bool {
1554 self.bulk_p2p
1555 }
1556
1557 pub fn expert_activation_label(&self) -> &'static str {
1558 if self.ep_device_arithmetic {
1559 "device-host-exact"
1560 } else {
1561 "host-canonical"
1562 }
1563 }
1564
1565 pub fn expert_accumulation_label(&self) -> &'static str {
1566 self.expert_activation_label()
1567 }
1568
1569 pub fn expert_output_label(&self) -> &'static str {
1570 if self.ep_device_arithmetic {
1571 "root-readback"
1572 } else {
1573 "host-accumulated"
1574 }
1575 }
1576
1577 pub fn transport_label(&self) -> &'static str {
1578 if self.devices.len() == 1 {
1579 "local"
1580 } else if self.native_p2p {
1581 "native-p2p"
1582 } else {
1583 "host-bounce"
1584 }
1585 }
1586
1587 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1588 self.ranks
1589 .iter()
1590 .map(|rank| rank.ctx().name().map_err(Into::into))
1591 .collect()
1592 }
1593
1594 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1600 self.ranks.get(rank)
1601 }
1602
1603 pub fn allocate_tp_kv_cache(
1604 &self,
1605 kv_dim_k: usize,
1606 kv_dim_v: usize,
1607 capacity: usize,
1608 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1609 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1610 }
1611
1612 pub fn allocate_tp_swa_kv_cache(
1613 &self,
1614 kv_dim_k: usize,
1615 kv_dim_v: usize,
1616 capacity: usize,
1617 window: usize,
1618 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1619 if window == 0 {
1620 return Err("TP SWA KV window must be nonzero".into());
1621 }
1622 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1623 }
1624
1625 fn allocate_tp_kv_cache_inner(
1626 &self,
1627 kv_dim_k: usize,
1628 kv_dim_v: usize,
1629 capacity: usize,
1630 window: Option<usize>,
1631 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1632 if capacity == 0 || capacity > i32::MAX as usize {
1633 return Err(
1634 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1635 );
1636 }
1637 let tp = self.ranks.len();
1638 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1639 let physical_rows = window
1640 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1641 .unwrap_or(capacity);
1642 let k_plane_bytes = physical_rows
1643 .checked_mul(shape.k_token_bytes)
1644 .and_then(|bytes| bytes.checked_add(8))
1645 .ok_or("TP KV K plane-byte overflow")?;
1646 let v_plane_bytes = physical_rows
1647 .checked_mul(shape.v_token_bytes)
1648 .and_then(|bytes| bytes.checked_add(8))
1649 .ok_or("TP KV V plane-byte overflow")?;
1650 let mut ranks = Vec::with_capacity(tp);
1651 for engine in &self.ranks {
1652 let _main = engine.gpu.enter_main()?;
1653 ranks.push(ResidentTpKvCacheRank::new(
1654 engine.alloc_u8(k_plane_bytes)?,
1655 engine.alloc_u8(v_plane_bytes)?,
1656 engine.htod_i32(&[0])?,
1657 ));
1658 }
1659 Ok(match window {
1660 Some(window) => ResidentTpKvCache::new_swa(
1661 ranks,
1662 shape.kv_dim_k,
1663 shape.kv_dim_v,
1664 shape.k_token_bytes,
1665 shape.v_token_bytes,
1666 capacity,
1667 window,
1668 ),
1669 None => ResidentTpKvCache::new(
1670 ranks,
1671 shape.kv_dim_k,
1672 shape.kv_dim_v,
1673 shape.k_token_bytes,
1674 shape.v_token_bytes,
1675 capacity,
1676 ),
1677 })
1678 }
1679
1680 pub fn grow_tp_kv_cache(
1681 &self,
1682 source: &ResidentTpKvCache,
1683 target_capacity: usize,
1684 rows: usize,
1685 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1686 self.validate_tp_kv_cache(source)?;
1687 let plan = source.prepare_grow(target_capacity, rows)?;
1688 let ranks = self.ranks.len();
1689 let global_k = source
1690 .kv_dim_k()
1691 .checked_mul(ranks)
1692 .ok_or("TP KV grow global K dimension overflow")?;
1693 let global_v = source
1694 .kv_dim_v()
1695 .checked_mul(ranks)
1696 .ok_or("TP KV grow global V dimension overflow")?;
1697 let mut target = match source.ring_window() {
1698 Some(window) => {
1699 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1700 }
1701 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1702 };
1703 self.validate_tp_kv_cache(&target)?;
1704
1705 for (rank, engine) in self.ranks.iter().enumerate() {
1706 let _main = engine.gpu.enter_main()?;
1707 let src = source
1708 .rank(rank)
1709 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1710 let dst = target
1711 .rank_mut(rank)
1712 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1713 if plan.k_bytes() > 0 {
1714 engine.copy_u8_range_into(
1715 dst.k_mut(),
1716 0,
1717 src.k(),
1718 plan.source_row() * source.k_tok_bytes(),
1719 plan.k_bytes(),
1720 )?;
1721 }
1722 if plan.v_bytes() > 0 {
1723 engine.copy_u8_range_into(
1724 dst.v_mut(),
1725 0,
1726 src.v(),
1727 plan.source_row() * source.v_tok_bytes(),
1728 plan.v_bytes(),
1729 )?;
1730 }
1731 }
1732 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1733
1734 for engine in &self.ranks {
1737 let _main = engine.gpu.enter_main()?;
1738 engine.stream().synchronize()?;
1739 }
1740 let physical_copy_rows = plan.copy_rows();
1741 target.publish_grow(plan)?;
1742 eprintln!(
1743 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1744 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1745 rank_streams_synchronized=true generation_preserved=true",
1746 rows,
1747 source.capacity(),
1748 target_capacity,
1749 ranks,
1750 physical_copy_rows,
1751 source.ring_window(),
1752 );
1753 Ok(target)
1754 }
1755
1756 pub fn hydrate_tp_kv_cache(
1757 &self,
1758 cache: &mut ResidentTpKvCache,
1759 rows: usize,
1760 k_rows: &[u8],
1761 v_rows: &[u8],
1762 ) -> Result<(), Box<dyn std::error::Error>> {
1763 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1764 }
1765
1766 pub fn hydrate_tp_kv_cache_from(
1767 &self,
1768 cache: &mut ResidentTpKvCache,
1769 logical_len: usize,
1770 resident_start: usize,
1771 k_rows: &[u8],
1772 v_rows: &[u8],
1773 ) -> Result<(), Box<dyn std::error::Error>> {
1774 self.validate_tp_kv_cache(cache)?;
1775 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1776 return Err(format!(
1777 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1778 cache.committed_len(),
1779 cache.staged_len()
1780 )
1781 .into());
1782 }
1783 if resident_start > logical_len || logical_len > cache.capacity() {
1784 return Err(format!(
1785 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1786 cache.capacity(),
1787 )
1788 .into());
1789 }
1790 let rows = logical_len - resident_start;
1791 if rows > cache.physical_capacity() {
1792 return Err(format!(
1793 "TP KV hydration rows {rows} exceed physical capacity {}",
1794 cache.physical_capacity()
1795 )
1796 .into());
1797 }
1798 for rank in 0..self.ranks.len() {
1799 let k_rank =
1800 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1801 let v_rank =
1802 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1803 let engine = &self.ranks[rank];
1804 let _main = engine.gpu.enter_main()?;
1805 let rank_cache = cache
1806 .rank_mut(rank)
1807 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1808 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1809 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1810 }
1811 cache.publish_hydration(logical_len, resident_start)?;
1812 Ok(())
1813 }
1814
1815 pub fn append_tp_kv_transaction(
1816 &self,
1817 cache: &mut ResidentTpKvCache,
1818 transaction: TpKvTransaction,
1819 k_shards: &[CudaSlice<f32>],
1820 v_shards: &[CudaSlice<f32>],
1821 rows: usize,
1822 ) -> Result<(), Box<dyn std::error::Error>> {
1823 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1824 }
1825
1826 #[allow(clippy::too_many_arguments)]
1831 pub fn append_tp_kv_transaction_inner(
1832 &self,
1833 cache: &mut ResidentTpKvCache,
1834 transaction: TpKvTransaction,
1835 k_shards: &[CudaSlice<f32>],
1836 v_shards: &[CudaSlice<f32>],
1837 rows: usize,
1838 external_rank_appends: bool,
1839 ) -> Result<(), Box<dyn std::error::Error>> {
1840 self.validate_tp_kv_cache(cache)?;
1841 let plan = cache.prepare_append(transaction, rows)?;
1842 let target = plan.target();
1843 let expected_k = rows
1844 .checked_mul(cache.kv_dim_k())
1845 .ok_or("TP KV K append size overflow")?;
1846 let expected_v = rows
1847 .checked_mul(cache.kv_dim_v())
1848 .ok_or("TP KV V append size overflow")?;
1849 if !external_rank_appends
1852 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1853 {
1854 return Err(format!(
1855 "TP KV append shard counts k={} v={} != ranks {}",
1856 k_shards.len(),
1857 v_shards.len(),
1858 self.ranks.len()
1859 )
1860 .into());
1861 }
1862 let kv_dim_k = cache.kv_dim_k();
1863 let kv_dim_v = cache.kv_dim_v();
1864 let k_tok_bytes = cache.k_tok_bytes();
1865 let v_tok_bytes = cache.v_tok_bytes();
1866 if let Some(KvRingAppend::Rebase {
1867 src_row,
1868 keep_rows,
1869 new_base,
1870 ..
1871 }) = plan.ring_append()
1872 {
1873 for rank in 0..self.ranks.len() {
1874 let engine = &self.ranks[rank];
1875 let _main = engine.gpu.enter_main()?;
1876 let rank_cache = cache
1877 .rank_mut(rank)
1878 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1879 if keep_rows > 0 {
1880 let k_len = keep_rows
1881 .checked_mul(k_tok_bytes)
1882 .ok_or("TP KV K rebase-byte overflow")?;
1883 let v_len = keep_rows
1884 .checked_mul(v_tok_bytes)
1885 .ok_or("TP KV V rebase-byte overflow")?;
1886 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1887 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
1888 engine.copy_u8_range_into(
1889 &mut k_tmp,
1890 0,
1891 rank_cache.k(),
1892 src_row * k_tok_bytes,
1893 k_len,
1894 )?;
1895 engine.copy_u8_range_into(
1896 &mut v_tmp,
1897 0,
1898 rank_cache.v(),
1899 src_row * v_tok_bytes,
1900 v_len,
1901 )?;
1902 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
1903 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
1904 }
1905 if rank_cache.base_d().is_some() {
1909 let value = new_base as i32;
1910 let rank_cache = cache
1911 .rank_mut(rank)
1912 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1913 if let Some(base_d) = rank_cache.base_d_mut() {
1914 engine.set_i32_one(base_d, value)?;
1915 }
1916 }
1917 }
1918 }
1919 cache.publish_append_rebase(plan)?;
1920 let write_row = plan.write_row();
1921 for rank in 0..self.ranks.len() {
1922 if external_rank_appends {
1923 break;
1924 }
1925 let engine = &self.ranks[rank];
1926 let _main = engine.gpu.enter_main()?;
1927 if k_shards[rank].len() != expected_k
1928 || v_shards[rank].len() != expected_v
1929 || k_shards[rank].ordinal() != engine.ctx().ordinal()
1930 || v_shards[rank].ordinal() != engine.ctx().ordinal()
1931 {
1932 return Err(format!(
1933 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
1934 != expected {expected_k}/{expected_v} on device {}",
1935 k_shards[rank].len(),
1936 k_shards[rank].ordinal(),
1937 v_shards[rank].len(),
1938 v_shards[rank].ordinal(),
1939 engine.ctx().ordinal(),
1940 )
1941 .into());
1942 }
1943 let rank_cache = cache
1944 .rank_mut(rank)
1945 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1946 let (rank_k, rank_v) = rank_cache.planes_mut();
1947 engine.append_kv_quantized_rows(
1948 &k_shards[rank],
1949 &v_shards[rank],
1950 rank_k,
1951 rank_v,
1952 write_row,
1953 rows,
1954 kv_dim_k,
1955 kv_dim_v,
1956 k_tok_bytes,
1957 v_tok_bytes,
1958 Engine::kv_fp8_on(),
1959 )?;
1960 }
1961 if !external_rank_appends {
1962 self.set_tp_kv_len_mirrors(cache, target)?;
1965 }
1966 cache.publish_append_plan(plan)?;
1967 Ok(())
1968 }
1969
1970 pub fn commit_tp_kv_transaction(
1971 &self,
1972 cache: &mut ResidentTpKvCache,
1973 transaction: TpKvTransaction,
1974 accepted_rows: usize,
1975 ) -> Result<(), Box<dyn std::error::Error>> {
1976 self.validate_tp_kv_cache(cache)?;
1977 let target = cache.commit_target(transaction, accepted_rows)?;
1978 self.set_tp_kv_len_mirrors(cache, target)?;
1979 cache.publish_finalize(transaction, target)?;
1980 Ok(())
1981 }
1982
1983 pub fn commit_tp_kv_transaction_external(
1989 &self,
1990 cache: &mut ResidentTpKvCache,
1991 transaction: TpKvTransaction,
1992 accepted_rows: usize,
1993 ) -> Result<(), Box<dyn std::error::Error>> {
1994 self.validate_tp_kv_cache(cache)?;
1995 let target = cache.commit_target(transaction, accepted_rows)?;
1996 cache.publish_finalize(transaction, target)?;
1997 Ok(())
1998 }
1999
2000 pub fn rollback_tp_kv_transaction(
2001 &self,
2002 cache: &mut ResidentTpKvCache,
2003 transaction: TpKvTransaction,
2004 ) -> Result<(), Box<dyn std::error::Error>> {
2005 self.validate_tp_kv_cache(cache)?;
2006 cache.validate_transaction(transaction)?;
2007 let target = transaction.base_len();
2008 self.set_tp_kv_len_mirrors(cache, target)?;
2009 cache.publish_finalize(transaction, target)?;
2010 Ok(())
2011 }
2012
2013 pub fn tp_kv_device_lengths(
2014 &self,
2015 cache: &ResidentTpKvCache,
2016 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2017 self.validate_tp_kv_cache(cache)?;
2018 let mut lengths = Vec::with_capacity(self.ranks.len());
2019 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2020 let _main = engine.gpu.enter_main()?;
2021 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2022 }
2023 Ok(lengths)
2024 }
2025
2026 fn set_tp_kv_len_mirrors(
2027 &self,
2028 cache: &mut ResidentTpKvCache,
2029 len: usize,
2030 ) -> Result<(), Box<dyn std::error::Error>> {
2031 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2032 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2033 let _main = engine.gpu.enter_main()?;
2034 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2035 }
2036 Ok(())
2037 }
2038
2039 fn validate_tp_kv_cache(
2040 &self,
2041 cache: &ResidentTpKvCache,
2042 ) -> Result<(), Box<dyn std::error::Error>> {
2043 if cache.ranks_len() != self.ranks.len() {
2044 return Err(format!(
2045 "TP KV cache ranks {} != runtime ranks {}",
2046 cache.ranks_len(),
2047 self.ranks.len()
2048 )
2049 .into());
2050 }
2051 let expected_k = cache
2052 .physical_capacity()
2053 .checked_mul(cache.k_tok_bytes())
2054 .and_then(|bytes| bytes.checked_add(8))
2055 .ok_or("TP KV K plane validation overflow")?;
2056 let expected_v = cache
2057 .physical_capacity()
2058 .checked_mul(cache.v_tok_bytes())
2059 .and_then(|bytes| bytes.checked_add(8))
2060 .ok_or("TP KV V plane validation overflow")?;
2061 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2062 let device = engine.ctx().ordinal();
2063 if rank_cache.k().len() != expected_k
2064 || rank_cache.v().len() != expected_v
2065 || rank_cache.len_d().len() != 1
2066 || rank_cache.k().ordinal() != device
2067 || rank_cache.v().ordinal() != device
2068 || rank_cache.len_d().ordinal() != device
2069 {
2070 return Err(format!(
2071 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2072 )
2073 .into());
2074 }
2075 }
2076 Ok(())
2077 }
2078
2079 pub fn full(
2080 &self,
2081 matrix: E4m3BlockMatrix<'_>,
2082 activations: &[f32],
2083 tokens: usize,
2084 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2085 matrix.validate()?;
2086 validate_activations(activations, tokens, matrix.in_features)?;
2087 run_rank(&self.ranks[0], matrix, activations, tokens)
2088 }
2089
2090 pub fn column_parallel(
2094 &self,
2095 matrix: E4m3BlockMatrix<'_>,
2096 activations: &[f32],
2097 tokens: usize,
2098 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2099 matrix.validate()?;
2100 validate_activations(activations, tokens, matrix.in_features)?;
2101 let tp = self.ranks.len();
2102 if matrix.out_features % tp != 0 {
2103 return Err(format!(
2104 "column-parallel out_features {} is not divisible by TP={tp}",
2105 matrix.out_features
2106 )
2107 .into());
2108 }
2109 let local_out = matrix.out_features / tp;
2110 if local_out % FP8_BLOCK != 0 {
2111 return Err(format!(
2112 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2113 E4M3 scale block"
2114 )
2115 .into());
2116 }
2117
2118 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2119 let mut rank_outputs = Vec::with_capacity(tp);
2120 for (rank_index, rank) in self.ranks.iter().enumerate() {
2121 let shard = column_shard(matrix, tp, rank_index)?;
2122 let output = run_rank(rank, shard, activations, tokens)?;
2123 let row_start = rank_index * local_out;
2124 for token in 0..tokens {
2125 gathered[token * matrix.out_features + row_start
2126 ..token * matrix.out_features + row_start + local_out]
2127 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2128 }
2129 rank_outputs.push(output);
2130 }
2131 Ok(ColumnParallelResult {
2132 gathered,
2133 rank_outputs,
2134 })
2135 }
2136
2137 pub fn upload_column_parallel(
2138 &self,
2139 matrix: E4m3BlockMatrix<'_>,
2140 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2141 matrix.validate()?;
2142 let tp = self.ranks.len();
2143 validate_column_shape(matrix, tp)?;
2144 let mut ranks = Vec::with_capacity(tp);
2145 for (rank_index, engine) in self.ranks.iter().enumerate() {
2146 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2147 }
2148 Ok(ResidentColumnParallel {
2149 ranks,
2150 out_features: matrix.out_features,
2151 in_features: matrix.in_features,
2152 })
2153 }
2154
2155 pub fn column_parallel_resident(
2156 &self,
2157 matrix: &ResidentColumnParallel,
2158 activations: &[f32],
2159 tokens: usize,
2160 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2161 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2162 validate_activations(activations, tokens, matrix.in_features)?;
2163 let local_out = matrix.out_features / self.ranks.len();
2164 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2165 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2166 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2167 let output = run_resident_rank(engine, shard, activations, tokens)?;
2168 let row_start = rank_index * local_out;
2169 for token in 0..tokens {
2170 gathered[token * matrix.out_features + row_start
2171 ..token * matrix.out_features + row_start + local_out]
2172 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2173 }
2174 rank_outputs.push(output);
2175 }
2176 Ok(ColumnParallelResult {
2177 gathered,
2178 rank_outputs,
2179 })
2180 }
2181
2182 pub fn row_parallel(
2186 &self,
2187 matrix: E4m3BlockMatrix<'_>,
2188 activations: &[f32],
2189 tokens: usize,
2190 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2191 matrix.validate()?;
2192 validate_activations(activations, tokens, matrix.in_features)?;
2193 let tp = self.ranks.len();
2194 if matrix.in_features % tp != 0 {
2195 return Err(format!(
2196 "row-parallel in_features {} is not divisible by TP={tp}",
2197 matrix.in_features
2198 )
2199 .into());
2200 }
2201 let local_in = matrix.in_features / tp;
2202 if local_in % FP8_BLOCK != 0 {
2203 return Err(format!(
2204 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2205 E4M3 scale block"
2206 )
2207 .into());
2208 }
2209
2210 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2211 let mut rank_partials = Vec::with_capacity(tp);
2212 for (rank_index, rank) in self.ranks.iter().enumerate() {
2213 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2214 let local_activations =
2215 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2216 let shard = E4m3BlockMatrix {
2217 codes: &codes,
2218 scales: &scales,
2219 out_features: matrix.out_features,
2220 in_features: local_in,
2221 };
2222 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2223 for (sum, value) in reduced.iter_mut().zip(&partial) {
2224 *sum += *value;
2225 }
2226 rank_partials.push(partial);
2227 }
2228 Ok(RowParallelResult {
2229 reduced,
2230 rank_partials,
2231 })
2232 }
2233
2234 pub fn upload_row_parallel(
2235 &self,
2236 matrix: E4m3BlockMatrix<'_>,
2237 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2238 matrix.validate()?;
2239 let tp = self.ranks.len();
2240 validate_row_shape(matrix, tp)?;
2241 let local_in = matrix.in_features / tp;
2242 let mut ranks = Vec::with_capacity(tp);
2243 for (rank_index, engine) in self.ranks.iter().enumerate() {
2244 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2245 ranks.push(upload_rank(
2246 engine,
2247 E4m3BlockMatrix {
2248 codes: &codes,
2249 scales: &scales,
2250 out_features: matrix.out_features,
2251 in_features: local_in,
2252 },
2253 )?);
2254 }
2255 Ok(ResidentRowParallel {
2256 ranks,
2257 out_features: matrix.out_features,
2258 in_features: matrix.in_features,
2259 })
2260 }
2261
2262 pub fn row_parallel_resident(
2263 &self,
2264 matrix: &ResidentRowParallel,
2265 activations: &[f32],
2266 tokens: usize,
2267 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2268 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2269 validate_activations(activations, tokens, matrix.in_features)?;
2270 let tp = self.ranks.len();
2271 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2272 let mut rank_partials = Vec::with_capacity(tp);
2273 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2274 let local_activations =
2275 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2276 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2277 for (sum, value) in reduced.iter_mut().zip(&partial) {
2278 *sum += *value;
2279 }
2280 rank_partials.push(partial);
2281 }
2282 Ok(RowParallelResult {
2283 reduced,
2284 rank_partials,
2285 })
2286 }
2287
2288 pub fn upload_bf16_column_parallel(
2289 &self,
2290 matrix: Bf16Matrix<'_>,
2291 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2292 self.upload_bf16_column_parallel_inner(matrix, None, false)
2293 }
2294
2295 pub fn upload_step_bf16_column_parallel(
2297 &self,
2298 matrix: Bf16Matrix<'_>,
2299 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2300 self.upload_step_bf16_column_parallel_inner(matrix, false)
2301 }
2302
2303 pub fn upload_step_bf16_column_parallel_f32_mirror(
2308 &self,
2309 matrix: Bf16Matrix<'_>,
2310 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2311 self.upload_step_bf16_column_parallel_inner(matrix, true)
2312 }
2313
2314 fn upload_step_bf16_column_parallel_inner(
2315 &self,
2316 matrix: Bf16Matrix<'_>,
2317 f32_mirror: bool,
2318 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2319 let canonical_chunk_rows =
2320 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2321 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2322 }
2323
2324 fn upload_bf16_column_parallel_inner(
2325 &self,
2326 matrix: Bf16Matrix<'_>,
2327 canonical_chunk_rows: Option<usize>,
2328 f32_mirror: bool,
2329 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2330 matrix.validate()?;
2331 let tp = self.ranks.len();
2332 if matrix.out_features % tp != 0 {
2333 return Err(format!(
2334 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2335 matrix.out_features
2336 )
2337 .into());
2338 }
2339 let mut ranks = Vec::with_capacity(tp);
2340 for (rank, engine) in self.ranks.iter().enumerate() {
2341 ranks.push(upload_bf16_rank(
2342 engine,
2343 bf16_column_shard(matrix, tp, rank)?,
2344 f32_mirror,
2345 )?);
2346 }
2347 Ok(ResidentBf16ColumnParallel {
2348 ranks,
2349 out_features: matrix.out_features,
2350 in_features: matrix.in_features,
2351 canonical_chunk_rows,
2352 })
2353 }
2354
2355 pub fn bf16_column_parallel_resident(
2356 &self,
2357 matrix: &ResidentBf16ColumnParallel,
2358 activations: &[f32],
2359 tokens: usize,
2360 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2361 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2362 validate_activations(activations, tokens, matrix.in_features)?;
2363 let local_out = matrix.out_features / self.ranks.len();
2364 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2365 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2366 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2367 let output = run_resident_bf16_rank(
2368 engine,
2369 shard,
2370 activations,
2371 tokens,
2372 matrix.canonical_chunk_rows,
2373 )?;
2374 for token in 0..tokens {
2375 let src = &output[token * local_out..(token + 1) * local_out];
2376 let dst_start = token * matrix.out_features + rank * local_out;
2377 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2378 }
2379 rank_outputs.push(output);
2380 }
2381 Ok(ColumnParallelResult {
2382 gathered,
2383 rank_outputs,
2384 })
2385 }
2386
2387 pub fn bf16_column_parallel_resident_native(
2394 &self,
2395 matrix: &ResidentBf16ColumnParallel,
2396 activations: &[f32],
2397 tokens: usize,
2398 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2399 let rank_outputs =
2400 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2401 let local_out = matrix.out_features / self.ranks.len();
2402 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2403 }
2404
2405 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2410 self.ranks
2411 .first()
2412 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2413 }
2414
2415 pub fn bf16_column_parallel_resident_native_device(
2428 &self,
2429 matrix: &ResidentBf16ColumnParallel,
2430 root_activation: &CudaSlice<f32>,
2431 tokens: usize,
2432 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2433 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2434 matrix,
2435 root_activation,
2436 tokens,
2437 )?;
2438 let local_out = matrix.out_features / self.ranks.len();
2439 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2440 let root = &self.ranks[0];
2441 let _main = root.gpu.enter_main()?;
2442 root.stream().synchronize()?;
2443 Ok(gathered)
2444 }
2445
2446 pub fn bf16_column_parallel_resident_device_shards_from_root(
2451 &self,
2452 matrix: &ResidentBf16ColumnParallel,
2453 root_activation: &CudaSlice<f32>,
2454 tokens: usize,
2455 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2456 if self.ranks.len() > 1 && !self.native_p2p {
2457 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2458 }
2459 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2460 let values = tokens
2461 .checked_mul(matrix.in_features)
2462 .ok_or("device BF16 column activation size overflow")?;
2463 let root = &self.ranks[0];
2464 if tokens == 0
2465 || root_activation.len() < values
2466 || root_activation.ordinal() != root.ctx().ordinal()
2467 {
2468 return Err("device BF16 column root activation geometry mismatch".into());
2469 }
2470
2471 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2472 let root_input = {
2473 let _main = root.gpu.enter_main()?;
2474 let mut root_input = root.uninit(values)?;
2475 root.stream()
2476 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2477 root_input
2478 };
2479 {
2483 let _main = root.gpu.enter_main()?;
2484 root.stream().synchronize()?;
2485 }
2486 rank_inputs.push(root_input);
2487 for engine in &self.ranks[1..] {
2488 let peer_input = {
2489 let _main = engine.gpu.enter_main()?;
2490 let mut peer_input = engine.uninit(values)?;
2491 engine
2492 .stream()
2493 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2494 peer_input
2495 };
2496 rank_inputs.push(peer_input);
2497 }
2498
2499 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2500 for rank in 0..self.ranks.len() {
2501 rank_outputs.push(run_resident_bf16_rank_device(
2502 &self.ranks[rank],
2503 &matrix.ranks[rank],
2504 &rank_inputs[rank],
2505 tokens,
2506 matrix.canonical_chunk_rows,
2507 self.bulk_p2p,
2508 )?);
2509 }
2510 Ok(rank_outputs)
2511 }
2512
2513 pub fn bf16_column_parallel_resident_device_shards(
2520 &self,
2521 matrix: &ResidentBf16ColumnParallel,
2522 activations: &[f32],
2523 tokens: usize,
2524 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2525 if self.ranks.len() > 1 && !self.native_p2p {
2526 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2527 }
2528 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2529 validate_activations(activations, tokens, matrix.in_features)?;
2530
2531 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2532 let root_input = {
2533 let root = &self.ranks[0];
2534 let _main = root.gpu.enter_main()?;
2535 root.htod(activations)?
2536 };
2537 {
2543 let root = &self.ranks[0];
2544 let _main = root.gpu.enter_main()?;
2545 root.stream().synchronize()?;
2546 }
2547 rank_inputs.push(root_input);
2548 for engine in &self.ranks[1..] {
2549 let peer_input = {
2550 let _main = engine.gpu.enter_main()?;
2551 let mut peer_input = engine.uninit(activations.len())?;
2552 engine
2553 .stream()
2554 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2555 peer_input
2556 };
2557 rank_inputs.push(peer_input);
2558 }
2559
2560 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2561 for rank in 0..self.ranks.len() {
2562 rank_outputs.push(run_resident_bf16_rank_device(
2563 &self.ranks[rank],
2564 &matrix.ranks[rank],
2565 &rank_inputs[rank],
2566 tokens,
2567 matrix.canonical_chunk_rows,
2568 self.bulk_p2p,
2569 )?);
2570 }
2571 Ok(rank_outputs)
2572 }
2573
2574 pub fn allocate_replicated_device_rows(
2578 &self,
2579 tokens: usize,
2580 width: usize,
2581 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2582 if self.ranks.len() > 1 && !self.native_p2p {
2583 return Err("replicated device rows require native P2P ranks".into());
2584 }
2585 let values = tokens
2586 .checked_mul(width)
2587 .ok_or("replicated device row size overflow")?;
2588 let rank_lengths = vec![values; self.ranks.len()];
2589 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2590 let mut ranks = Vec::with_capacity(self.ranks.len());
2591 for engine in &self.ranks {
2592 let _main = engine.gpu.enter_main()?;
2593 ranks.push(engine.uninit(values)?);
2594 }
2595 Ok(ResidentReplicatedDeviceRows {
2596 ranks,
2597 tokens,
2598 width,
2599 })
2600 }
2601
2602 pub fn refresh_replicated_device_rows_from_root(
2604 &self,
2605 rows: &mut ResidentReplicatedDeviceRows,
2606 source: &CudaSlice<f32>,
2607 ) -> Result<(), Box<dyn std::error::Error>> {
2608 if self.ranks.len() > 1 && !self.native_p2p {
2609 return Err("replicated device rows require native P2P ranks".into());
2610 }
2611 validate_replicated_device_rows(&self.ranks, rows)?;
2612 let root = self
2613 .ranks
2614 .first()
2615 .ok_or("replicated rows have no root rank")?;
2616 let values = replicated_device_row_source_values(
2617 rows.tokens,
2618 rows.width,
2619 source.len(),
2620 source.ordinal(),
2621 root.ctx().ordinal(),
2622 )?;
2623 let (root_rows, peer_rows) = rows
2624 .ranks
2625 .split_first_mut()
2626 .ok_or("replicated rows have no root allocation")?;
2627 {
2628 let _main = root.gpu.enter_main()?;
2629 let mut destination = root_rows.slice_mut(0..values);
2630 root.stream()
2631 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2632 root.stream().synchronize()?;
2633 }
2634 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2635 let _main = engine.gpu.enter_main()?;
2636 let mut destination = peer_rows.slice_mut(0..values);
2637 engine
2638 .stream()
2639 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2640 }
2641 Ok(())
2642 }
2643
2644 pub fn upload_replicated_device_rows(
2646 &self,
2647 rows: &[f32],
2648 tokens: usize,
2649 width: usize,
2650 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2651 if self.ranks.len() > 1 && !self.native_p2p {
2652 return Err("replicated device rows require native P2P ranks".into());
2653 }
2654 validate_activations(rows, tokens, width)?;
2655 let root = self
2656 .ranks
2657 .first()
2658 .ok_or("replicated rows have no root rank")?;
2659 let root_rows = {
2660 let _main = root.gpu.enter_main()?;
2661 root.htod(rows)?
2662 };
2663 {
2664 let _main = root.gpu.enter_main()?;
2665 root.stream().synchronize()?;
2666 }
2667 let mut ranks = Vec::with_capacity(self.ranks.len());
2668 ranks.push(root_rows);
2669 for engine in self.ranks.iter().skip(1) {
2670 let _main = engine.gpu.enter_main()?;
2671 let mut peer_rows = engine.uninit(rows.len())?;
2672 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2673 ranks.push(peer_rows);
2674 }
2675 Ok(ResidentReplicatedDeviceRows {
2676 ranks,
2677 tokens,
2678 width,
2679 })
2680 }
2681
2682 pub fn bf16_column_parallel_resident_replicated_device_shards(
2684 &self,
2685 matrix: &ResidentBf16ColumnParallel,
2686 activations: &ResidentReplicatedDeviceRows,
2687 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2688 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2689 validate_replicated_device_rows(&self.ranks, activations)?;
2690 if activations.width != matrix.in_features {
2691 return Err(format!(
2692 "replicated BF16 column input width {} != matrix width {}",
2693 activations.width, matrix.in_features
2694 )
2695 .into());
2696 }
2697 let mut outputs = Vec::with_capacity(self.ranks.len());
2698 for rank in 0..self.ranks.len() {
2699 outputs.push(run_resident_bf16_rank_device(
2700 &self.ranks[rank],
2701 &matrix.ranks[rank],
2702 &activations.ranks[rank],
2703 activations.tokens,
2704 matrix.canonical_chunk_rows,
2705 self.bulk_p2p,
2706 )?);
2707 }
2708 Ok(outputs)
2709 }
2710
2711 #[allow(clippy::too_many_arguments)]
2713 pub fn upload_sigmoid_topk_router(
2714 &self,
2715 weight: Bf16Matrix<'_>,
2716 correction_bias: &[f32],
2717 active: Option<&[bool]>,
2718 experts_per_token: usize,
2719 scaling_factor: f32,
2720 route_norm: bool,
2721 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2722 weight.validate()?;
2723 if correction_bias.len() != weight.out_features
2724 || experts_per_token == 0
2725 || experts_per_token > weight.out_features
2726 || !correction_bias.iter().all(|value| value.is_finite())
2727 || !scaling_factor.is_finite()
2728 || scaling_factor <= 0.0
2729 {
2730 return Err(format!(
2731 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2732 weight.out_features,
2733 weight.in_features,
2734 correction_bias.len(),
2735 experts_per_token,
2736 )
2737 .into());
2738 }
2739 let active_row = active
2740 .map(|mask| {
2741 if mask.len() != weight.out_features {
2742 return Err(format!(
2743 "sigmoid router active mask {} != experts {}",
2744 mask.len(),
2745 weight.out_features
2746 ));
2747 }
2748 Ok(mask
2749 .iter()
2750 .map(|&enabled| u8::from(enabled))
2751 .collect::<Vec<_>>())
2752 })
2753 .transpose()?
2754 .unwrap_or_else(|| vec![1; weight.out_features]);
2755 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2756 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2757
2758 let root = self
2759 .ranks
2760 .first()
2761 .ok_or("sigmoid router runtime has no root rank")?;
2762 let _main = root.gpu.enter_main()?;
2763 let bf16 = root.htod_bytes(weight.bytes)?;
2764 let weight_f32 = root.bf16_to_f32(
2765 &bf16.slice(0..bf16.len()),
2766 weight.out_features * weight.in_features,
2767 )?;
2768 Ok(ResidentSigmoidTopKRouter {
2769 weight: weight_f32,
2770 correction_bias: root.htod(correction_bias)?,
2771 active: root.htod_bytes(&active_row)?,
2772 root_device: root.ctx().ordinal(),
2773 input_width: weight.in_features,
2774 expert_count: weight.out_features,
2775 experts_per_token,
2776 active_count,
2777 scaling_factor,
2778 route_norm,
2779 })
2780 }
2781
2782 pub fn sigmoid_topk_replicated_device_rows_host(
2787 &self,
2788 router: &ResidentSigmoidTopKRouter,
2789 input: &ResidentReplicatedDeviceRows,
2790 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2791 validate_replicated_device_rows(&self.ranks, input)?;
2792 if input.width != router.input_width {
2793 return Err(format!(
2794 "sigmoid router input width {} != resident width {}",
2795 input.width, router.input_width
2796 )
2797 .into());
2798 }
2799 let root = self
2800 .ranks
2801 .first()
2802 .ok_or("sigmoid router runtime has no root rank")?;
2803 let _main = root.gpu.enter_main()?;
2804 if root.ctx().ordinal() != router.root_device
2805 || router.weight.ordinal() != router.root_device
2806 || router.correction_bias.ordinal() != router.root_device
2807 || router.active.ordinal() != router.root_device
2808 {
2809 return Err("sigmoid router root residency changed".into());
2810 }
2811 let logits = root.router_gemv(
2812 &router.weight,
2813 &input.ranks[0],
2814 router.input_width,
2815 router.expert_count,
2816 input.tokens,
2817 )?;
2818 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2819 &logits,
2820 input.tokens,
2821 router.expert_count,
2822 router.experts_per_token,
2823 router.active_count,
2824 &router.correction_bias,
2825 &router.active,
2826 router.scaling_factor,
2827 router.route_norm,
2828 )?;
2829 Ok(SigmoidTopKHostOutput {
2830 logits: root.dtoh(&logits)?,
2831 selected,
2832 weights,
2833 })
2834 }
2835
2836 pub fn upload_replicated_bf16_swiglu(
2838 &self,
2839 gate: Bf16Matrix<'_>,
2840 up: Bf16Matrix<'_>,
2841 down: Bf16Matrix<'_>,
2842 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2843 gate.validate()?;
2844 up.validate()?;
2845 down.validate()?;
2846 if gate.in_features != up.in_features
2847 || gate.out_features != up.out_features
2848 || down.in_features != gate.out_features
2849 || down.out_features != gate.in_features
2850 {
2851 return Err(format!(
2852 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2853 gate.out_features,
2854 gate.in_features,
2855 up.out_features,
2856 up.in_features,
2857 down.out_features,
2858 down.in_features,
2859 )
2860 .into());
2861 }
2862 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2863 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2864 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2865 for engine in &self.ranks {
2866 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2867 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2868 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2869 }
2870 Ok(ResidentReplicatedBf16SwiGlu {
2871 gate: gate_ranks,
2872 up: up_ranks,
2873 down: down_ranks,
2874 input_width: gate.in_features,
2875 intermediate_width: gate.out_features,
2876 })
2877 }
2878
2879 pub fn replicated_bf16_swiglu_resident_device(
2881 &self,
2882 mlp: &ResidentReplicatedBf16SwiGlu,
2883 input: &ResidentReplicatedDeviceRows,
2884 activation_limit: Option<f32>,
2885 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2886 validate_step_expert_activation_limit(activation_limit)?;
2887 validate_replicated_device_rows(&self.ranks, input)?;
2888 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2889 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2890 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2891 if input.width != mlp.input_width
2892 || mlp.gate.len() != self.ranks.len()
2893 || mlp.up.len() != self.ranks.len()
2894 || mlp.down.len() != self.ranks.len()
2895 {
2896 return Err("replicated BF16 SwiGLU residency or input width changed".into());
2897 }
2898
2899 let mut outputs = Vec::with_capacity(self.ranks.len());
2900 for rank in 0..self.ranks.len() {
2901 let engine = &self.ranks[rank];
2902 let gate = run_resident_bf16_rank_device(
2903 engine,
2904 &mlp.gate[rank],
2905 &input.ranks[rank],
2906 input.tokens,
2907 None,
2908 self.bulk_p2p,
2909 )?;
2910 let up = run_resident_bf16_rank_device(
2911 engine,
2912 &mlp.up[rank],
2913 &input.ranks[rank],
2914 input.tokens,
2915 None,
2916 self.bulk_p2p,
2917 )?;
2918 let _main = engine.gpu.enter_main()?;
2919 let values = input
2920 .tokens
2921 .checked_mul(mlp.intermediate_width)
2922 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
2923 let mut activation = engine.uninit(values)?;
2924 if let Some(limit) = activation_limit {
2925 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
2926 } else {
2927 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
2928 }
2929 outputs.push(run_resident_bf16_rank_device(
2930 engine,
2931 &mlp.down[rank],
2932 &activation,
2933 input.tokens,
2934 None,
2935 self.bulk_p2p,
2936 )?);
2937 }
2938 Ok(ResidentReplicatedDeviceRows {
2939 ranks: outputs,
2940 tokens: input.tokens,
2941 width: mlp.input_width,
2942 })
2943 }
2944
2945 pub fn rms_norm_replicated_device_rows(
2947 &self,
2948 input: &ResidentReplicatedDeviceRows,
2949 weight: &[f32],
2950 eps: f32,
2951 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2952 validate_replicated_device_rows(&self.ranks, input)?;
2953 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
2954 return Err(format!(
2955 "replicated RMS norm weight/eps {}/{} != width {}",
2956 weight.len(),
2957 eps,
2958 input.width
2959 )
2960 .into());
2961 }
2962 let mut ranks = Vec::with_capacity(self.ranks.len());
2963 for (rank, engine) in self.ranks.iter().enumerate() {
2964 let _main = engine.gpu.enter_main()?;
2965 let weight = engine.htod(weight)?;
2966 let mut output = engine.uninit(input.tokens * input.width)?;
2967 engine.rms_norm(
2968 &input.ranks[rank],
2969 &weight,
2970 &mut output,
2971 input.width,
2972 input.tokens,
2973 eps,
2974 )?;
2975 ranks.push(output);
2976 }
2977 Ok(ResidentReplicatedDeviceRows {
2978 ranks,
2979 tokens: input.tokens,
2980 width: input.width,
2981 })
2982 }
2983
2984 pub fn add_rms_norm_replicated_device_rows(
2986 &self,
2987 input: &ResidentReplicatedDeviceRows,
2988 update: &ResidentReplicatedDeviceRows,
2989 weight: &[f32],
2990 eps: f32,
2991 ) -> Result<
2992 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
2993 Box<dyn std::error::Error>,
2994 > {
2995 validate_replicated_device_rows(&self.ranks, input)?;
2996 validate_replicated_device_rows(&self.ranks, update)?;
2997 if input.tokens != update.tokens
2998 || input.width != update.width
2999 || weight.len() != input.width
3000 || !eps.is_finite()
3001 || eps <= 0.0
3002 {
3003 return Err(format!(
3004 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3005 input.tokens,
3006 input.width,
3007 update.tokens,
3008 update.width,
3009 weight.len(),
3010 )
3011 .into());
3012 }
3013 let values = input.tokens * input.width;
3014 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3015 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3016 for (rank, engine) in self.ranks.iter().enumerate() {
3017 let _main = engine.gpu.enter_main()?;
3018 let weight = engine.htod(weight)?;
3019 let mut residual = engine.uninit(values)?;
3020 let mut normalized = engine.uninit(values)?;
3021 engine.add_rms_norm(
3022 &input.ranks[rank],
3023 &update.ranks[rank],
3024 &weight,
3025 &mut residual,
3026 &mut normalized,
3027 input.width,
3028 input.tokens,
3029 eps,
3030 )?;
3031 residual_ranks.push(residual);
3032 normalized_ranks.push(normalized);
3033 }
3034 Ok((
3035 ResidentReplicatedDeviceRows {
3036 ranks: residual_ranks,
3037 tokens: input.tokens,
3038 width: input.width,
3039 },
3040 ResidentReplicatedDeviceRows {
3041 ranks: normalized_ranks,
3042 tokens: input.tokens,
3043 width: input.width,
3044 },
3045 ))
3046 }
3047
3048 pub fn collect_replicated_device_rows(
3049 &self,
3050 rows: &ResidentReplicatedDeviceRows,
3051 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3052 validate_replicated_device_rows(&self.ranks, rows)?;
3053 let mut outputs = Vec::with_capacity(self.ranks.len());
3054 for (rank, engine) in self.ranks.iter().enumerate() {
3055 let _main = engine.gpu.enter_main()?;
3056 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3057 }
3058 Ok(outputs)
3059 }
3060
3061 pub fn upload_bf16_row_parallel(
3062 &self,
3063 matrix: Bf16Matrix<'_>,
3064 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3065 matrix.validate()?;
3066 let tp = self.ranks.len();
3067 if matrix.in_features % tp != 0 {
3068 return Err(format!(
3069 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3070 matrix.in_features
3071 )
3072 .into());
3073 }
3074 let mut ranks = Vec::with_capacity(tp);
3075 for (rank, engine) in self.ranks.iter().enumerate() {
3076 let shard = bf16_row_shard(matrix, tp, rank)?;
3077 ranks.push(upload_bf16_rank(
3078 engine,
3079 Bf16Matrix {
3080 bytes: &shard,
3081 out_features: matrix.out_features,
3082 in_features: matrix.in_features / tp,
3083 },
3084 false,
3085 )?);
3086 }
3087 Ok(ResidentBf16RowParallel {
3088 ranks,
3089 out_features: matrix.out_features,
3090 in_features: matrix.in_features,
3091 })
3092 }
3093
3094 pub fn bf16_row_parallel_resident(
3095 &self,
3096 matrix: &ResidentBf16RowParallel,
3097 activations: &[f32],
3098 tokens: usize,
3099 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3100 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3101 validate_activations(activations, tokens, matrix.in_features)?;
3102 let tp = self.ranks.len();
3103 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3104 let mut rank_partials = Vec::with_capacity(tp);
3105 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3106 let local_activations =
3107 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3108 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3109 for (sum, value) in reduced.iter_mut().zip(&partial) {
3110 *sum += value;
3111 }
3112 rank_partials.push(partial);
3113 }
3114 Ok(RowParallelResult {
3115 reduced,
3116 rank_partials,
3117 })
3118 }
3119
3120 pub fn upload_step_bf16_row_parallel(
3122 &self,
3123 matrix: Bf16Matrix<'_>,
3124 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3125 self.upload_step_bf16_row_parallel_inner(matrix, false)
3126 }
3127
3128 pub fn upload_step_bf16_row_parallel_f32_mirror(
3129 &self,
3130 matrix: Bf16Matrix<'_>,
3131 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3132 self.upload_step_bf16_row_parallel_inner(matrix, true)
3133 }
3134
3135 fn upload_step_bf16_row_parallel_inner(
3136 &self,
3137 matrix: Bf16Matrix<'_>,
3138 f32_mirror: bool,
3139 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3140 matrix.validate()?;
3141 let tp = self.ranks.len();
3142 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3143 let local_in = matrix.in_features / tp;
3144 let blocks_per_rank = local_in / canonical_chunk_cols;
3145 let mut ranks = Vec::with_capacity(tp);
3146 for (rank, engine) in self.ranks.iter().enumerate() {
3147 let mut blocks = Vec::with_capacity(blocks_per_rank);
3148 for block in 0..blocks_per_rank {
3149 let global_block = rank * blocks_per_rank + block;
3150 let col_start = global_block * canonical_chunk_cols;
3151 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3152 blocks.push(upload_bf16_rank(
3153 engine,
3154 Bf16Matrix {
3155 bytes: &bytes,
3156 out_features: matrix.out_features,
3157 in_features: canonical_chunk_cols,
3158 },
3159 f32_mirror,
3160 )?);
3161 }
3162 ranks.push(blocks);
3163 }
3164 Ok(ResidentStepBf16RowParallel {
3165 ranks,
3166 out_features: matrix.out_features,
3167 in_features: matrix.in_features,
3168 canonical_chunk_cols,
3169 })
3170 }
3171
3172 pub fn step_bf16_row_parallel_resident(
3177 &self,
3178 matrix: &ResidentStepBf16RowParallel,
3179 activations: &[f32],
3180 tokens: usize,
3181 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3182 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3183 validate_activations(activations, tokens, matrix.in_features)?;
3184 let root = &self.ranks[0];
3185 let output_len = tokens
3186 .checked_mul(matrix.out_features)
3187 .ok_or("Step BF16 row output size overflow")?;
3188 let mut reduced = {
3189 let _main = root.gpu.enter_main()?;
3190 root.htod(&vec![0.0f32; output_len])?
3191 };
3192 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3193 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3194 for (block, resident) in blocks.iter().enumerate() {
3195 let global_block = rank * blocks_per_rank + block;
3196 let input = activation_shard(
3197 activations,
3198 tokens,
3199 matrix.in_features,
3200 PRODUCT_MAX_CARDS,
3201 global_block,
3202 );
3203 let partial =
3204 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3205 let next = {
3206 let _main = root.gpu.enter_main()?;
3207 let partial = root.htod(&partial)?;
3208 let mut next = root.uninit(output_len)?;
3209 root.add(&reduced, &partial, &mut next, output_len)?;
3210 next
3211 };
3212 reduced = next;
3213 }
3214 }
3215 let _main = root.gpu.enter_main()?;
3216 root.dtoh(&reduced)
3217 }
3218
3219 pub fn step_bf16_row_parallel_resident_native(
3225 &self,
3226 matrix: &ResidentStepBf16RowParallel,
3227 activations: &[f32],
3228 tokens: usize,
3229 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3230 if self.ranks.len() > 1 && !self.native_p2p {
3231 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3232 }
3233 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3234 validate_activations(activations, tokens, matrix.in_features)?;
3235 let root = &self.ranks[0];
3236 let root_input = {
3237 let _main = root.gpu.enter_main()?;
3238 root.htod(activations)?
3239 };
3240 {
3243 let _main = root.gpu.enter_main()?;
3244 root.stream().synchronize()?;
3245 }
3246 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3247 let _main = root.gpu.enter_main()?;
3248 root.dtoh(&reduced)
3249 }
3250
3251 pub fn step_bf16_row_parallel_resident_native_device(
3259 &self,
3260 matrix: &ResidentStepBf16RowParallel,
3261 root_activation: &CudaSlice<f32>,
3262 tokens: usize,
3263 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3264 if self.ranks.len() > 1 && !self.native_p2p {
3265 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3266 }
3267 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3268 let values = tokens
3269 .checked_mul(matrix.in_features)
3270 .ok_or("device Step BF16 row activation size overflow")?;
3271 let root = &self.ranks[0];
3272 if tokens == 0
3273 || root_activation.len() < values
3274 || root_activation.ordinal() != root.ctx().ordinal()
3275 {
3276 return Err("device Step BF16 row root activation geometry mismatch".into());
3277 }
3278 let root_input = {
3279 let _main = root.gpu.enter_main()?;
3280 let mut root_input = root.uninit(values)?;
3281 root.stream()
3282 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3283 root.stream().synchronize()?; root_input
3285 };
3286 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3287 let _main = root.gpu.enter_main()?;
3288 root.stream().synchronize()?;
3289 Ok(reduced)
3290 }
3291
3292 fn step_bf16_row_native_reduce_from_root(
3297 &self,
3298 matrix: &ResidentStepBf16RowParallel,
3299 root_input: &CudaSlice<f32>,
3300 tokens: usize,
3301 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3302 let root = &self.ranks[0];
3303 let output_len = tokens
3304 .checked_mul(matrix.out_features)
3305 .ok_or("native Step BF16 row output size overflow")?;
3306 let mut reduced = {
3307 let _main = root.gpu.enter_main()?;
3308 root.htod(&vec![0.0f32; output_len])?
3309 };
3310 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3311 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3312 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3313 let mut remote_partial_keepalive = Vec::new();
3314 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3315 for (block, resident) in blocks.iter().enumerate() {
3316 let global_block = rank * blocks_per_rank + block;
3317 let col_start = global_block * matrix.canonical_chunk_cols;
3318 let block_len = tokens
3319 .checked_mul(matrix.canonical_chunk_cols)
3320 .ok_or("native Step BF16 row block size overflow")?;
3321 let block_input = if self.bulk_p2p {
3322 let root_packed = {
3323 let _main = root.gpu.enter_main()?;
3324 let mut root_packed = root.uninit(block_len)?;
3325 root.copy_rows_strided(
3326 &root_input,
3327 &mut root_packed,
3328 matrix.canonical_chunk_cols,
3329 tokens,
3330 matrix.in_features,
3331 col_start,
3332 )?;
3333 root_packed
3334 };
3335 if rank == 0 {
3336 root_packed
3337 } else {
3338 {
3341 let _main = root.gpu.enter_main()?;
3342 root.stream().synchronize()?;
3343 }
3344 let engine = &self.ranks[rank];
3345 let _main = engine.gpu.enter_main()?;
3346 let mut block_input = engine.uninit(block_len)?;
3347 engine
3348 .stream()
3349 .memcpy_dtod(&root_packed, &mut block_input)?;
3350 root_packed_keepalive.push(root_packed);
3351 block_input
3352 }
3353 } else {
3354 let engine = &self.ranks[rank];
3355 let _main = engine.gpu.enter_main()?;
3356 let mut block_input = engine.uninit(block_len)?;
3357 for token in 0..tokens {
3358 let source_start = token * matrix.in_features + col_start;
3359 let source = root_input
3360 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3361 let destination_start = token * matrix.canonical_chunk_cols;
3362 let mut destination = block_input.slice_mut(
3363 destination_start..destination_start + matrix.canonical_chunk_cols,
3364 );
3365 engine.stream().memcpy_dtod(&source, &mut destination)?;
3366 }
3367 block_input
3368 };
3369 let partial = run_resident_bf16_rank_device(
3370 &self.ranks[rank],
3371 resident,
3372 &block_input,
3373 tokens,
3374 None,
3375 self.bulk_p2p,
3376 )?;
3377 block_input_keepalive.push(block_input);
3378 let root_partial = if rank == 0 {
3379 partial
3380 } else {
3381 {
3384 let engine = &self.ranks[rank];
3385 let _main = engine.gpu.enter_main()?;
3386 engine.stream().synchronize()?;
3387 }
3388 let _main = root.gpu.enter_main()?;
3389 let mut peer_partial = root.uninit(output_len)?;
3390 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3391 remote_partial_keepalive.push(partial);
3392 peer_partial
3393 };
3394 let next = {
3395 let _main = root.gpu.enter_main()?;
3396 let mut next = root.uninit(output_len)?;
3397 root.add(&reduced, &root_partial, &mut next, output_len)?;
3398 next
3399 };
3400 reduced = next;
3401 }
3402 }
3403 {
3404 let _main = root.gpu.enter_main()?;
3405 root.stream().synchronize()?;
3406 }
3407 drop(remote_partial_keepalive);
3408 drop(root_packed_keepalive);
3409 drop(block_input_keepalive);
3410 Ok(reduced)
3411 }
3412
3413 pub fn step_bf16_row_parallel_resident_root_device(
3416 &self,
3417 matrix: &ResidentStepBf16RowParallel,
3418 rank_activations: &[CudaSlice<f32>],
3419 tokens: usize,
3420 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3421 if self.ranks.len() > 1 && !self.native_p2p {
3422 return Err(
3423 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3424 );
3425 }
3426 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3427 let local_width = matrix.in_features / self.ranks.len();
3428 let shard_len = tokens
3429 .checked_mul(local_width)
3430 .ok_or("device Step BF16 row shard size overflow")?;
3431 if tokens == 0
3432 || rank_activations.len() != self.ranks.len()
3433 || rank_activations
3434 .iter()
3435 .zip(&self.ranks)
3436 .any(|(rows, engine)| {
3437 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3438 })
3439 {
3440 return Err("device Step BF16 row activation shard geometry changed".into());
3441 }
3442
3443 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3444 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3445 let mut partials = Vec::with_capacity(self.ranks.len());
3446 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3447 if blocks.len() != blocks_per_rank {
3448 return Err(format!(
3449 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3450 blocks.len()
3451 )
3452 .into());
3453 }
3454 let engine = &self.ranks[rank];
3455 let _main = engine.gpu.enter_main()?;
3456 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3457 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3458 for (block, resident) in blocks.iter().enumerate() {
3459 let block_len = tokens
3460 .checked_mul(matrix.canonical_chunk_cols)
3461 .ok_or("device Step BF16 row block size overflow")?;
3462 let mut block_input = engine.uninit(block_len)?;
3463 let local_col_start = block * matrix.canonical_chunk_cols;
3464 if self.bulk_p2p {
3465 engine.copy_rows_strided(
3466 &rank_activations[rank],
3467 &mut block_input,
3468 matrix.canonical_chunk_cols,
3469 tokens,
3470 local_width,
3471 local_col_start,
3472 )?;
3473 } else {
3474 for token in 0..tokens {
3475 let source_start = token * local_width + local_col_start;
3476 let source = rank_activations[rank]
3477 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3478 let destination_start = token * matrix.canonical_chunk_cols;
3479 let mut destination = block_input.slice_mut(
3480 destination_start..destination_start + matrix.canonical_chunk_cols,
3481 );
3482 engine.stream().memcpy_dtod(&source, &mut destination)?;
3483 }
3484 }
3485 let partial = run_resident_bf16_rank_device(
3486 engine,
3487 resident,
3488 &block_input,
3489 tokens,
3490 None,
3491 self.bulk_p2p,
3492 )?;
3493 rank_inputs.push(block_input);
3494 rank_partials.push(partial);
3495 }
3496 block_inputs.push(rank_inputs);
3497 partials.push(rank_partials);
3498 }
3499 for engine in self.ranks.iter().skip(1) {
3500 let _main = engine.gpu.enter_main()?;
3501 engine.stream().synchronize()?;
3502 }
3503
3504 let output_len = tokens
3505 .checked_mul(matrix.out_features)
3506 .ok_or("device Step BF16 row output size overflow")?;
3507 let root = &self.ranks[0];
3508 let _main = root.gpu.enter_main()?;
3509 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3510 let mut remote_partials = Vec::new();
3511 for (rank, rank_partials) in partials.into_iter().enumerate() {
3512 for partial in rank_partials {
3513 let root_partial = if rank == 0 {
3514 partial
3515 } else {
3516 let mut peer_partial = root.uninit(output_len)?;
3517 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3518 remote_partials.push(partial);
3519 peer_partial
3520 };
3521 let mut next = root.uninit(output_len)?;
3522 root.add(&reduced, &root_partial, &mut next, output_len)?;
3523 reduced = next;
3524 }
3525 }
3526 root.stream().synchronize()?;
3527 drop(remote_partials);
3528 drop(block_inputs);
3529 Ok(reduced)
3530 }
3531
3532 pub fn step_bf16_row_parallel_resident_replicated_device(
3534 &self,
3535 matrix: &ResidentStepBf16RowParallel,
3536 rank_activations: &[CudaSlice<f32>],
3537 tokens: usize,
3538 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3539 let reduced =
3540 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3541 let output_len = tokens
3542 .checked_mul(matrix.out_features)
3543 .ok_or("device Step BF16 row output size overflow")?;
3544 let mut ranks = Vec::with_capacity(self.ranks.len());
3545 ranks.push(reduced);
3546 for engine in self.ranks.iter().skip(1) {
3547 let _main = engine.gpu.enter_main()?;
3548 let mut peer_output = engine.uninit(output_len)?;
3549 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3550 ranks.push(peer_output);
3551 }
3552 Ok(ResidentReplicatedDeviceRows {
3553 ranks,
3554 tokens,
3555 width: matrix.out_features,
3556 })
3557 }
3558
3559 pub fn upload_expert(
3560 &self,
3561 gate: E4m3BlockMatrix<'_>,
3562 up: E4m3BlockMatrix<'_>,
3563 down: E4m3BlockMatrix<'_>,
3564 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3565 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3566 return Err("TP expert gate/up dimensions differ".into());
3567 }
3568 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3569 return Err(format!(
3570 "TP expert down {}x{} does not invert gate/up {}x{}",
3571 down.out_features, down.in_features, gate.out_features, gate.in_features
3572 )
3573 .into());
3574 }
3575 Ok(ResidentTpExpert {
3576 gate: self.upload_column_parallel(gate)?,
3577 up: self.upload_column_parallel(up)?,
3578 down: self.upload_row_parallel(down)?,
3579 input_width: gate.in_features,
3580 expert_width: gate.out_features,
3581 })
3582 }
3583
3584 pub fn run_expert(
3585 &self,
3586 expert: &ResidentTpExpert,
3587 input: &[f32],
3588 tokens: usize,
3589 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3590 validate_activations(input, tokens, expert.input_width)?;
3591 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3592 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3593 let activated: Vec<f32> = gate
3594 .gathered
3595 .iter()
3596 .zip(&up.gathered)
3597 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3598 .collect();
3599 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3600 Ok(self
3601 .row_parallel_resident(&expert.down, &activated, tokens)?
3602 .reduced)
3603 }
3604
3605 pub fn upload_expert_parallel(
3606 &self,
3607 gate: E4m3ExpertBank<'_>,
3608 up: E4m3ExpertBank<'_>,
3609 down: E4m3ExpertBank<'_>,
3610 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3611 gate.validate()?;
3612 up.validate()?;
3613 down.validate()?;
3614 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3615 return Err("EP gate/up/down expert counts differ".into());
3616 }
3617 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3618 return Err("EP gate/up dimensions differ".into());
3619 }
3620 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3621 return Err(format!(
3622 "EP down {}x{} does not invert gate/up {}x{}",
3623 down.out_features, down.in_features, gate.out_features, gate.in_features
3624 )
3625 .into());
3626 }
3627 if gate.expert_count % self.ranks.len() != 0 {
3628 return Err(format!(
3629 "EP expert count {} is not divisible by {} ranks",
3630 gate.expert_count,
3631 self.ranks.len()
3632 )
3633 .into());
3634 }
3635
3636 let per_rank = gate.expert_count / self.ranks.len();
3637 let mut ranks = Vec::with_capacity(self.ranks.len());
3638 for (rank, engine) in self.ranks.iter().enumerate() {
3639 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3640 ranks.push(ResidentEpRank {
3641 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3642 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3643 down: upload_expert_bank_rank(engine, down, expert_range)?,
3644 });
3645 }
3646 Ok(ResidentExpertParallel {
3647 ranks,
3648 expert_count: gate.expert_count,
3649 input_width: gate.in_features,
3650 expert_width: gate.out_features,
3651 })
3652 }
3653
3654 #[allow(clippy::too_many_arguments)]
3660 pub fn prepare_step_grouped_fp8_gate(
3661 &self,
3662 gate: E4m3ExpertBank<'_>,
3663 up: E4m3ExpertBank<'_>,
3664 down: E4m3ExpertBank<'_>,
3665 input: &[f32],
3666 tokens: usize,
3667 selected: &[usize],
3668 activation_limit: Option<f32>,
3669 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3670 gate.validate()?;
3671 up.validate()?;
3672 down.validate()?;
3673 validate_step_expert_activation_limit(activation_limit)?;
3674 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3675 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3676 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3677 {
3678 return Err(format!(
3679 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3680 got gate/up/down={}/{}/{}",
3681 gate.expert_count, up.expert_count, down.expert_count,
3682 )
3683 .into());
3684 }
3685 if gate.in_features != up.in_features
3686 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3687 || up.out_features != STEP_GROUPED_FP8_WIDTH
3688 || down.in_features != STEP_GROUPED_FP8_WIDTH
3689 || down.out_features != gate.in_features
3690 {
3691 return Err(format!(
3692 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3693 gate.out_features,
3694 gate.in_features,
3695 up.out_features,
3696 up.in_features,
3697 down.out_features,
3698 down.in_features,
3699 )
3700 .into());
3701 }
3702 validate_activations(input, tokens, gate.in_features)?;
3703 let pairs = tokens
3704 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3705 .ok_or("official Step grouped FP8 route count overflow")?;
3706 if selected.len() != pairs {
3707 return Err(format!(
3708 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3709 ({pairs})",
3710 selected.len()
3711 )
3712 .into());
3713 }
3714 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3715 let mut unique = routes.to_vec();
3716 unique.sort_unstable();
3717 unique.dedup();
3718 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3719 return Err(format!(
3720 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3721 {routes:?}"
3722 )
3723 .into());
3724 }
3725 }
3726
3727 let engine = self
3728 .ranks
3729 .first()
3730 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3731 let _main = engine.gpu.enter_main()?;
3732 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3733 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3734 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3735 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3736 let input = engine.htod(input)?;
3737 let route_csr = ExpertCsr::from_token_routes(
3738 STEP_GROUPED_FP8_EXPERTS,
3739 tokens,
3740 STEP_GROUPED_FP8_TOP_K,
3741 selected,
3742 )?
3743 .upload(engine)?;
3744 let pair_rows = (0..pairs).collect::<Vec<_>>();
3745 let down_csr =
3746 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3747 .upload(engine)?;
3748 let gate_workspace =
3749 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3750 let up_workspace =
3751 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3752 let down_workspace =
3753 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3754 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3755 Ok(PreparedStepGroupedFp8Gate {
3756 device: engine.ctx().ordinal(),
3757 gate,
3758 up,
3759 down,
3760 input,
3761 route_csr,
3762 down_csr,
3763 gate_workspace,
3764 up_workspace,
3765 down_workspace,
3766 activation,
3767 activation_limit,
3768 tokens,
3769 pairs,
3770 })
3771 }
3772
3773 pub fn run_step_grouped_fp8_gate(
3775 &self,
3776 plan: &mut PreparedStepGroupedFp8Gate,
3777 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3778 let engine = self
3779 .ranks
3780 .first()
3781 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3782 if engine.ctx().ordinal() != plan.device {
3783 return Err(format!(
3784 "official Step grouped FP8 plan device {} != rank-zero device {}",
3785 plan.device,
3786 engine.ctx().ordinal()
3787 )
3788 .into());
3789 }
3790 let _main = engine.gpu.enter_main()?;
3791
3792 plan.gate_workspace.quantize(engine, &plan.input)?;
3793 plan.gate_workspace.project(
3794 engine,
3795 &plan.gate.codes,
3796 &plan.gate.scales,
3797 &plan.route_csr,
3798 plan.gate.code_stride,
3799 plan.gate.scale_stride,
3800 1.0,
3801 )?;
3802 plan.up_workspace.quantize(engine, &plan.input)?;
3803 plan.up_workspace.project(
3804 engine,
3805 &plan.up.codes,
3806 &plan.up.scales,
3807 &plan.route_csr,
3808 plan.up.code_stride,
3809 plan.up.scale_stride,
3810 1.0,
3811 )?;
3812 if let Some(limit) = plan.activation_limit {
3813 engine.silu_clamped_mul_host_expf(
3814 plan.gate_workspace.output(),
3815 plan.up_workspace.output(),
3816 limit,
3817 &mut plan.activation,
3818 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3819 )?;
3820 } else {
3821 engine.silu_mul_host_expf(
3822 plan.gate_workspace.output(),
3823 plan.up_workspace.output(),
3824 &mut plan.activation,
3825 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3826 )?;
3827 }
3828 plan.down_workspace.quantize(engine, &plan.activation)?;
3829 plan.down_workspace.project(
3830 engine,
3831 &plan.down.codes,
3832 &plan.down.scales,
3833 &plan.down_csr,
3834 plan.down.code_stride,
3835 plan.down.scale_stride,
3836 1.0,
3837 )?;
3838
3839 Ok(StepGroupedFp8ProjectionOutput {
3840 gate: engine.dtoh(plan.gate_workspace.output())?,
3841 up: engine.dtoh(plan.up_workspace.output())?,
3842 down: engine.dtoh(plan.down_workspace.output())?,
3843 })
3844 }
3845
3846 pub fn prepare_step_grouped_expert_parallel_gate(
3847 &self,
3848 experts: &ResidentExpertParallel,
3849 input: &[f32],
3850 tokens: usize,
3851 selected: &[usize],
3852 activation_limit: Option<f32>,
3853 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3854 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3855 experts,
3856 input,
3857 tokens,
3858 selected,
3859 activation_limit,
3860 tokens,
3861 )
3862 }
3863
3864 #[allow(clippy::too_many_arguments)]
3865 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3866 &self,
3867 experts: &ResidentExpertParallel,
3868 input: &[f32],
3869 tokens: usize,
3870 selected: &[usize],
3871 activation_limit: Option<f32>,
3872 max_tokens: usize,
3873 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3874 if !self.native_p2p || !self.ep_device_arithmetic {
3875 return Err(
3876 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3877 );
3878 }
3879 validate_step_expert_activation_limit(activation_limit)?;
3880 validate_ep_residency(&self.ranks, experts)?;
3881 validate_activations(input, tokens, experts.input_width)?;
3882 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3883 return Err(format!(
3884 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3885 )
3886 .into());
3887 }
3888 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3889 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3890 {
3891 return Err(format!(
3892 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3893 STEP_GROUPED_FP8_EXPERTS,
3894 STEP_GROUPED_FP8_WIDTH,
3895 experts.expert_count,
3896 experts.expert_width,
3897 )
3898 .into());
3899 }
3900 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3901 let max_pairs = max_tokens
3902 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3903 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
3904 let input_capacity = max_tokens
3905 .checked_mul(experts.input_width)
3906 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
3907
3908 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3909 for engine in &self.ranks {
3910 let _main = engine.gpu.enter_main()?;
3911 rank_inputs.push(engine.uninit(input_capacity)?);
3912 }
3913
3914 let mut owners = Vec::with_capacity(self.ranks.len());
3915 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
3916 if rank.gate.expert_range != rank.up.expert_range
3917 || rank.gate.expert_range != rank.down.expert_range
3918 {
3919 return Err(format!(
3920 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
3921 owner_rank
3922 )
3923 .into());
3924 }
3925 let local_experts = rank.gate.expert_range.len();
3926 let engine = &self.ranks[owner_rank];
3927 let _main = engine.gpu.enter_main()?;
3928 let route_csr =
3929 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
3930 let down_csr =
3931 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
3932 let gate_workspace = Fp8GroupedWorkspace::new(
3933 engine,
3934 experts.input_width,
3935 experts.expert_width,
3936 max_tokens,
3937 max_pairs,
3938 )?;
3939 let up_workspace = Fp8GroupedWorkspace::new(
3940 engine,
3941 experts.input_width,
3942 experts.expert_width,
3943 max_tokens,
3944 max_pairs,
3945 )?;
3946 let down_workspace = Fp8GroupedWorkspace::new(
3947 engine,
3948 experts.expert_width,
3949 experts.input_width,
3950 max_pairs,
3951 max_pairs,
3952 )?;
3953 let activation = engine.uninit(
3954 max_pairs
3955 .checked_mul(experts.expert_width)
3956 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
3957 )?;
3958 owners.push(PreparedStepGroupedExpertOwner {
3959 rank: owner_rank,
3960 global_pairs: Vec::new(),
3961 route_csr,
3962 down_csr,
3963 gate_workspace,
3964 up_workspace,
3965 down_workspace,
3966 activation,
3967 });
3968 }
3969
3970 let mut plan = PreparedStepGroupedExpertParallelGate {
3971 rank_inputs,
3972 owners,
3973 activation_limit,
3974 tokens: 0,
3975 pairs: 0,
3976 max_tokens,
3977 max_pairs,
3978 input_width: experts.input_width,
3979 expert_width: experts.expert_width,
3980 generation: 0,
3981 executed_generation: None,
3982 ready: false,
3983 };
3984 self.refresh_step_grouped_expert_parallel_gate(
3985 experts, &mut plan, input, tokens, selected,
3986 )?;
3987 Ok(plan)
3988 }
3989
3990 fn prepare_step_grouped_expert_parallel_refresh(
3991 &self,
3992 experts: &ResidentExpertParallel,
3993 plan: &PreparedStepGroupedExpertParallelGate,
3994 tokens: usize,
3995 selected: &[usize],
3996 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
3997 {
3998 validate_ep_residency(&self.ranks, experts)?;
3999 if plan.rank_inputs.len() != self.ranks.len()
4000 || plan.owners.len() != self.ranks.len()
4001 || plan.input_width != experts.input_width
4002 || plan.expert_width != experts.expert_width
4003 || tokens > plan.max_tokens
4004 {
4005 return Err(format!(
4006 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4007 input={}/{} expert={}/{} tokens={}/{}",
4008 plan.rank_inputs.len(),
4009 self.ranks.len(),
4010 plan.owners.len(),
4011 self.ranks.len(),
4012 plan.input_width,
4013 experts.input_width,
4014 plan.expert_width,
4015 experts.expert_width,
4016 tokens,
4017 plan.max_tokens,
4018 )
4019 .into());
4020 }
4021 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4022 if pairs > plan.max_pairs {
4023 return Err(format!(
4024 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4025 plan.max_pairs
4026 )
4027 .into());
4028 }
4029 let next_generation = plan
4030 .generation
4031 .checked_add(1)
4032 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4033 let owner_routes = partition_expert_owner_routes(
4034 experts.expert_count,
4035 self.ranks.len(),
4036 tokens,
4037 STEP_GROUPED_FP8_TOP_K,
4038 selected,
4039 )?;
4040 let mut schedules = Vec::with_capacity(self.ranks.len());
4041 for routes in owner_routes {
4042 if routes.selected.is_empty() {
4043 schedules.push(None);
4044 continue;
4045 }
4046 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4047 let local_pairs = routes.selected.len();
4048 let route_csr = ExpertCsr::from_pair_rows(
4049 local_experts,
4050 tokens,
4051 &routes.selected,
4052 &routes.token_rows,
4053 )?;
4054 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4055 let down_csr = ExpertCsr::from_pair_rows(
4056 local_experts,
4057 local_pairs,
4058 &routes.selected,
4059 &down_rows,
4060 )?;
4061 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4062 global_pairs: routes.global_pairs,
4063 route_csr,
4064 down_csr,
4065 }));
4066 }
4067 Ok((pairs, next_generation, schedules))
4068 }
4069
4070 fn commit_step_grouped_expert_parallel_refresh(
4071 &self,
4072 plan: &mut PreparedStepGroupedExpertParallelGate,
4073 tokens: usize,
4074 pairs: usize,
4075 next_generation: u64,
4076 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4077 ) -> Result<(), Box<dyn std::error::Error>> {
4078 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4079 let engine = &self.ranks[owner.rank];
4080 let _main = engine.gpu.enter_main()?;
4081 if let Some(schedule) = schedule {
4082 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4083 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4084 owner.global_pairs = schedule.global_pairs;
4085 } else {
4086 owner.route_csr.clear();
4087 owner.down_csr.clear();
4088 owner.global_pairs.clear();
4089 }
4090 }
4091 plan.tokens = tokens;
4092 plan.pairs = pairs;
4093 plan.generation = next_generation;
4094 plan.ready = true;
4095 Ok(())
4096 }
4097
4098 pub fn refresh_step_grouped_expert_parallel_gate(
4099 &self,
4100 experts: &ResidentExpertParallel,
4101 plan: &mut PreparedStepGroupedExpertParallelGate,
4102 input: &[f32],
4103 tokens: usize,
4104 selected: &[usize],
4105 ) -> Result<(), Box<dyn std::error::Error>> {
4106 validate_activations(input, tokens, experts.input_width)?;
4107 let (pairs, next_generation, schedules) =
4108 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4109
4110 plan.ready = false;
4111 plan.executed_generation = None;
4112 {
4113 let root = &self.ranks[0];
4114 let _main = root.gpu.enter_main()?;
4115 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4116 root.stream().memcpy_htod(input, &mut destination)?;
4117 root.stream().synchronize()?;
4118 }
4119 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4120 let root_input = &root_inputs[0];
4121 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4122 let engine = &self.ranks[rank + 1];
4123 let _main = engine.gpu.enter_main()?;
4124 let mut destination = peer_input.slice_mut(0..input.len());
4125 engine
4126 .stream()
4127 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4128 }
4129 self.commit_step_grouped_expert_parallel_refresh(
4130 plan,
4131 tokens,
4132 pairs,
4133 next_generation,
4134 schedules,
4135 )
4136 }
4137
4138 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4143 &self,
4144 experts: &ResidentExpertParallel,
4145 plan: &mut PreparedStepGroupedExpertParallelGate,
4146 input: &CudaSlice<f32>,
4147 tokens: usize,
4148 selected: &[usize],
4149 ) -> Result<(), Box<dyn std::error::Error>> {
4150 let input_values = tokens
4151 .checked_mul(experts.input_width)
4152 .ok_or("Step owner-grouped FP8 input size overflow")?;
4153 let root = self
4154 .ranks
4155 .first()
4156 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4157 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4158 return Err(format!(
4159 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4160 device {}",
4161 input.len(),
4162 input.ordinal(),
4163 input_values,
4164 root.ctx().ordinal(),
4165 )
4166 .into());
4167 }
4168 let (pairs, next_generation, schedules) =
4169 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4170
4171 plan.ready = false;
4172 plan.executed_generation = None;
4173 {
4174 let _main = root.gpu.enter_main()?;
4175 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4176 root.stream()
4177 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4178 root.stream().synchronize()?;
4179 }
4180 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4181 let root_input = &root_inputs[0];
4182 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4183 let engine = &self.ranks[rank + 1];
4184 let _main = engine.gpu.enter_main()?;
4185 let mut destination = peer_input.slice_mut(0..input_values);
4186 engine
4187 .stream()
4188 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4189 }
4190 self.commit_step_grouped_expert_parallel_refresh(
4191 plan,
4192 tokens,
4193 pairs,
4194 next_generation,
4195 schedules,
4196 )
4197 }
4198
4199 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4204 &self,
4205 experts: &ResidentExpertParallel,
4206 plan: &mut PreparedStepGroupedExpertParallelGate,
4207 input: &ResidentReplicatedDeviceRows,
4208 ) -> Result<(), Box<dyn std::error::Error>> {
4209 validate_ep_residency(&self.ranks, experts)?;
4210 validate_replicated_device_rows(&self.ranks, input)?;
4211 if !plan.ready
4212 || input.tokens != plan.tokens
4213 || input.width != plan.input_width
4214 || input.tokens > plan.max_tokens
4215 || plan.rank_inputs.len() != self.ranks.len()
4216 || plan.owners.len() != self.ranks.len()
4217 || plan.input_width != experts.input_width
4218 || plan.expert_width != experts.expert_width
4219 {
4220 return Err("Step owner-grouped replicated input geometry changed".into());
4221 }
4222 let values = input
4223 .tokens
4224 .checked_mul(input.width)
4225 .ok_or("Step owner-grouped replicated input size overflow")?;
4226 let next_generation = plan
4227 .generation
4228 .checked_add(1)
4229 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4230 plan.ready = false;
4231 plan.executed_generation = None;
4232 for (rank, engine) in self.ranks.iter().enumerate() {
4233 let _main = engine.gpu.enter_main()?;
4234 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4235 engine
4236 .stream()
4237 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4238 }
4239 plan.generation = next_generation;
4240 plan.ready = true;
4241 Ok(())
4242 }
4243
4244 pub fn execute_step_grouped_expert_parallel_gate(
4245 &self,
4246 experts: &ResidentExpertParallel,
4247 plan: &mut PreparedStepGroupedExpertParallelGate,
4248 ) -> Result<(), Box<dyn std::error::Error>> {
4249 validate_ep_residency(&self.ranks, experts)?;
4250 if !plan.ready
4251 || plan.rank_inputs.len() != self.ranks.len()
4252 || plan.owners.len() != self.ranks.len()
4253 || plan.input_width != experts.input_width
4254 || plan.expert_width != experts.expert_width
4255 {
4256 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4257 }
4258 plan.executed_generation = None;
4259
4260 for owner in &mut plan.owners {
4261 if owner.global_pairs.is_empty() {
4262 continue;
4263 }
4264 let engine = &self.ranks[owner.rank];
4265 let bank = &experts.ranks[owner.rank];
4266 let _main = engine.gpu.enter_main()?;
4267 let local_pairs = owner.global_pairs.len();
4268 owner.gate_workspace.quantize_for_shape(
4269 engine,
4270 &plan.rank_inputs[owner.rank],
4271 plan.tokens,
4272 local_pairs,
4273 )?;
4274 owner.gate_workspace.project(
4275 engine,
4276 &bank.gate.codes,
4277 &bank.gate.scales,
4278 &owner.route_csr,
4279 bank.gate.code_stride,
4280 bank.gate.scale_stride,
4281 1.0,
4282 )?;
4283 owner.up_workspace.quantize_for_shape(
4284 engine,
4285 &plan.rank_inputs[owner.rank],
4286 plan.tokens,
4287 local_pairs,
4288 )?;
4289 owner.up_workspace.project(
4290 engine,
4291 &bank.up.codes,
4292 &bank.up.scales,
4293 &owner.route_csr,
4294 bank.up.code_stride,
4295 bank.up.scale_stride,
4296 1.0,
4297 )?;
4298 }
4299 for owner in &mut plan.owners {
4300 if owner.global_pairs.is_empty() {
4301 continue;
4302 }
4303 let engine = &self.ranks[owner.rank];
4304 let _main = engine.gpu.enter_main()?;
4305 let values = owner.global_pairs.len() * plan.expert_width;
4306 if let Some(limit) = plan.activation_limit {
4307 engine.silu_clamped_mul_host_expf(
4308 owner.gate_workspace.output(),
4309 owner.up_workspace.output(),
4310 limit,
4311 &mut owner.activation,
4312 values,
4313 )?;
4314 } else {
4315 engine.silu_mul_host_expf(
4316 owner.gate_workspace.output(),
4317 owner.up_workspace.output(),
4318 &mut owner.activation,
4319 values,
4320 )?;
4321 }
4322 }
4323 for owner in &mut plan.owners {
4324 if owner.global_pairs.is_empty() {
4325 continue;
4326 }
4327 let engine = &self.ranks[owner.rank];
4328 let bank = &experts.ranks[owner.rank];
4329 let _main = engine.gpu.enter_main()?;
4330 let local_pairs = owner.global_pairs.len();
4331 owner.down_workspace.quantize_for_shape(
4332 engine,
4333 &owner.activation,
4334 local_pairs,
4335 local_pairs,
4336 )?;
4337 owner.down_workspace.project(
4338 engine,
4339 &bank.down.codes,
4340 &bank.down.scales,
4341 &owner.down_csr,
4342 bank.down.code_stride,
4343 bank.down.scale_stride,
4344 1.0,
4345 )?;
4346 }
4347 plan.executed_generation = Some(plan.generation);
4348 Ok(())
4349 }
4350
4351 pub fn collect_step_grouped_expert_parallel_gate(
4352 &self,
4353 plan: &PreparedStepGroupedExpertParallelGate,
4354 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4355 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4356 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4357 }
4358 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4359 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4360 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4361 for owner in &plan.owners {
4362 if owner.global_pairs.is_empty() {
4363 continue;
4364 }
4365 let engine = &self.ranks[owner.rank];
4366 let _main = engine.gpu.enter_main()?;
4367 let owner_gate = engine.dtoh_view(
4368 &owner
4369 .gate_workspace
4370 .output()
4371 .slice(0..owner.gate_workspace.output_len()),
4372 )?;
4373 let owner_up = engine.dtoh_view(
4374 &owner
4375 .up_workspace
4376 .output()
4377 .slice(0..owner.up_workspace.output_len()),
4378 )?;
4379 let owner_down = engine.dtoh_view(
4380 &owner
4381 .down_workspace
4382 .output()
4383 .slice(0..owner.down_workspace.output_len()),
4384 )?;
4385 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4386 let local_expert = local_pair * plan.expert_width;
4387 let global_expert = global_pair * plan.expert_width;
4388 gate[global_expert..global_expert + plan.expert_width]
4389 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4390 up[global_expert..global_expert + plan.expert_width]
4391 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4392
4393 let local_hidden = local_pair * plan.input_width;
4394 let global_hidden = global_pair * plan.input_width;
4395 down[global_hidden..global_hidden + plan.input_width]
4396 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4397 }
4398 }
4399 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4400 }
4401
4402 pub fn run_step_grouped_expert_parallel_gate(
4403 &self,
4404 experts: &ResidentExpertParallel,
4405 plan: &mut PreparedStepGroupedExpertParallelGate,
4406 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4407 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4408 self.collect_step_grouped_expert_parallel_gate(plan)
4409 }
4410
4411 pub fn prepare_step_grouped_expert_parallel_combine(
4412 &self,
4413 plan: &PreparedStepGroupedExpertParallelGate,
4414 route_weights: &[f32],
4415 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4416 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4417 return Err(
4418 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4419 );
4420 }
4421 let owner_pairs = plan
4422 .owners
4423 .iter()
4424 .map(|owner| owner.global_pairs.as_slice())
4425 .collect::<Vec<_>>();
4426 let shape = validate_weighted_route_combine(
4427 plan.input_width,
4428 STEP_GROUPED_FP8_TOP_K,
4429 plan.max_tokens,
4430 plan.tokens,
4431 &owner_pairs,
4432 route_weights,
4433 )?;
4434 if shape.max_pairs != plan.max_pairs {
4435 return Err(format!(
4436 "Step owner-grouped combine capacity {} != projection capacity {}",
4437 shape.max_pairs, plan.max_pairs
4438 )
4439 .into());
4440 }
4441 let root = self
4442 .ranks
4443 .first()
4444 .ok_or("Step owner-grouped combine has no root rank")?;
4445 let slot_values = shape
4446 .max_pairs
4447 .checked_mul(plan.input_width)
4448 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4449 let output_values = plan
4450 .max_tokens
4451 .checked_mul(plan.input_width)
4452 .ok_or("Step owner-grouped combine output capacity overflow")?;
4453 let (root_device, owners, peer_staging, slots, weights, output) = {
4454 let _main = root.gpu.enter_main()?;
4455 let mut owners = Vec::with_capacity(plan.owners.len());
4456 for _ in &plan.owners {
4457 owners.push(PreparedPeerWeightedRouteOwner {
4458 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4459 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4460 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4461 active_pairs: 0,
4462 });
4463 }
4464 (
4465 root.ctx().ordinal(),
4466 owners,
4467 root.uninit(slot_values)?,
4468 root.uninit(slot_values)?,
4469 root.uninit(shape.max_pairs)?,
4470 root.uninit(output_values)?,
4471 )
4472 };
4473 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4474 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4475 for engine in self.ranks.iter().skip(1) {
4476 let _main = engine.gpu.enter_main()?;
4477 peer_devices.push(engine.ctx().ordinal());
4478 peer_outputs.push(engine.uninit(output_values)?);
4479 }
4480 let mut combine = PreparedPeerWeightedRouteCombine {
4481 root_device,
4482 owners,
4483 peer_staging,
4484 slots,
4485 weights,
4486 output,
4487 peer_devices,
4488 peer_outputs,
4489 width: plan.input_width,
4490 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4491 max_tokens: plan.max_tokens,
4492 max_pairs: shape.max_pairs,
4493 tokens: 0,
4494 pairs: 0,
4495 projection_generation: 0,
4496 output_generation: None,
4497 broadcast_generation: None,
4498 ready: false,
4499 };
4500 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4501 Ok(combine)
4502 }
4503
4504 pub fn refresh_step_grouped_expert_parallel_combine(
4505 &self,
4506 plan: &PreparedStepGroupedExpertParallelGate,
4507 combine: &mut PreparedPeerWeightedRouteCombine,
4508 route_weights: &[f32],
4509 ) -> Result<(), Box<dyn std::error::Error>> {
4510 let output_capacity = combine
4511 .max_tokens
4512 .checked_mul(combine.width)
4513 .ok_or("Step owner-grouped combine output capacity overflow")?;
4514 if !plan.ready
4515 || combine.owners.len() != plan.owners.len()
4516 || combine.peer_devices.len() + 1 != self.ranks.len()
4517 || combine.peer_outputs.len() + 1 != self.ranks.len()
4518 || combine.width != plan.input_width
4519 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4520 || combine.max_tokens != plan.max_tokens
4521 || combine.max_pairs != plan.max_pairs
4522 || combine.output.len() < output_capacity
4523 || combine
4524 .peer_outputs
4525 .iter()
4526 .any(|output| output.len() < output_capacity)
4527 {
4528 return Err("Step owner-grouped combine/projection geometry changed".into());
4529 }
4530 if self
4531 .ranks
4532 .iter()
4533 .skip(1)
4534 .zip(&combine.peer_devices)
4535 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4536 {
4537 return Err("Step owner-grouped combine peer devices changed".into());
4538 }
4539 let owner_pairs = plan
4540 .owners
4541 .iter()
4542 .map(|owner| owner.global_pairs.as_slice())
4543 .collect::<Vec<_>>();
4544 let shape = validate_weighted_route_combine(
4545 combine.width,
4546 combine.experts_per_token,
4547 combine.max_tokens,
4548 plan.tokens,
4549 &owner_pairs,
4550 route_weights,
4551 )?;
4552 if shape.max_pairs != combine.max_pairs {
4553 return Err("Step owner-grouped combine capacity changed during refresh".into());
4554 }
4555 let metadata = owner_pairs
4556 .iter()
4557 .map(|pairs| {
4558 let token_rows = pairs
4559 .iter()
4560 .map(|&pair| (pair / combine.experts_per_token) as i32)
4561 .collect::<Vec<_>>();
4562 let slots = pairs
4563 .iter()
4564 .map(|&pair| (pair % combine.experts_per_token) as i32)
4565 .collect::<Vec<_>>();
4566 let weights = pairs
4567 .iter()
4568 .map(|&pair| route_weights[pair])
4569 .collect::<Vec<_>>();
4570 (token_rows, slots, weights)
4571 })
4572 .collect::<Vec<_>>();
4573
4574 combine.ready = false;
4575 combine.output_generation = None;
4576 combine.broadcast_generation = None;
4577 let root = self
4578 .ranks
4579 .first()
4580 .ok_or("Step owner-grouped combine has no root rank")?;
4581 let _main = root.gpu.enter_main()?;
4582 if root.ctx().ordinal() != combine.root_device {
4583 return Err(format!(
4584 "Step owner-grouped combine root device changed {} != {}",
4585 root.ctx().ordinal(),
4586 combine.root_device
4587 )
4588 .into());
4589 }
4590 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4591 if token_rows.is_empty() {
4592 owner.active_pairs = 0;
4593 continue;
4594 }
4595 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4596 root.htod_i32_into(&mut owner.slots, &slots)?;
4597 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4598 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4599 owner.active_pairs = token_rows.len();
4600 }
4601 combine.tokens = plan.tokens;
4602 combine.pairs = shape.pairs;
4603 combine.projection_generation = plan.generation;
4604 combine.ready = true;
4605 Ok(())
4606 }
4607
4608 pub fn execute_step_grouped_expert_parallel_combine(
4609 &self,
4610 plan: &PreparedStepGroupedExpertParallelGate,
4611 combine: &mut PreparedPeerWeightedRouteCombine,
4612 ) -> Result<(), Box<dyn std::error::Error>> {
4613 if !plan.ready
4614 || plan.executed_generation != Some(plan.generation)
4615 || !combine.ready
4616 || combine.tokens != plan.tokens
4617 || combine.pairs != plan.pairs
4618 || combine.width != plan.input_width
4619 || combine.owners.len() != plan.owners.len()
4620 || combine.projection_generation != plan.generation
4621 {
4622 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4623 }
4624 combine.output_generation = None;
4625 combine.broadcast_generation = None;
4626 for owner in &plan.owners {
4627 if owner.rank == 0 || owner.global_pairs.is_empty() {
4628 continue;
4629 }
4630 let engine = &self.ranks[owner.rank];
4631 let _main = engine.gpu.enter_main()?;
4632 engine.stream().synchronize()?;
4633 }
4634 let root = self
4635 .ranks
4636 .first()
4637 .ok_or("Step owner-grouped combine has no root rank")?;
4638 let _main = root.gpu.enter_main()?;
4639 if root.ctx().ordinal() != combine.root_device {
4640 return Err("Step owner-grouped combine is not resident on the root device".into());
4641 }
4642 for (index, owner) in plan.owners.iter().enumerate() {
4643 let metadata = &combine.owners[index];
4644 if owner.global_pairs.len() != metadata.active_pairs {
4645 return Err(format!(
4646 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4647 owner.global_pairs.len(),
4648 metadata.active_pairs
4649 )
4650 .into());
4651 }
4652 if metadata.active_pairs == 0 {
4653 continue;
4654 }
4655 let values = metadata
4656 .active_pairs
4657 .checked_mul(combine.width)
4658 .ok_or("Step owner-grouped combine peer value count overflow")?;
4659 if owner.rank == 0 {
4660 root.scatter_slot(
4661 owner.down_workspace.output(),
4662 &metadata.token_rows,
4663 &metadata.slots,
4664 &metadata.weights,
4665 &mut combine.slots,
4666 &mut combine.weights,
4667 combine.width,
4668 combine.experts_per_token,
4669 metadata.active_pairs,
4670 )?;
4671 } else {
4672 let source = owner.down_workspace.output().slice(0..values);
4673 let mut destination = combine.peer_staging.slice_mut(0..values);
4674 root.stream().memcpy_dtod(&source, &mut destination)?;
4675 root.scatter_slot(
4676 &combine.peer_staging,
4677 &metadata.token_rows,
4678 &metadata.slots,
4679 &metadata.weights,
4680 &mut combine.slots,
4681 &mut combine.weights,
4682 combine.width,
4683 combine.experts_per_token,
4684 metadata.active_pairs,
4685 )?;
4686 }
4687 }
4688 root.reduce_slots_host(
4689 &combine.slots,
4690 &combine.weights,
4691 &mut combine.output,
4692 combine.width,
4693 combine.experts_per_token,
4694 combine.tokens,
4695 )?;
4696 combine.output_generation = Some(plan.generation);
4697 Ok(())
4698 }
4699
4700 pub fn collect_step_grouped_expert_parallel_combine(
4701 &self,
4702 plan: &PreparedStepGroupedExpertParallelGate,
4703 combine: &PreparedPeerWeightedRouteCombine,
4704 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4705 if !plan.ready
4706 || combine.output_generation != Some(plan.generation)
4707 || combine.projection_generation != plan.generation
4708 {
4709 return Err("Step owner-grouped combine output is stale or has not executed".into());
4710 }
4711 let root = self
4712 .ranks
4713 .first()
4714 .ok_or("Step owner-grouped combine has no root rank")?;
4715 let _main = root.gpu.enter_main()?;
4716 if root.ctx().ordinal() != combine.root_device {
4717 return Err("Step owner-grouped combine is not resident on the root device".into());
4718 }
4719 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4720 }
4721
4722 pub fn copy_step_grouped_expert_parallel_combine_root(
4727 &self,
4728 plan: &PreparedStepGroupedExpertParallelGate,
4729 combine: &PreparedPeerWeightedRouteCombine,
4730 destination: &Engine,
4731 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4732 if !plan.ready
4733 || combine.output_generation != Some(plan.generation)
4734 || combine.projection_generation != plan.generation
4735 {
4736 return Err("Step owner-grouped combine output is stale or has not executed".into());
4737 }
4738 let root = self
4739 .ranks
4740 .first()
4741 .ok_or("Step owner-grouped combine has no root rank")?;
4742 if root.ctx().ordinal() != combine.root_device
4743 || destination.ctx().ordinal() != combine.root_device
4744 {
4745 return Err(format!(
4746 "Step owner-grouped combine root/destination devices {}/{} != {}",
4747 root.ctx().ordinal(),
4748 destination.ctx().ordinal(),
4749 combine.root_device,
4750 )
4751 .into());
4752 }
4753 let values = combine
4754 .tokens
4755 .checked_mul(combine.width)
4756 .ok_or("Step owner-grouped combine copy size overflow")?;
4757 {
4758 let _main = root.gpu.enter_main()?;
4759 root.stream().synchronize()?;
4760 }
4761 let _main = destination.gpu.enter_main()?;
4762 let mut output = destination.uninit(values)?;
4763 destination
4764 .stream()
4765 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4766 Ok(output)
4767 }
4768
4769 pub fn broadcast_step_grouped_expert_parallel_combine(
4770 &self,
4771 plan: &PreparedStepGroupedExpertParallelGate,
4772 combine: &mut PreparedPeerWeightedRouteCombine,
4773 ) -> Result<(), Box<dyn std::error::Error>> {
4774 if !plan.ready
4775 || combine.output_generation != Some(plan.generation)
4776 || combine.projection_generation != plan.generation
4777 || combine.peer_devices.len() + 1 != self.ranks.len()
4778 || combine.peer_outputs.len() + 1 != self.ranks.len()
4779 {
4780 return Err("Step owner-grouped combine output cannot be broadcast".into());
4781 }
4782 combine.broadcast_generation = None;
4783 let values = combine
4784 .tokens
4785 .checked_mul(combine.width)
4786 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4787 {
4788 let root = self
4789 .ranks
4790 .first()
4791 .ok_or("Step owner-grouped combine has no root rank")?;
4792 let _main = root.gpu.enter_main()?;
4793 if root.ctx().ordinal() != combine.root_device {
4794 return Err("Step owner-grouped combine root device changed".into());
4795 }
4796 root.stream().synchronize()?;
4797 }
4798 let source = &combine.output;
4799 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4800 let engine = &self.ranks[index + 1];
4801 let _main = engine.gpu.enter_main()?;
4802 if engine.ctx().ordinal() != combine.peer_devices[index] {
4803 return Err(format!(
4804 "Step owner-grouped combine peer {} device changed",
4805 index + 1
4806 )
4807 .into());
4808 }
4809 let mut destination = destination_buffer.slice_mut(0..values);
4810 engine
4811 .stream()
4812 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4813 }
4814 combine.broadcast_generation = Some(plan.generation);
4815 Ok(())
4816 }
4817
4818 pub fn collect_step_grouped_expert_parallel_broadcast(
4819 &self,
4820 plan: &PreparedStepGroupedExpertParallelGate,
4821 combine: &PreparedPeerWeightedRouteCombine,
4822 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4823 if !plan.ready
4824 || combine.output_generation != Some(plan.generation)
4825 || combine.broadcast_generation != Some(plan.generation)
4826 || combine.peer_outputs.len() + 1 != self.ranks.len()
4827 {
4828 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4829 }
4830 let values = combine
4831 .tokens
4832 .checked_mul(combine.width)
4833 .ok_or("Step owner-grouped combine collection size overflow")?;
4834 let mut outputs = Vec::with_capacity(self.ranks.len());
4835 {
4836 let root = &self.ranks[0];
4837 let _main = root.gpu.enter_main()?;
4838 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4839 }
4840 for (index, output) in combine.peer_outputs.iter().enumerate() {
4841 let engine = &self.ranks[index + 1];
4842 let _main = engine.gpu.enter_main()?;
4843 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4844 }
4845 Ok(outputs)
4846 }
4847
4848 pub fn finish_step_grouped_expert_parallel_layer(
4850 &self,
4851 plan: &PreparedStepGroupedExpertParallelGate,
4852 combine: &PreparedPeerWeightedRouteCombine,
4853 shared: &ResidentReplicatedDeviceRows,
4854 residual: &ResidentReplicatedDeviceRows,
4855 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4856 validate_replicated_device_rows(&self.ranks, shared)?;
4857 validate_replicated_device_rows(&self.ranks, residual)?;
4858 if !plan.ready
4859 || plan.executed_generation != Some(plan.generation)
4860 || combine.output_generation != Some(plan.generation)
4861 || combine.broadcast_generation != Some(plan.generation)
4862 || combine.projection_generation != plan.generation
4863 || combine.peer_outputs.len() + 1 != self.ranks.len()
4864 || shared.tokens != combine.tokens
4865 || residual.tokens != combine.tokens
4866 || shared.width != combine.width
4867 || residual.width != combine.width
4868 {
4869 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4870 }
4871 let values = combine
4872 .tokens
4873 .checked_mul(combine.width)
4874 .ok_or("Step full-layer output size overflow")?;
4875 let mut ranks = Vec::with_capacity(self.ranks.len());
4876 for rank in 0..self.ranks.len() {
4877 let engine = &self.ranks[rank];
4878 let _main = engine.gpu.enter_main()?;
4879 let routed = if rank == 0 {
4880 &combine.output
4881 } else {
4882 &combine.peer_outputs[rank - 1]
4883 };
4884 let mut ffn = engine.uninit(values)?;
4885 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4886 let mut output = engine.uninit(values)?;
4887 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4888 ranks.push(output);
4889 }
4890 Ok(ResidentReplicatedDeviceRows {
4891 ranks,
4892 tokens: combine.tokens,
4893 width: combine.width,
4894 })
4895 }
4896
4897 pub fn run_step_grouped_expert_parallel_combine(
4898 &self,
4899 plan: &PreparedStepGroupedExpertParallelGate,
4900 combine: &mut PreparedPeerWeightedRouteCombine,
4901 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4902 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
4903 self.collect_step_grouped_expert_parallel_combine(plan, combine)
4904 }
4905
4906 pub fn upload_tensor_parallel(
4907 &self,
4908 gate: E4m3ExpertBank<'_>,
4909 up: E4m3ExpertBank<'_>,
4910 down: E4m3ExpertBank<'_>,
4911 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
4912 gate.validate()?;
4913 up.validate()?;
4914 down.validate()?;
4915 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4916 return Err("TP gate/up/down expert counts differ".into());
4917 }
4918 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4919 return Err("TP gate/up dimensions differ".into());
4920 }
4921 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4922 return Err(format!(
4923 "TP down {}x{} does not invert gate/up {}x{}",
4924 down.out_features, down.in_features, gate.out_features, gate.in_features
4925 )
4926 .into());
4927 }
4928 let tp = self.ranks.len();
4929 validate_column_bank_shape(gate, tp)?;
4930 validate_column_bank_shape(up, tp)?;
4931 validate_row_bank_shape(down, tp)?;
4932
4933 let mut gate_ranks = Vec::with_capacity(tp);
4934 let mut up_ranks = Vec::with_capacity(tp);
4935 let mut down_ranks = Vec::with_capacity(tp);
4936 for (rank, engine) in self.ranks.iter().enumerate() {
4937 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
4938 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
4939 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
4940 }
4941 Ok(ResidentTensorParallel {
4942 bank: ResidentTpExpertBank {
4943 gate: gate_ranks,
4944 up: up_ranks,
4945 down: down_ranks,
4946 expert_count: gate.expert_count,
4947 input_width: gate.in_features,
4948 expert_width: gate.out_features,
4949 },
4950 })
4951 }
4952
4953 pub fn run_tensor_parallel_routes(
4954 &self,
4955 experts: &ResidentTensorParallel,
4956 input: &[f32],
4957 tokens: usize,
4958 selected: &[usize],
4959 route_weights: &[f32],
4960 experts_per_token: usize,
4961 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4962 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
4963 validate_activations(input, tokens, experts.bank.input_width)?;
4964 let pairs = tokens
4965 .checked_mul(experts_per_token)
4966 .ok_or("TP route count overflow")?;
4967 if selected.len() != pairs || route_weights.len() != pairs {
4968 return Err(format!(
4969 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
4970 {experts_per_token} ({pairs})",
4971 selected.len(),
4972 route_weights.len(),
4973 )
4974 .into());
4975 }
4976 if !route_weights.iter().all(|weight| weight.is_finite()) {
4977 return Err("TP route weights contain a non-finite value".into());
4978 }
4979
4980 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
4981 for token in 0..tokens {
4982 let input_row =
4983 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
4984 for slot in 0..experts_per_token {
4985 let pair = token * experts_per_token + slot;
4986 let expert = selected[pair];
4987 if expert >= experts.bank.expert_count {
4988 return Err(format!(
4989 "TP selected expert {expert} outside 0..{}",
4990 experts.bank.expert_count
4991 )
4992 .into());
4993 }
4994 let down = if self.native_p2p {
4995 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
4996 } else {
4997 let gate =
4998 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
4999 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5000 let activated: Vec<f32> = gate
5001 .iter()
5002 .zip(&up)
5003 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5004 .collect();
5005 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5006 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5007 };
5008 let weight = route_weights[pair];
5009 for (sum, value) in output
5010 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5011 .iter_mut()
5012 .zip(down)
5013 {
5014 *sum += weight * value;
5015 }
5016 }
5017 }
5018 Ok(output)
5019 }
5020
5021 fn run_column_bank_expert(
5022 &self,
5023 ranks: &[ResidentE4m3ExpertBankRank],
5024 expert: usize,
5025 input: &[f32],
5026 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5027 let local_out = ranks
5028 .first()
5029 .ok_or("TP column bank has no ranks")?
5030 .out_features;
5031 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5032 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5033 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5034 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5035 }
5036 Ok(gathered)
5037 }
5038
5039 fn run_row_bank_expert(
5040 &self,
5041 ranks: &[ResidentE4m3ExpertBankRank],
5042 expert: usize,
5043 input: &[f32],
5044 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5045 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5046 if input.len() != local_in * ranks.len() {
5047 return Err(format!(
5048 "TP row input {} != {} ranks x {local_in}",
5049 input.len(),
5050 ranks.len()
5051 )
5052 .into());
5053 }
5054 let out_features = ranks[0].out_features;
5055 let mut reduced = vec![0.0f32; out_features];
5056 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5057 let blocks = bank
5058 .k_blocks
5059 .ok_or("TP row bank is not packed in native K-block order")?;
5060 if blocks * FP8_BLOCK != local_in {
5061 return Err(format!(
5062 "TP row bank has {blocks} blocks but local input width is {local_in}"
5063 )
5064 .into());
5065 }
5066 for block in 0..blocks {
5067 let global_start = rank * local_in + block * FP8_BLOCK;
5068 let partial = run_resident_bank_expert_block(
5069 engine,
5070 bank,
5071 expert,
5072 block,
5073 &input[global_start..global_start + FP8_BLOCK],
5074 )?;
5075 for (sum, value) in reduced.iter_mut().zip(partial) {
5076 *sum += value;
5077 }
5078 }
5079 }
5080 Ok(reduced)
5081 }
5082
5083 fn run_tensor_parallel_expert_native(
5084 &self,
5085 bank: &ResidentTpExpertBank,
5086 expert: usize,
5087 input: &[f32],
5088 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5089 if !self.native_p2p || self.ranks.len() < 2 {
5090 return Err("native TP expert execution requires at least two P2P ranks".into());
5091 }
5092 let local_out = bank
5093 .gate
5094 .first()
5095 .ok_or("native TP gate bank has no ranks")?
5096 .out_features;
5097 if local_out * self.ranks.len() != bank.expert_width {
5098 return Err(format!(
5099 "native TP gate shards {}x{local_out} != expert width {}",
5100 self.ranks.len(),
5101 bank.expert_width
5102 )
5103 .into());
5104 }
5105
5106 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5109 let root_input = {
5110 let root = &self.ranks[0];
5111 let _main = root.gpu.enter_main()?;
5112 root.htod(input)?
5113 };
5114 rank_inputs.push(root_input);
5115 for engine in &self.ranks[1..] {
5116 let peer_input = {
5117 let _main = engine.gpu.enter_main()?;
5118 let mut peer_input = engine.uninit(input.len())?;
5119 engine
5120 .stream()
5121 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5122 peer_input
5123 };
5124 rank_inputs.push(peer_input);
5125 }
5126
5127 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5128 let mut up_shards = Vec::with_capacity(self.ranks.len());
5129 for rank in 0..self.ranks.len() {
5130 gate_shards.push(run_resident_bank_expert_device(
5131 &self.ranks[rank],
5132 &bank.gate[rank],
5133 expert,
5134 &rank_inputs[rank],
5135 1,
5136 )?);
5137 up_shards.push(run_resident_bank_expert_device(
5138 &self.ranks[rank],
5139 &bank.up[rank],
5140 expert,
5141 &rank_inputs[rank],
5142 1,
5143 )?);
5144 }
5145
5146 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5150 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5151 let activated = gate
5152 .iter()
5153 .zip(&up)
5154 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5155 .collect::<Vec<_>>();
5156 debug_assert_eq!(activated.len(), bank.expert_width);
5157
5158 let root_activated = {
5159 let root = &self.ranks[0];
5160 let _main = root.gpu.enter_main()?;
5161 root.htod(&activated)?
5162 };
5163 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5164 for (rank, engine) in self.ranks.iter().enumerate() {
5165 let start = rank * local_out;
5166 let source = root_activated.slice(start..start + local_out);
5167 let local = {
5168 let _main = engine.gpu.enter_main()?;
5169 let mut local = engine.uninit(local_out)?;
5170 engine.stream().memcpy_dtod(&source, &mut local)?;
5171 local
5172 };
5173 rank_activated.push(local);
5174 }
5175
5176 let out_features = bank
5177 .down
5178 .first()
5179 .ok_or("native TP down bank has no ranks")?
5180 .out_features;
5181 let mut reduced = {
5182 let root = &self.ranks[0];
5183 let _main = root.gpu.enter_main()?;
5184 root.htod(&vec![0.0f32; out_features])?
5185 };
5186 let mut remote_partial_keepalive = Vec::new();
5187 for rank in 0..self.ranks.len() {
5188 let down = &bank.down[rank];
5189 let blocks = down
5190 .k_blocks
5191 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5192 if blocks * FP8_BLOCK != local_out {
5193 return Err(format!(
5194 "native TP rank {rank} has {blocks} blocks but local activation width is \
5195 {local_out}"
5196 )
5197 .into());
5198 }
5199 for block in 0..blocks {
5200 let start = block * FP8_BLOCK;
5201 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5202 let partial = run_resident_bank_expert_block_device(
5203 &self.ranks[rank],
5204 down,
5205 expert,
5206 block,
5207 &input_block,
5208 )?;
5209 let root_partial = if rank == 0 {
5210 partial
5211 } else {
5212 let root = &self.ranks[0];
5213 let _main = root.gpu.enter_main()?;
5214 let mut peer_partial = root.uninit(out_features)?;
5215 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5216 remote_partial_keepalive.push(partial);
5217 peer_partial
5218 };
5219 let next = {
5220 let root = &self.ranks[0];
5221 let _main = root.gpu.enter_main()?;
5222 let mut next = root.uninit(out_features)?;
5223 root.add(&reduced, &root_partial, &mut next, out_features)?;
5224 next
5225 };
5226 reduced = next;
5227 }
5228 }
5229 let output = {
5230 let root = &self.ranks[0];
5231 let _main = root.gpu.enter_main()?;
5232 root.dtoh(&reduced)?
5233 };
5234 drop(remote_partial_keepalive);
5235 Ok(output)
5236 }
5237
5238 pub fn gather_native_column_shards_device(
5240 &self,
5241 shards: &[CudaSlice<f32>],
5242 tokens: usize,
5243 local_out: usize,
5244 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5245 let shard_len = tokens
5246 .checked_mul(local_out)
5247 .ok_or("native TP gather shard size overflow")?;
5248 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5249 return Err("native TP gather shard geometry mismatch".into());
5250 }
5251 for engine in &self.ranks[1..] {
5255 let _main = engine.gpu.enter_main()?;
5256 engine.stream().synchronize()?;
5257 }
5258 let root = &self.ranks[0];
5259 let _main = root.gpu.enter_main()?;
5260 let global_out = shards
5261 .len()
5262 .checked_mul(local_out)
5263 .ok_or("native TP gather output width overflow")?;
5264 let gathered_len = tokens
5265 .checked_mul(global_out)
5266 .ok_or("native TP gather output size overflow")?;
5267 let mut gathered = root.uninit(gathered_len)?;
5268 if self.bulk_p2p {
5269 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5270 if shards.len() > 1 {
5271 let mut staging = root.uninit(shard_len)?;
5272 for (rank, shard) in shards.iter().enumerate().skip(1) {
5273 root.stream().memcpy_dtod(shard, &mut staging)?;
5274 root.place_rows_strided(
5275 &staging,
5276 &mut gathered,
5277 local_out,
5278 tokens,
5279 global_out,
5280 rank * local_out,
5281 )?;
5282 }
5283 }
5284 } else {
5285 for token in 0..tokens {
5286 for (rank, shard) in shards.iter().enumerate() {
5287 let source = shard.slice(token * local_out..(token + 1) * local_out);
5288 let start = token * global_out + rank * local_out;
5289 let mut destination = gathered.slice_mut(start..start + local_out);
5290 root.stream().memcpy_dtod(&source, &mut destination)?;
5291 }
5292 }
5293 }
5294 Ok(gathered)
5295 }
5296
5297 pub fn gather_native_column_shards(
5298 &self,
5299 shards: &[CudaSlice<f32>],
5300 tokens: usize,
5301 local_out: usize,
5302 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5303 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5304 let root = &self.ranks[0];
5305 let _main = root.gpu.enter_main()?;
5306 root.dtoh(&gathered)
5307 }
5308
5309 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5310 &self.decode_v2
5311 }
5312
5313 pub(crate) fn decode_v2_ensure(
5322 &self,
5323 e: &Engine,
5324 q_m: &ResidentBf16ColumnParallel,
5325 k_m: &ResidentBf16ColumnParallel,
5326 v_m: &ResidentBf16ColumnParallel,
5327 o_m: &ResidentStepBf16RowParallel,
5328 heads: usize,
5329 ) -> Result<usize, Box<dyn std::error::Error>> {
5330 if self.ranks.len() > 1 && !self.native_p2p {
5331 return Err("step TP decode v2 requires native P2P ranks".into());
5332 }
5333 let ranks = self.ranks.len();
5334 let fused_door = step_tp_qkv_fused_enabled()?;
5338 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5339 ResidentBf16Weight::F32(_) => true,
5340 ResidentBf16Weight::Bf16(_) => fused_door,
5341 };
5342 for matrix in [q_m, k_m, v_m] {
5343 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5344 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5345 return Err("step TP decode v2 QKV geometry mismatch".into());
5346 }
5347 for rank in &matrix.ranks {
5348 if !arm_ok(&rank.weight) {
5349 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5350 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5351 .into());
5352 }
5353 }
5354 }
5355 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5356 for blocks in &o_m.ranks {
5357 for block in blocks {
5358 if !arm_ok(&block.weight) {
5359 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5360 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5361 .into());
5362 }
5363 }
5364 }
5365 if v_m.out_features != k_m.out_features
5366 || o_m.in_features != q_m.out_features
5367 || heads == 0
5368 || heads % ranks != 0
5369 {
5370 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5371 }
5372 let local_q_dim = q_m.out_features / ranks;
5373 let local_kv_dim = k_m.out_features / ranks;
5374 let o_out = o_m.out_features;
5375 let o_block_cols = o_m.canonical_chunk_cols;
5376 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5377 if blocks_per_rank == 0
5378 || o_m
5379 .ranks
5380 .iter()
5381 .any(|blocks| blocks.len() != blocks_per_rank)
5382 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5383 {
5384 return Err("step TP decode v2 O canonical block grid mismatch".into());
5385 }
5386
5387 let mut guard = self
5388 .decode_v2
5389 .lock()
5390 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5391 if let Some(index) = guard.iter().position(|ws| {
5392 ws.local_q_dim == local_q_dim
5393 && ws.local_kv_dim == local_kv_dim
5394 && ws.heads == heads
5395 && ws.o_out == o_out
5396 && ws.o_block_cols == o_block_cols
5397 && ws.blocks_per_rank == blocks_per_rank
5398 && ws.e_device == e.ctx().ordinal()
5399 && ws.q.len() == ranks
5400 }) {
5401 return Ok(index);
5402 }
5403
5404 let mut q_raw = Vec::with_capacity(ranks);
5405 let mut k_raw = Vec::with_capacity(ranks);
5406 let mut v_raw = Vec::with_capacity(ranks);
5407 let mut q = Vec::with_capacity(ranks);
5408 let mut k = Vec::with_capacity(ranks);
5409 let mut pos = Vec::with_capacity(ranks);
5410 let mut gate = Vec::with_capacity(ranks);
5411 let mut attn_out = Vec::with_capacity(ranks);
5412 let mut gated = Vec::with_capacity(ranks);
5413 let mut fuse_ctr = Vec::with_capacity(ranks);
5414 let mut o_partials = Vec::with_capacity(ranks);
5415 let mut ev_rank = Vec::with_capacity(ranks);
5416 let direct_join = oproj_direct_on();
5417 for (rank, engine) in self.ranks.iter().enumerate() {
5418 let _main = engine.gpu.enter_main()?;
5419 q_raw.push(engine.uninit(local_q_dim)?);
5420 k_raw.push(engine.uninit(local_kv_dim)?);
5421 v_raw.push(engine.uninit(local_kv_dim)?);
5422 q.push(engine.uninit(local_q_dim)?);
5423 k.push(engine.uninit(local_kv_dim)?);
5424 pos.push(engine.htod_i32(&[0])?);
5425 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5426 gate.push(engine.uninit(heads / ranks)?);
5427 attn_out.push(engine.uninit(local_q_dim)?);
5428 gated.push(engine.uninit(local_q_dim)?);
5429 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5430 for _ in 0..blocks_per_rank {
5431 if direct_join && rank != 0 {
5434 let root = &self.ranks[0];
5435 let _root_main = root.gpu.enter_main()?;
5436 rank_partials.push(root.uninit(o_out)?);
5437 } else {
5438 rank_partials.push(engine.uninit(o_out)?);
5439 }
5440 }
5441 o_partials.push(rank_partials);
5442 ev_rank.push(engine.ctx().new_event(None)?);
5443 }
5444 let root = &self.ranks[0];
5445 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5446 let _main = root.gpu.enter_main()?;
5447 (
5448 root.uninit(o_out)?,
5449 root.uninit(o_out)?,
5450 root.uninit(o_out)?,
5451 root.htod(&vec![0.0f32; o_out])?,
5452 root.uninit(ranks * local_kv_dim)?,
5453 root.uninit(ranks * local_kv_dim)?,
5454 root.ctx().new_event(None)?,
5455 root.ctx().new_event(None)?,
5456 )
5457 };
5458 let (gate_e, ev_entry) = {
5459 let _main = e.gpu.enter_main()?;
5460 (e.uninit(heads)?, e.ctx().new_event(None)?)
5461 };
5462 let raw_attn_in = Vec::new();
5463 let raw_pos = Vec::new();
5464 guard.push(StepTpDecodeV2Ws {
5465 tcol_q: Vec::new(),
5466 tcol_k: Vec::new(),
5467 tcol_v: Vec::new(),
5468 tcol_g: Vec::new(),
5469 tcol_in: Vec::new(),
5470 tcol_cap: 0,
5471 w8_aq: Vec::new(),
5472 w8_ad: Vec::new(),
5473 w8_in: 0,
5474 w8o_aq: Vec::new(),
5475 w8o_ad: Vec::new(),
5476 w8o_in: 0,
5477 fa2_q: Vec::new(),
5478 fa2_gate: Vec::new(),
5479 fa2_gated: Vec::new(),
5480 fa2_cap: 0,
5481 rope_k_t: Vec::new(),
5482 rope_ctr_t: Vec::new(),
5483 rope_pos_t: Vec::new(),
5484 rows_tabs: Vec::new(),
5485 tcol_gated: Vec::new(),
5486 tcol_opart: Vec::new(),
5487 tcol_opeer: None,
5488 tcol_omix: None,
5489 tcol_ocap: 0,
5490 q_raw,
5491 k_raw,
5492 v_raw,
5493 q,
5494 k,
5495 pos,
5496 fuse_ctr,
5497 gate,
5498 attn_out,
5499 gated,
5500 o_partials,
5501 ev_rank,
5502 peer_partial,
5503 reduce_a,
5504 reduce_b,
5505 zeros,
5506 k_shadow,
5507 v_shadow,
5508 ev_refresh,
5509 ev_oproj,
5510 gate_e,
5511 attn_in: Vec::new(),
5512 h_stage: None,
5513 pos_stage: None,
5514 raw_h_stage: 0,
5515 raw_pos_stage: 0,
5516 raw_attn_in,
5517 raw_pos,
5518 raw_o_partial1: 0,
5519 raw_peer_partial: 0,
5520 raw_k1: 0,
5521 raw_v1: 0,
5522 raw_k_shadow: 0,
5523 raw_v_shadow: 0,
5524 raw_mixed_stage_e: 0,
5525 raw_reduce_a: 0,
5526 raw_shadow_stage_e: (0, 0),
5527 ev_entry,
5528 e_device: e.ctx().ordinal(),
5529 local_q_dim,
5530 local_kv_dim,
5531 heads,
5532 o_out,
5533 o_block_cols,
5534 blocks_per_rank,
5535 });
5536 eprintln!(
5537 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5538 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5539 residency=persistent ordering=evented performance_claim=false"
5540 );
5541 Ok(guard.len() - 1)
5542 }
5543
5544 #[allow(clippy::too_many_arguments)]
5552 #[allow(clippy::too_many_arguments)]
5557 pub fn decode_v2_input_qkv_tcol(
5558 &self,
5559 ws_index: usize,
5560 e: &Engine,
5561 h_t: &CudaSlice<f32>,
5562 t: usize,
5563 q_m: &ResidentBf16ColumnParallel,
5564 k_m: &ResidentBf16ColumnParallel,
5565 v_m: &ResidentBf16ColumnParallel,
5566 gate_shards: Option<StepTpGateShards<'_>>,
5567 ) -> Result<(), Box<dyn std::error::Error>> {
5568 let ranks = self.ranks.len();
5569 let mut guard = self
5570 .decode_v2
5571 .lock()
5572 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5573 let ws = guard
5574 .get_mut(ws_index)
5575 .ok_or("step TP decode v2 workspace index out of range")?;
5576 let in_f = q_m.in_features;
5577 if h_t.len() < t * in_f || t == 0 || t > 32 {
5578 return Err("decode_v2_input_qkv_tcol geometry".into());
5579 }
5580 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5582 ws.tcol_q.clear();
5583 ws.tcol_k.clear();
5584 ws.tcol_v.clear();
5585 ws.tcol_g.clear();
5586 ws.tcol_in.clear();
5587 for engine in &self.ranks {
5588 let _m = engine.gpu.enter_main()?;
5589 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
5590 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
5591 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
5592 ws.tcol_g
5593 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
5594 ws.tcol_in.push(engine.uninit(32 * in_f)?);
5595 }
5596 ws.tcol_cap = 32;
5597 }
5598 use cudarc::driver::DevicePtr;
5600 let raw_src = {
5601 let _main = e.gpu.enter_main()?;
5602 let stream = e.stream();
5603 let (p, _g) = h_t.device_ptr(&stream);
5604 ws.ev_entry.record(&stream)?;
5605 p as u64
5606 };
5607 for rank in 0..ranks {
5608 let engine = &self.ranks[rank];
5609 let _main = engine.gpu.enter_main()?;
5610 engine.stream().wait(&ws.ev_entry)?;
5611 let raw_dst = {
5612 let stream = engine.stream();
5613 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5614 p as u64
5615 };
5616 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5617 let out_g = match &gate_shards {
5618 Some(_) => ws.heads / ranks,
5619 None => 0,
5620 };
5621 match (
5622 &q_m.ranks[rank].weight,
5623 &k_m.ranks[rank].weight,
5624 &v_m.ranks[rank].weight,
5625 ) {
5626 (
5627 ResidentBf16Weight::Bf16(wq),
5628 ResidentBf16Weight::Bf16(wk),
5629 ResidentBf16Weight::Bf16(wv),
5630 ) => {
5631 let wg = match &gate_shards {
5632 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5633 Some(StepTpGateShards::F32(_)) => {
5634 return Err(
5635 "tcol verify: gate shard class does not match bf16 QKV".into()
5636 );
5637 }
5638 None => wq,
5639 };
5640 let StepTpDecodeV2Ws {
5641 tcol_q,
5642 tcol_k,
5643 tcol_v,
5644 tcol_g,
5645 tcol_in,
5646 local_q_dim,
5647 local_kv_dim,
5648 ..
5649 } = &mut *ws;
5650 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5653 let refk = *REFK
5654 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5655 if refk {
5656 let lq = *local_q_dim;
5657 let lkv = *local_kv_dim;
5658 let mut hrow = engine.uninit(in_f)?;
5659 let mut qr = engine.uninit(lq)?;
5660 let mut kr = engine.uninit(lkv)?;
5661 let mut vr = engine.uninit(lkv)?;
5662 let mut gr = engine.uninit(out_g.max(1))?;
5663 for c in 0..t {
5664 {
5665 let mut dst = hrow.slice_mut(0..in_f);
5666 engine.stream().memcpy_dtod(
5667 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5668 &mut dst,
5669 )?;
5670 }
5671 engine.matvec_bf16_qkvg_into(
5672 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5673 lq, lkv, out_g,
5674 )?;
5675 let stream = engine.stream();
5676 {
5677 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5678 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5679 }
5680 {
5681 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5682 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5683 }
5684 {
5685 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5686 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5687 }
5688 if out_g > 0 {
5689 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5690 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5691 }
5692 }
5693 } else {
5694 engine.matvec_bf16_qkvg_tcol_into(
5695 wq,
5696 wk,
5697 wv,
5698 wg,
5699 &tcol_in[rank],
5700 &mut tcol_q[rank],
5701 &mut tcol_k[rank],
5702 &mut tcol_v[rank],
5703 &mut tcol_g[rank],
5704 in_f,
5705 *local_q_dim,
5706 *local_kv_dim,
5707 out_g,
5708 t,
5709 )?;
5710 }
5711 }
5712 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5713 }
5714 }
5715 Ok(())
5716 }
5717
5718 pub(crate) fn decode_v2_oproj_tcol_eligible(
5722 &self,
5723 ws: &StepTpDecodeV2Ws,
5724 o_m: &ResidentStepBf16RowParallel,
5725 ) -> bool {
5726 self.ranks.len() == 2
5727 && ws.blocks_per_rank == 4
5728 && step_tp_qkv_fused_enabled().unwrap_or(false)
5729 && no_local_shadow_on()
5730 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5731 && o_m
5732 .ranks
5733 .iter()
5734 .flatten()
5735 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5736 }
5737
5738 pub(crate) fn decode_v2_stash_fa2(
5743 &self,
5744 ws: &mut StepTpDecodeV2Ws,
5745 e: &Engine,
5746 col: usize,
5747 ) -> Result<(), Box<dyn std::error::Error>> {
5748 let ranks = self.ranks.len();
5749 if col >= 32 {
5750 return Err("decode_v2_stash_fa2 column out of range".into());
5751 }
5752 let lq = ws.local_q_dim;
5753 let lg = (ws.heads / ranks).max(1);
5754 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks {
5755 ws.fa2_q.clear();
5756 ws.fa2_gate.clear();
5757 ws.fa2_gated.clear();
5758 ws.rope_k_t.clear();
5759 ws.rope_ctr_t.clear();
5760 ws.rope_pos_t.clear();
5761 for engine in &self.ranks {
5762 let _m = engine.gpu.enter_main()?;
5763 ws.fa2_q.push(engine.uninit(32 * lq)?);
5764 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5765 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5766 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
5767 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5768 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5769 }
5770 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5771 ws.fa2_cap = 32;
5772 }
5773 for rank in 0..ranks {
5774 let engine = &self.ranks[rank];
5775 let _main = engine.gpu.enter_main()?;
5776 {
5777 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5778 engine
5779 .stream()
5780 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5781 }
5782 {
5783 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5784 engine
5785 .stream()
5786 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5787 }
5788 ws.ev_rank[rank].record(&engine.stream())?;
5789 }
5790 {
5791 let _main = e.gpu.enter_main()?;
5792 for ev in ws.ev_rank.iter() {
5793 e.stream().wait(ev)?;
5794 }
5795 }
5796 Ok(())
5797 }
5798
5799 #[allow(clippy::too_many_arguments)]
5806 pub(crate) fn decode_v2_spec_fa2_join(
5807 &self,
5808 ws_index: usize,
5809 e: &Engine,
5810 o_m: &ResidentStepBf16RowParallel,
5811 kv: &ResidentTpKvCache,
5812 head_dim: usize,
5813 window: usize,
5814 bucket_max: usize,
5815 scale: f32,
5816 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5817 let ranks = self.ranks.len();
5818 static ONCE: std::sync::Once = std::sync::Once::new();
5820 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
5821 {
5822 let mut guard = self
5823 .decode_v2
5824 .lock()
5825 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5826 let ws = guard
5827 .get_mut(ws_index)
5828 .ok_or("step TP decode v2 workspace index out of range")?;
5829 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
5830 return Err("spec fa2 join without stashed columns".into());
5831 }
5832 let lq = ws.local_q_dim;
5833 let local_heads = (ws.heads / ranks).max(1);
5834 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
5835 let capacity = kv.physical_capacity();
5836 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
5837 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
5839 ws.tcol_gated.clear();
5840 ws.tcol_opart.clear();
5841 for engine in &self.ranks {
5842 let _m = engine.gpu.enter_main()?;
5843 ws.tcol_gated.push(engine.uninit(32 * lq)?);
5844 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
5845 }
5846 let root = &self.ranks[0];
5847 let _m = root.gpu.enter_main()?;
5848 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
5849 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
5850 ws.tcol_ocap = 32;
5851 }
5852 for rank in 0..ranks {
5853 let engine = &self.ranks[rank];
5854 let _main = engine.gpu.enter_main()?;
5855 let rank_cache = kv
5856 .rank(rank)
5857 .ok_or("spec fa2 join lost its KV cache rank")?;
5858 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
5859 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
5860 {
5861 let StepTpDecodeV2Ws {
5862 fa2_q,
5863 fa2_gate,
5864 fa2_gated,
5865 ..
5866 } = &mut *ws;
5867 engine.fa_decode_dcw2(
5868 &fa2_q[rank],
5869 &k_ring,
5870 &v_ring,
5871 &mut fa2_gated[rank],
5872 head_dim,
5873 local_heads,
5874 local_kv_heads,
5875 rank_cache.len_d(),
5876 rank_cache.base_d(),
5877 window,
5878 bucket_max,
5879 scale,
5880 k_tok_bytes,
5881 v_tok_bytes,
5882 &fa2_gate[rank],
5883 )?;
5884 }
5885 let StepTpDecodeV2Ws {
5888 fa2_gated,
5889 tcol_gated,
5890 ..
5891 } = &mut *ws;
5892 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
5893 engine
5894 .stream()
5895 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
5896 }
5897 }
5898 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
5899 }
5900
5901 #[allow(clippy::too_many_arguments)]
5911 pub(crate) fn decode_v2_rope_fa_rows(
5912 &self,
5913 ws_index: usize,
5914 e: &Engine,
5915 o_m: &ResidentStepBf16RowParallel,
5916 session_parts: &[Vec<[u64; 4]>],
5917 tab_keys: &[u64],
5918 positions: &[i32],
5919 stage_pos: bool,
5920 same_session: bool,
5921 q_norms: &[CudaSlice<f32>],
5922 k_norms: &[CudaSlice<f32>],
5923 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
5924 t: usize,
5925 head_dim: usize,
5926 n_rot: usize,
5927 window: usize,
5928 max_ns: usize,
5929 scale: f32,
5930 k_tok_bytes: usize,
5931 v_tok_bytes: usize,
5932 eps: f32,
5933 rope_base: f32,
5934 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
5935 use cudarc::driver::DevicePtr;
5936 let ranks = self.ranks.len();
5937 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
5938 return Err("rope fa rows geometry".into());
5939 }
5940 {
5941 let mut guard = self
5942 .decode_v2
5943 .lock()
5944 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5945 let ws = guard
5946 .get_mut(ws_index)
5947 .ok_or("step TP decode v2 workspace index out of range")?;
5948 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5949 return Err("rope fa rows without tcol slabs".into());
5950 }
5951 let lq = ws.local_q_dim;
5952 let lkv = ws.local_kv_dim;
5953 let lg = (ws.heads / ranks).max(1);
5954 let local_heads = (ws.heads / ranks).max(1);
5955 let local_kv_heads = (lkv / head_dim).max(1);
5956 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks {
5958 ws.fa2_q.clear();
5959 ws.fa2_gate.clear();
5960 ws.fa2_gated.clear();
5961 ws.rope_k_t.clear();
5962 ws.rope_ctr_t.clear();
5963 ws.rope_pos_t.clear();
5964 for engine in &self.ranks {
5965 let _m = engine.gpu.enter_main()?;
5966 ws.fa2_q.push(engine.uninit(32 * lq)?);
5967 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5968 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5969 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
5970 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5971 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5972 }
5973 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5974 ws.fa2_cap = 32;
5975 }
5976 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
5977 ws.tcol_gated.clear();
5978 ws.tcol_opart.clear();
5979 for engine in &self.ranks {
5980 let _m = engine.gpu.enter_main()?;
5981 ws.tcol_gated.push(engine.uninit(32 * lq)?);
5982 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
5983 }
5984 let root = &self.ranks[0];
5985 let _m = root.gpu.enter_main()?;
5986 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
5987 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
5988 ws.tcol_ocap = 32;
5989 }
5990 for rank in 0..ranks {
5991 let engine = &self.ranks[rank];
5992 let _main = engine.gpu.enter_main()?;
5993 if stage_pos {
5994 let host: Vec<i32> = positions[..t].to_vec();
5995 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
5996 engine.stream().memcpy_htod(&host, &mut view)?;
5997 }
5998 if !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6001 let ctr_base = {
6002 let s = engine.stream();
6003 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6004 p as u64
6005 };
6006 let mut host = Vec::with_capacity(t * 6);
6007 for (r, parts) in session_parts[rank].iter().enumerate().take(t) {
6008 host.extend_from_slice(&[
6009 parts[0],
6010 parts[1],
6011 parts[2],
6012 parts[3],
6013 if same_session {
6014 ctr_base
6015 } else {
6016 ctr_base + (r as u64) * 4
6017 },
6018 if same_session {
6019 (t - 1 - r) as u64
6020 } else {
6021 0u64
6022 },
6023 ]);
6024 }
6025 let tab = engine.stream().clone_htod(&host)?;
6026 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6027 }
6028 let StepTpDecodeV2Ws {
6029 tcol_q,
6030 tcol_k,
6031 tcol_v,
6032 tcol_g,
6033 fa2_q,
6034 fa2_gated,
6035 rope_k_t,
6036 rope_pos_t,
6037 rows_tabs,
6038 ..
6039 } = &mut *ws;
6040 let tab = rows_tabs[rank]
6041 .get(&tab_keys[rank])
6042 .expect("inserted above");
6043 engine.qk_norm_rope_append_inc_dcw_rows(
6044 &tcol_q[rank],
6045 &tcol_k[rank],
6046 &tcol_v[rank],
6047 &q_norms[rank],
6048 &k_norms[rank],
6049 &mut fa2_q[rank],
6050 &mut rope_k_t[rank],
6051 tab,
6052 &rope_pos_t[rank],
6053 same_session,
6054 t,
6055 lkv,
6056 lkv,
6057 k_tok_bytes,
6058 v_tok_bytes,
6059 head_dim,
6060 n_rot,
6061 local_heads,
6062 local_kv_heads,
6063 eps,
6064 rope_base,
6065 1.0,
6066 rope_freqs[rank],
6067 )?;
6068 engine.fa_decode_dcw_rows(
6069 &fa2_q[rank],
6070 tab,
6071 &mut fa2_gated[rank],
6072 t,
6073 head_dim,
6074 local_heads,
6075 local_kv_heads,
6076 window,
6077 max_ns,
6078 scale,
6079 k_tok_bytes,
6080 v_tok_bytes,
6081 &tcol_g[rank],
6082 )?;
6083 let StepTpDecodeV2Ws {
6084 fa2_gated,
6085 tcol_gated,
6086 ..
6087 } = &mut *ws;
6088 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6089 engine
6090 .stream()
6091 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6092 }
6093 }
6094 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6095 }
6096
6097 #[allow(clippy::too_many_arguments)]
6104 pub(crate) fn decode_v2_fa_rows_join(
6105 &self,
6106 ws_index: usize,
6107 e: &Engine,
6108 o_m: &ResidentStepBf16RowParallel,
6109 tabs: &[&crate::CudaSlice<u64>],
6110 t: usize,
6111 head_dim: usize,
6112 window: usize,
6113 max_ns: usize,
6114 scale: f32,
6115 k_tok_bytes: usize,
6116 v_tok_bytes: usize,
6117 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6118 let ranks = self.ranks.len();
6119 if tabs.len() != ranks {
6120 return Err("fa rows join needs one table per rank".into());
6121 }
6122 {
6123 let mut guard = self
6124 .decode_v2
6125 .lock()
6126 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6127 let ws = guard
6128 .get_mut(ws_index)
6129 .ok_or("step TP decode v2 workspace index out of range")?;
6130 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6131 return Err("fa rows join without stashed rows".into());
6132 }
6133 let lq = ws.local_q_dim;
6134 let local_heads = (ws.heads / ranks).max(1);
6135 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6136 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6137 ws.tcol_gated.clear();
6138 ws.tcol_opart.clear();
6139 for engine in &self.ranks {
6140 let _m = engine.gpu.enter_main()?;
6141 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6142 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6143 }
6144 let root = &self.ranks[0];
6145 let _m = root.gpu.enter_main()?;
6146 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6147 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6148 ws.tcol_ocap = 32;
6149 }
6150 for rank in 0..ranks {
6151 let engine = &self.ranks[rank];
6152 let _main = engine.gpu.enter_main()?;
6153 {
6154 let StepTpDecodeV2Ws {
6155 fa2_q,
6156 fa2_gate,
6157 fa2_gated,
6158 ..
6159 } = &mut *ws;
6160 engine.fa_decode_dcw_rows(
6161 &fa2_q[rank],
6162 tabs[rank],
6163 &mut fa2_gated[rank],
6164 t,
6165 head_dim,
6166 local_heads,
6167 local_kv_heads,
6168 window,
6169 max_ns,
6170 scale,
6171 k_tok_bytes,
6172 v_tok_bytes,
6173 &fa2_gate[rank],
6174 )?;
6175 }
6176 let StepTpDecodeV2Ws {
6177 fa2_gated,
6178 tcol_gated,
6179 ..
6180 } = &mut *ws;
6181 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6182 engine
6183 .stream()
6184 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6185 }
6186 }
6187 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6188 }
6189
6190 pub(crate) fn decode_v2_stash_gated(
6195 &self,
6196 ws: &mut StepTpDecodeV2Ws,
6197 e: &Engine,
6198 col: usize,
6199 ) -> Result<(), Box<dyn std::error::Error>> {
6200 let ranks = self.ranks.len();
6201 if col >= 8 {
6202 return Err("decode_v2_stash_gated column out of range".into());
6203 }
6204 let lq = ws.local_q_dim;
6205 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6206 ws.tcol_gated.clear();
6207 ws.tcol_opart.clear();
6208 for engine in &self.ranks {
6209 let _m = engine.gpu.enter_main()?;
6210 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6211 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6212 }
6213 let root = &self.ranks[0];
6214 let _m = root.gpu.enter_main()?;
6215 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6216 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6217 ws.tcol_ocap = 32;
6218 }
6219 for rank in 0..ranks {
6220 let engine = &self.ranks[rank];
6221 let _main = engine.gpu.enter_main()?;
6222 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6223 engine
6224 .stream()
6225 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6226 ws.ev_rank[rank].record(&engine.stream())?;
6230 }
6231 {
6232 let _main = e.gpu.enter_main()?;
6233 for ev in ws.ev_rank.iter() {
6234 e.stream().wait(ev)?;
6235 }
6236 }
6237 Ok(())
6238 }
6239
6240 pub(crate) fn decode_v2_oproj_tcol(
6246 &self,
6247 ws_index: usize,
6248 e: &Engine,
6249 o_m: &ResidentStepBf16RowParallel,
6250 t: usize,
6251 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6252 let ranks = self.ranks.len();
6253 let mut guard = self
6254 .decode_v2
6255 .lock()
6256 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6257 let ws = guard
6258 .get_mut(ws_index)
6259 .ok_or("step TP decode v2 workspace index out of range")?;
6260 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6261 return Err("decode_v2_oproj_tcol geometry".into());
6262 }
6263 for rank in 0..ranks {
6264 let engine = &self.ranks[rank];
6265 let _main = engine.gpu.enter_main()?;
6266 let mut weights = Vec::with_capacity(4);
6267 for block in 0..4 {
6268 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6269 return Err("tcol o_proj requires bf16-resident O blocks".into());
6270 };
6271 weights.push(weight);
6272 }
6273 {
6274 let StepTpDecodeV2Ws {
6275 tcol_gated,
6276 tcol_opart,
6277 local_q_dim,
6278 o_block_cols,
6279 o_out,
6280 ..
6281 } = &mut *ws;
6282 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6285 let refk = *REFK
6286 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6287 if refk {
6288 let lq = *local_q_dim;
6289 let mut xr = engine.uninit(lq)?;
6290 let mut yr = engine.uninit(*o_out)?;
6291 for c in 0..t {
6292 {
6293 let mut dst = xr.slice_mut(0..lq);
6294 engine.stream().memcpy_dtod(
6295 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6296 &mut dst,
6297 )?;
6298 }
6299 engine.matvec_bf16_b4_into(
6300 [weights[0], weights[1], weights[2], weights[3]],
6301 &xr,
6302 &mut yr,
6303 *o_block_cols,
6304 *o_out,
6305 )?;
6306 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6307 engine
6308 .stream()
6309 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6310 }
6311 } else {
6312 engine.matvec_bf16_b4_tcol_into(
6313 [weights[0], weights[1], weights[2], weights[3]],
6314 &tcol_gated[rank],
6315 &mut tcol_opart[rank],
6316 *o_block_cols,
6317 *o_out,
6318 t,
6319 )?;
6320 }
6321 }
6322 if rank != 0 {
6323 ws.ev_rank[rank].record(&engine.stream())?;
6324 }
6325 }
6326 let root = &self.ranks[0];
6327 {
6328 let _main = root.gpu.enter_main()?;
6329 for ev in ws.ev_rank.iter().skip(1) {
6330 root.stream().wait(ev)?;
6331 }
6332 {
6333 let StepTpDecodeV2Ws {
6334 tcol_opart,
6335 tcol_opeer,
6336 tcol_omix,
6337 o_out,
6338 ..
6339 } = &mut *ws;
6340 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6341 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6342 {
6343 let mut dst = opeer.slice_mut(0..t * *o_out);
6344 root.stream()
6345 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6346 }
6347 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6350 }
6351 ws.ev_oproj.record(&root.stream())?;
6352 }
6353 let _main = e.gpu.enter_main()?;
6354 e.stream().wait(&ws.ev_oproj)?;
6355 let mut out = e.uninit(t * ws.o_out)?;
6356 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6357 e.stream().memcpy_dtod(
6358 &omix.slice(0..t * ws.o_out),
6359 &mut out.slice_mut(0..t * ws.o_out),
6360 )?;
6361 Ok(out)
6362 }
6363
6364 pub(crate) fn decode_v2_input_qkv(
6365 &self,
6366 ws: &mut StepTpDecodeV2Ws,
6367 e: &Engine,
6368 h: &CudaSlice<f32>,
6369 pos_d: &CudaSlice<i32>,
6370 gate_raw: Option<&CudaSlice<f32>>,
6371 gate_shards: Option<StepTpGateShards<'_>>,
6372 decode_input: &mut ResidentReplicatedDeviceRows,
6373 q_m: &ResidentBf16ColumnParallel,
6374 k_m: &ResidentBf16ColumnParallel,
6375 v_m: &ResidentBf16ColumnParallel,
6376 q_norm: &[CudaSlice<f32>],
6377 k_norm: &[CudaSlice<f32>],
6378 head_dim: usize,
6379 n_rot: usize,
6380 rope_base: f32,
6381 rope_freqs: &[Option<&CudaSlice<f32>>],
6382 rms_eps: f32,
6383 defer_norm_rope: bool,
6384 tcol_col: Option<usize>,
6385 ) -> Result<(), Box<dyn std::error::Error>> {
6386 let ranks = self.ranks.len();
6387 validate_replicated_device_rows(&self.ranks, decode_input)?;
6388 if decode_input.tokens != 1
6389 || decode_input.width != q_m.in_features
6390 || pos_d.len() != 1
6391 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6392 || gate_raw.is_none() != gate_shards.is_some()
6393 || gate_shards.as_ref().is_some_and(|shards| match shards {
6394 StepTpGateShards::F32(shards) => shards.len() != ranks,
6395 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6396 })
6397 || q_norm.len() != ranks
6398 || k_norm.len() != ranks
6399 || rope_freqs.len() != ranks
6400 || e.ctx().ordinal() != ws.e_device
6401 {
6402 return Err("step TP decode v2 input geometry mismatch".into());
6403 }
6404
6405 let qkv_fused = step_tp_qkv_fused_enabled()?;
6406 if gate_shards.is_some() && !qkv_fused {
6407 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6408 }
6409 let values = decode_input.width;
6410 if h.len() != values {
6411 return Err(format!(
6412 "step TP decode v2 hidden width {} != replicated width {values}",
6413 h.len()
6414 )
6415 .into());
6416 }
6417
6418 if qkv_fused {
6419 if ws.h_stage.is_none() {
6423 use cudarc::driver::DevicePtr;
6424 let _main = e.gpu.enter_main()?;
6425 let h_stage = e.uninit(values)?;
6426 let pos_stage = e.htod_i32(&[0])?;
6427 {
6428 let stream = e.stream();
6429 let (hp, _g0) = h_stage.device_ptr(&stream);
6430 let (pp, _g1) = pos_stage.device_ptr(&stream);
6431 ws.raw_h_stage = hp as u64;
6432 ws.raw_pos_stage = pp as u64;
6433 }
6434 ws.h_stage = Some(h_stage);
6435 ws.pos_stage = Some(pos_stage);
6436 for rank in 0..ranks {
6437 use cudarc::driver::DevicePtr;
6438 let engine = &self.ranks[rank];
6439 let _rmain = engine.gpu.enter_main()?;
6440 let attn_in = engine.uninit(values)?;
6441 let (dp, pp) = {
6442 let stream = engine.stream();
6443 let (dp, _g2) = attn_in.device_ptr(&stream);
6444 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6445 (dp as u64, pp as u64)
6446 };
6447 ws.raw_attn_in.push(dp);
6448 ws.raw_pos.push(pp);
6449 ws.attn_in.push(attn_in);
6450 }
6451 {
6452 use cudarc::driver::DevicePtr;
6453 let root = &self.ranks[0];
6454 let _rmain = root.gpu.enter_main()?;
6455 let stream = root.stream();
6456 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6457 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6458 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6459 ws.raw_peer_partial = a as u64;
6460 ws.raw_k_shadow = b as u64;
6461 ws.raw_v_shadow = c as u64;
6462 }
6463 {
6464 use cudarc::driver::DevicePtr;
6465 let rank1 = &self.ranks[1];
6466 let _rmain = rank1.gpu.enter_main()?;
6467 let stream = rank1.stream();
6468 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6469 let (b, _g) = ws.k[1].device_ptr(&stream);
6470 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6471 ws.raw_o_partial1 = a as u64;
6472 ws.raw_k1 = b as u64;
6473 ws.raw_v1 = c as u64;
6474 }
6475 }
6476 {
6477 let _main = e.gpu.enter_main()?;
6478 {
6479 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6482 let mut dst = h_stage.slice_mut(0..values);
6483 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6484 }
6485 {
6486 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6487 let mut dst = pos_stage.slice_mut(0..1);
6488 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6489 }
6490 ws.ev_entry.record(&e.stream())?;
6491 }
6492 for rank in 0..ranks {
6493 let engine = &self.ranks[rank];
6494 let _main = engine.gpu.enter_main()?;
6495 engine.stream().wait(&ws.ev_entry)?;
6496 }
6497 } else {
6498 {
6500 let _main = e.gpu.enter_main()?;
6501 if let Some(gate_raw) = gate_raw {
6502 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6503 e.stream()
6504 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6505 }
6506 ws.ev_entry.record(&e.stream())?;
6507 }
6508 {
6509 let root = &self.ranks[0];
6510 let _main = root.gpu.enter_main()?;
6511 root.stream().wait(&ws.ev_entry)?;
6512 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6513 root.stream()
6514 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6515 ws.ev_refresh.record(&root.stream())?;
6516 }
6517 for rank in 1..ranks {
6518 let engine = &self.ranks[rank];
6519 let _main = engine.gpu.enter_main()?;
6520 engine.stream().wait(&ws.ev_refresh)?;
6521 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6522 let mut destination = peer_rows[0].slice_mut(0..values);
6523 engine
6524 .stream()
6525 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6526 }
6527 }
6528 for rank in 0..ranks {
6529 self.decode_v2_input_qkv_rank(
6530 ws,
6531 pos_d,
6532 decode_input,
6533 q_m,
6534 k_m,
6535 v_m,
6536 q_norm,
6537 k_norm,
6538 head_dim,
6539 n_rot,
6540 rope_base,
6541 rope_freqs,
6542 rms_eps,
6543 gate_shards.as_ref(),
6544 qkv_fused,
6545 defer_norm_rope,
6546 rank,
6547 tcol_col,
6548 )?;
6549 }
6550 Ok(())
6551 }
6552
6553 #[allow(clippy::too_many_arguments)]
6556 pub(crate) fn decode_v2_input_qkv_rank(
6557 &self,
6558 ws: &mut StepTpDecodeV2Ws,
6559 pos_d: &CudaSlice<i32>,
6560 decode_input: &mut ResidentReplicatedDeviceRows,
6561 q_m: &ResidentBf16ColumnParallel,
6562 k_m: &ResidentBf16ColumnParallel,
6563 v_m: &ResidentBf16ColumnParallel,
6564 q_norm: &[CudaSlice<f32>],
6565 k_norm: &[CudaSlice<f32>],
6566 head_dim: usize,
6567 n_rot: usize,
6568 rope_base: f32,
6569 rope_freqs: &[Option<&CudaSlice<f32>>],
6570 rms_eps: f32,
6571 gate_shards: Option<&StepTpGateShards<'_>>,
6572 qkv_fused: bool,
6573 defer_norm_rope: bool,
6574 rank: usize,
6575 tcol_col: Option<usize>,
6576 ) -> Result<(), Box<dyn std::error::Error>> {
6577 let ranks = self.ranks.len();
6578 let local_heads = ws.local_q_dim / head_dim;
6579 let local_kv_heads = ws.local_kv_dim / head_dim;
6580 let engine = &self.ranks[rank];
6581 let _main = engine.gpu.enter_main()?;
6582 let ws_e_device = ws.e_device;
6583 if qkv_fused && tcol_col.is_some() {
6588 let c = tcol_col.expect("checked");
6589 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6590 return Err("tcol select without precompute".into());
6591 }
6592 if engine.ctx().ordinal() != ws_e_device {
6596 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6597 }
6598 let StepTpDecodeV2Ws {
6599 tcol_q,
6600 tcol_k,
6601 tcol_v,
6602 tcol_g,
6603 q_raw,
6604 k_raw,
6605 v_raw,
6606 gate,
6607 local_q_dim,
6608 local_kv_dim,
6609 heads,
6610 ..
6611 } = &mut *ws;
6612 let lg = *heads / ranks;
6613 let stream = engine.stream();
6614 {
6615 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6616 stream.memcpy_dtod(
6617 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6618 &mut dst,
6619 )?;
6620 }
6621 {
6622 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6623 stream.memcpy_dtod(
6624 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6625 &mut dst,
6626 )?;
6627 }
6628 {
6629 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6630 stream.memcpy_dtod(
6631 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6632 &mut dst,
6633 )?;
6634 }
6635 if lg > 0 {
6636 let mut dst = gate[rank].slice_mut(0..lg);
6637 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6638 }
6639 if !defer_norm_rope {
6640 } else {
6644 return Ok(());
6645 }
6646 }
6647 if qkv_fused {
6648 let same_dev = engine.ctx().ordinal() == ws.e_device;
6653 if !same_dev {
6654 raw_copy_bytes(
6655 ws.raw_attn_in[rank],
6656 ws.raw_h_stage,
6657 q_m.in_features * 4,
6658 engine,
6659 )?;
6660 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6661 }
6662 let StepTpDecodeV2Ws {
6663 q_raw,
6664 k_raw,
6665 v_raw,
6666 gate,
6667 gate_e,
6668 attn_in,
6669 h_stage,
6670 heads,
6671 local_q_dim,
6672 local_kv_dim,
6673 w8_aq,
6674 w8_ad,
6675 w8_in,
6676 ..
6677 } = &mut *ws;
6678 let input_ref: &CudaSlice<f32> = if same_dev {
6679 h_stage
6680 .as_ref()
6681 .ok_or("step TP decode v2 stage not armed")?
6682 } else {
6683 &attn_in[rank]
6684 };
6685 match (
6686 &q_m.ranks[rank].weight,
6687 &k_m.ranks[rank].weight,
6688 &v_m.ranks[rank].weight,
6689 ) {
6690 (
6691 ResidentBf16Weight::F32(wq),
6692 ResidentBf16Weight::F32(wk),
6693 ResidentBf16Weight::F32(wv),
6694 ) => {
6695 let (wg, out_g) = match &gate_shards {
6696 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6697 Some(StepTpGateShards::Bf16(_)) => {
6698 return Err("step TP decode v2 gate shard class does not \
6699 match the F32 projections"
6700 .into());
6701 }
6702 None => (&*gate_e, 0),
6704 };
6705 engine.matvec_f32_qkv_into(
6706 wq,
6707 wk,
6708 wv,
6709 wg,
6710 input_ref,
6711 &mut q_raw[rank],
6712 &mut k_raw[rank],
6713 &mut v_raw[rank],
6714 &mut gate[rank],
6715 q_m.in_features,
6716 *local_q_dim,
6717 *local_kv_dim,
6718 out_g,
6719 )?;
6720 }
6721 (
6722 ResidentBf16Weight::Bf16(wq),
6723 ResidentBf16Weight::Bf16(wk),
6724 ResidentBf16Weight::Bf16(wv),
6725 ) => {
6726 let (wg, out_g) = match &gate_shards {
6727 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6728 Some(StepTpGateShards::F32(_)) => {
6729 return Err("step TP decode v2 gate shard class does not \
6730 match the bf16 projections"
6731 .into());
6732 }
6733 None => (wq, 0),
6734 };
6735 let in_f = q_m.in_features;
6742 let q8_ready = crate::step_tp_w8_on()
6743 && q_m.ranks[rank].q8.is_some()
6744 && k_m.ranks[rank].q8.is_some()
6745 && v_m.ranks[rank].q8.is_some();
6746 if q8_ready {
6747 if *w8_in != in_f || w8_aq.len() != ranks {
6748 w8_aq.clear();
6749 w8_ad.clear();
6750 for e_rank in &self.ranks {
6751 let _m = e_rank.gpu.enter_main()?;
6752 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
6753 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
6754 }
6755 *w8_in = in_f;
6756 }
6757 engine.quantize_q8_1_into(
6758 input_ref,
6759 1,
6760 in_f,
6761 &mut w8_aq[rank],
6762 &mut w8_ad[rank],
6763 )?;
6764 engine.qmatvec_q8_0_qkv_rp_into(
6769 q_m.ranks[rank].q8.as_ref().unwrap(),
6770 k_m.ranks[rank].q8.as_ref().unwrap(),
6771 v_m.ranks[rank].q8.as_ref().unwrap(),
6772 &w8_aq[rank],
6773 &w8_ad[rank],
6774 &mut q_raw[rank],
6775 &mut k_raw[rank],
6776 &mut v_raw[rank],
6777 in_f,
6778 *local_q_dim,
6779 *local_kv_dim,
6780 )?;
6781 if out_g > 0 {
6782 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
6783 }
6784 } else {
6785 engine.matvec_bf16_qkvg_into(
6786 wq,
6787 wk,
6788 wv,
6789 wg,
6790 input_ref,
6791 &mut q_raw[rank],
6792 &mut k_raw[rank],
6793 &mut v_raw[rank],
6794 &mut gate[rank],
6795 q_m.in_features,
6796 *local_q_dim,
6797 *local_kv_dim,
6798 out_g,
6799 )?;
6800 }
6801 }
6802 _ => {
6803 return Err("step TP decode v2 QKV projections mix residency classes".into());
6804 }
6805 }
6806 } else {
6807 for (matrix, local_out, raw) in [
6808 (q_m, ws.local_q_dim, &mut ws.q_raw),
6809 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6810 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6811 ] {
6812 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6813 return Err("step TP decode v2 lost its F32 projection residency".into());
6814 };
6815 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6816 engine.linear_f32_resident_canonical_rows_t1_into(
6817 &decode_input.ranks[rank],
6818 values_w,
6819 &mut raw[rank],
6820 matrix.in_features,
6821 local_out,
6822 chunk_rows,
6823 )?;
6824 }
6825 }
6826 if qkv_fused && defer_norm_rope {
6827 } else if qkv_fused {
6829 let StepTpDecodeV2Ws {
6832 q_raw,
6833 k_raw,
6834 q,
6835 k,
6836 pos,
6837 pos_stage,
6838 ..
6839 } = &mut *ws;
6840 let same_dev = engine.ctx().ordinal() == ws_e_device;
6841 let pos_ref: &CudaSlice<i32> = if same_dev {
6842 pos_stage
6843 .as_ref()
6844 .ok_or("step TP decode v2 pos stage not armed")?
6845 } else {
6846 &pos[rank]
6847 };
6848 engine.qk_norm_rope_into(
6849 &q_raw[rank],
6850 &k_raw[rank],
6851 &q_norm[rank],
6852 &k_norm[rank],
6853 &mut q[rank],
6854 &mut k[rank],
6855 pos_ref,
6856 head_dim,
6857 n_rot,
6858 local_heads,
6859 local_kv_heads,
6860 rms_eps,
6861 rope_base,
6862 1.0,
6863 rope_freqs[rank],
6864 )?;
6865 } else {
6866 engine.rms_norm(
6867 &ws.q_raw[rank],
6868 &q_norm[rank],
6869 &mut ws.q[rank],
6870 head_dim,
6871 local_heads,
6872 rms_eps,
6873 )?;
6874 engine.rms_norm(
6875 &ws.k_raw[rank],
6876 &k_norm[rank],
6877 &mut ws.k[rank],
6878 head_dim,
6879 local_kv_heads,
6880 rms_eps,
6881 )?;
6882 {
6883 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6884 engine
6885 .stream()
6886 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6887 }
6888 engine.rope_neox2(
6889 &mut ws.q[rank],
6890 &mut ws.k[rank],
6891 &ws.pos[rank],
6892 head_dim,
6893 n_rot,
6894 local_heads,
6895 local_kv_heads,
6896 1,
6897 rope_base,
6898 1.0,
6899 rope_freqs[rank],
6900 )?;
6901 }
6902 if gate_shards.is_none() {
6903 let gate_start = rank * (ws.heads / ranks);
6904 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6905 engine.stream().memcpy_dtod(
6906 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6907 &mut gate_dst,
6908 )?;
6909 }
6910 Ok(())
6911 }
6912
6913 pub(crate) fn decode_v2_finish_rank_partial(
6917 &self,
6918 ws: &mut StepTpDecodeV2Ws,
6919 o_m: &ResidentStepBf16RowParallel,
6920 o_fused: bool,
6921 rank: usize,
6922 ) -> Result<(), Box<dyn std::error::Error>> {
6923 let engine = &self.ranks[rank];
6924 let _main = engine.gpu.enter_main()?;
6925 if o_fused {
6926 let StepTpDecodeV2Ws {
6927 gated,
6928 o_partials,
6929 o_block_cols,
6930 o_out,
6931 w8o_aq,
6932 w8o_ad,
6933 w8o_in,
6934 ..
6935 } = &mut *ws;
6936 let all_f32 = o_m.ranks[rank]
6937 .iter()
6938 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6939 if all_f32 {
6940 let mut weights = Vec::with_capacity(4);
6941 for block in 0..4 {
6942 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6943 unreachable!("all_f32 checked above");
6944 };
6945 weights.push(weight);
6946 }
6947 engine.matvec_f32_b4_into(
6948 [weights[0], weights[1], weights[2], weights[3]],
6949 &gated[rank],
6950 &mut o_partials[rank][0],
6951 *o_block_cols,
6952 *o_out,
6953 )?;
6954 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
6955 let in_f = 4 * *o_block_cols;
6960 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
6961 w8o_aq.clear();
6962 w8o_ad.clear();
6963 for e_rank in &self.ranks {
6964 let _m = e_rank.gpu.enter_main()?;
6965 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
6966 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
6967 }
6968 *w8o_in = in_f;
6969 }
6970 engine.quantize_q8_1_into(
6971 &gated[rank],
6972 1,
6973 in_f,
6974 &mut w8o_aq[rank],
6975 &mut w8o_ad[rank],
6976 )?;
6977 engine.qmatvec_q8_0_b4_rp_into(
6978 [
6979 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6980 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6981 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6982 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6983 ],
6984 &w8o_aq[rank],
6985 &w8o_ad[rank],
6986 &mut o_partials[rank][0],
6987 *o_block_cols,
6988 *o_out,
6989 )?;
6990 } else {
6991 let mut weights = Vec::with_capacity(4);
6992 for block in 0..4 {
6993 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6994 return Err("step TP decode v2 O projections mix residency classes".into());
6995 };
6996 weights.push(weight);
6997 }
6998 engine.matvec_bf16_b4_into(
6999 [weights[0], weights[1], weights[2], weights[3]],
7000 &gated[rank],
7001 &mut o_partials[rank][0],
7002 *o_block_cols,
7003 *o_out,
7004 )?;
7005 }
7006 } else {
7007 for block in 0..ws.blocks_per_rank {
7008 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7009 return Err("step TP decode v2 lost its F32 O residency".into());
7010 };
7011 let x =
7012 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7013 let w = weight.slice(0..weight.len());
7014 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7015 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7016 }
7017 }
7018 Ok(())
7019 }
7020
7021 pub(crate) fn decode_v2_finish(
7029 &self,
7030 ws: &mut StepTpDecodeV2Ws,
7031 e: &Engine,
7032 o_m: &ResidentStepBf16RowParallel,
7033 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7034 let ranks = self.ranks.len();
7035 if e.ctx().ordinal() != ws.e_device {
7036 return Err("step TP decode v2 finish engine changed".into());
7037 }
7038 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7043
7044 for rank in 0..ranks {
7047 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7048 if rank == 0 {
7049 continue;
7052 }
7053 let engine = &self.ranks[rank];
7054 let _main = engine.gpu.enter_main()?;
7055 ws.ev_rank[rank].record(&engine.stream())?;
7056 }
7057
7058 let root = &self.ranks[0];
7060 #[allow(unused_assignments)]
7061 let mut final_in_a = false;
7062 {
7063 let _main = root.gpu.enter_main()?;
7064 for ev in ws.ev_rank.iter().skip(1) {
7065 root.stream().wait(ev)?;
7066 }
7067 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7068 ws.ev_oproj.record(&root.stream())?;
7074 let _main = e.gpu.enter_main()?;
7075 e.stream().wait(&ws.ev_oproj)?;
7076 let mut output = e.uninit(ws.o_out)?;
7077 if oproj_tail_on() && oproj_tail_eligible() {
7078 use cudarc::driver::DevicePtr;
7081 let stream = e.stream();
7082 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7083 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7084 set_oproj_tail((p0 as u64, p1 as u64));
7085 return Ok(output);
7086 }
7087 e.add(
7088 &ws.o_partials[0][0],
7089 &ws.o_partials[1][0],
7090 &mut output,
7091 ws.o_out,
7092 )?;
7093 return Ok(output);
7094 }
7095 if o_fused {
7096 self.decode_v2_finish_root_fused(ws)?;
7097 ws.ev_oproj.record(&root.stream())?;
7098 let _main = e.gpu.enter_main()?;
7099 e.stream().wait(&ws.ev_oproj)?;
7100 let mut output = e.uninit(ws.o_out)?;
7101 e.stream().memcpy_dtod(
7102 &ws.reduce_a.slice(0..ws.o_out),
7103 &mut output.slice_mut(0..ws.o_out),
7104 )?;
7105 return Ok(output);
7106 }
7107 let mut first = true;
7108 let mut current_is_a = false;
7109 for rank in 0..ranks {
7110 for block in 0..ws.blocks_per_rank {
7111 let use_peer = rank != 0;
7112 if use_peer {
7113 root.stream()
7114 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
7115 }
7116 match (first, current_is_a, use_peer) {
7118 (true, _, true) => {
7119 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7120 }
7121 (true, _, false) => root.add(
7122 &ws.zeros,
7123 &ws.o_partials[0][block],
7124 &mut ws.reduce_a,
7125 ws.o_out,
7126 )?,
7127 (false, true, true) => {
7128 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7129 }
7130 (false, true, false) => root.add(
7131 &ws.reduce_a,
7132 &ws.o_partials[0][block],
7133 &mut ws.reduce_b,
7134 ws.o_out,
7135 )?,
7136 (false, false, true) => {
7137 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7138 }
7139 (false, false, false) => root.add(
7140 &ws.reduce_b,
7141 &ws.o_partials[0][block],
7142 &mut ws.reduce_a,
7143 ws.o_out,
7144 )?,
7145 }
7146 current_is_a = first || !current_is_a;
7147 first = false;
7148 }
7149 }
7150 final_in_a = current_is_a;
7151
7152 for rank in 0..ranks {
7153 let start = rank * ws.local_kv_dim;
7154 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
7155 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
7156 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
7157 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
7158 }
7159 ws.ev_oproj.record(&root.stream())?;
7160 }
7161
7162 let _main = e.gpu.enter_main()?;
7166 e.stream().wait(&ws.ev_oproj)?;
7167 let mut output = e.uninit(ws.o_out)?;
7168 let source = if final_in_a {
7169 &ws.reduce_a
7170 } else {
7171 &ws.reduce_b
7172 };
7173 e.stream().memcpy_dtod(
7174 &source.slice(0..ws.o_out),
7175 &mut output.slice_mut(0..ws.o_out),
7176 )?;
7177 Ok(output)
7178 }
7179
7180 pub fn run_routed_experts(
7181 &self,
7182 experts: &ResidentExpertParallel,
7183 input: &[f32],
7184 tokens: usize,
7185 selected: &[usize],
7186 route_weights: &[f32],
7187 experts_per_token: usize,
7188 activation_limit: Option<f32>,
7189 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7190 validate_step_expert_activation_limit(activation_limit)?;
7191 validate_ep_residency(&self.ranks, experts)?;
7192 validate_activations(input, tokens, experts.input_width)?;
7193 let pairs = tokens
7194 .checked_mul(experts_per_token)
7195 .ok_or("EP route count overflow")?;
7196 if selected.len() != pairs || route_weights.len() != pairs {
7197 return Err(format!(
7198 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7199 {experts_per_token} ({pairs})",
7200 selected.len(),
7201 route_weights.len(),
7202 )
7203 .into());
7204 }
7205 if !route_weights.iter().all(|weight| weight.is_finite()) {
7206 return Err("EP route weights contain a non-finite value".into());
7207 }
7208 if self.native_p2p {
7209 return self.run_routed_experts_native(
7210 experts,
7211 input,
7212 tokens,
7213 selected,
7214 route_weights,
7215 experts_per_token,
7216 activation_limit,
7217 );
7218 }
7219
7220 let mut output = vec![0.0f32; tokens * experts.input_width];
7221 let per_rank = experts.expert_count / experts.ranks.len();
7222 for token in 0..tokens {
7223 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7224 for slot in 0..experts_per_token {
7225 let pair = token * experts_per_token + slot;
7226 let expert = selected[pair];
7227 if expert >= experts.expert_count {
7228 return Err(format!(
7229 "EP selected expert {expert} outside 0..{}",
7230 experts.expert_count
7231 )
7232 .into());
7233 }
7234 let owner = expert / per_rank;
7235 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7236 let rank = &experts.ranks[owner];
7237 let engine = &self.ranks[owner];
7238 let gate =
7239 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7240 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7241 let activated: Vec<f32> = gate
7242 .iter()
7243 .zip(&up)
7244 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7245 .collect();
7246 debug_assert_eq!(activated.len(), experts.expert_width);
7247 let down =
7248 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7249 let weight = route_weights[pair];
7250 for (sum, value) in output
7251 [token * experts.input_width..(token + 1) * experts.input_width]
7252 .iter_mut()
7253 .zip(down)
7254 {
7255 *sum += weight * value;
7256 }
7257 }
7258 }
7259 Ok(output)
7260 }
7261
7262 fn run_routed_experts_native(
7263 &self,
7264 experts: &ResidentExpertParallel,
7265 input: &[f32],
7266 tokens: usize,
7267 selected: &[usize],
7268 route_weights: &[f32],
7269 experts_per_token: usize,
7270 activation_limit: Option<f32>,
7271 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7272 if !self.native_p2p || self.ranks.len() < 2 {
7273 return Err("native EP execution requires at least two P2P ranks".into());
7274 }
7275 if self.ep_device_arithmetic {
7276 return self.run_routed_experts_native_device(
7277 experts,
7278 input,
7279 tokens,
7280 selected,
7281 route_weights,
7282 experts_per_token,
7283 activation_limit,
7284 );
7285 }
7286 let mut output = vec![0.0f32; tokens * experts.input_width];
7287 let per_rank = experts.expert_count / experts.ranks.len();
7288 for token in 0..tokens {
7289 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7290 let mut rank_inputs = (0..self.ranks.len())
7291 .map(|_| None)
7292 .collect::<Vec<Option<CudaSlice<f32>>>>();
7293 rank_inputs[0] = Some({
7294 let root = &self.ranks[0];
7295 let _main = root.gpu.enter_main()?;
7296 root.htod(input_row)?
7297 });
7298
7299 for slot in 0..experts_per_token {
7300 let pair = token * experts_per_token + slot;
7301 let expert = selected[pair];
7302 if expert >= experts.expert_count {
7303 return Err(format!(
7304 "EP selected expert {expert} outside 0..{}",
7305 experts.expert_count
7306 )
7307 .into());
7308 }
7309 let owner = expert / per_rank;
7310 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7311 if rank_inputs[owner].is_none() {
7312 let peer_input = {
7313 let root_input = rank_inputs[0]
7314 .as_ref()
7315 .ok_or("native EP lost its root input")?;
7316 let engine = &self.ranks[owner];
7317 let _main = engine.gpu.enter_main()?;
7318 let mut peer_input = engine.uninit(experts.input_width)?;
7319 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7320 peer_input
7321 };
7322 rank_inputs[owner] = Some(peer_input);
7323 }
7324
7325 let rank = &experts.ranks[owner];
7326 let engine = &self.ranks[owner];
7327 let owner_input = rank_inputs[owner]
7328 .as_ref()
7329 .ok_or("native EP owner input is absent after dispatch")?;
7330 let gate = run_resident_bank_expert_device(
7331 engine,
7332 &rank.gate,
7333 local_expert,
7334 owner_input,
7335 1,
7336 )?;
7337 let up = run_resident_bank_expert_device(
7338 engine,
7339 &rank.up,
7340 local_expert,
7341 owner_input,
7342 1,
7343 )?;
7344 let (gate, up) = {
7345 let _main = engine.gpu.enter_main()?;
7346 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
7347 };
7348 let activated = gate
7349 .iter()
7350 .zip(&up)
7351 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7352 .collect::<Vec<_>>();
7353 debug_assert_eq!(activated.len(), experts.expert_width);
7354 let activated = {
7355 let _main = engine.gpu.enter_main()?;
7356 engine.htod(&activated)?
7357 };
7358 let down = run_resident_bank_expert_device(
7359 engine,
7360 &rank.down,
7361 local_expert,
7362 &activated,
7363 1,
7364 )?;
7365 let down = if owner == 0 {
7366 let _main = engine.gpu.enter_main()?;
7367 engine.dtoh(&down)?
7368 } else {
7369 let root = &self.ranks[0];
7370 let _main = root.gpu.enter_main()?;
7371 let mut root_down = root.uninit(experts.input_width)?;
7372 root.stream().memcpy_dtod(&down, &mut root_down)?;
7373 root.dtoh(&root_down)?
7374 };
7375 let weight = route_weights[pair];
7376 for (sum, value) in output
7377 [token * experts.input_width..(token + 1) * experts.input_width]
7378 .iter_mut()
7379 .zip(down)
7380 {
7381 *sum += weight * value;
7382 }
7383 }
7384 }
7385 Ok(output)
7386 }
7387
7388 fn run_routed_experts_native_device(
7389 &self,
7390 experts: &ResidentExpertParallel,
7391 input: &[f32],
7392 tokens: usize,
7393 selected: &[usize],
7394 route_weights: &[f32],
7395 experts_per_token: usize,
7396 activation_limit: Option<f32>,
7397 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7398 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
7399 return Err(
7400 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
7401 );
7402 }
7403 let mut output = Vec::with_capacity(tokens * experts.input_width);
7404 let per_rank = experts.expert_count / experts.ranks.len();
7405 let root = &self.ranks[0];
7406 for token in 0..tokens {
7407 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7408 let mut rank_inputs = (0..self.ranks.len())
7409 .map(|_| None)
7410 .collect::<Vec<Option<CudaSlice<f32>>>>();
7411 rank_inputs[0] = Some({
7412 let _main = root.gpu.enter_main()?;
7413 root.htod(input_row)?
7414 });
7415 let mut root_output = {
7416 let _main = root.gpu.enter_main()?;
7417 root.zeros(experts.input_width)?
7418 };
7419 let mut remote_down_keepalive = Vec::new();
7420
7421 for slot in 0..experts_per_token {
7422 let pair = token * experts_per_token + slot;
7423 let expert = selected[pair];
7424 if expert >= experts.expert_count {
7425 return Err(format!(
7426 "EP selected expert {expert} outside 0..{}",
7427 experts.expert_count
7428 )
7429 .into());
7430 }
7431 let owner = expert / per_rank;
7432 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7433 if rank_inputs[owner].is_none() {
7434 let peer_input = {
7435 let root_input = rank_inputs[0]
7436 .as_ref()
7437 .ok_or("native EP lost its root input")?;
7438 let engine = &self.ranks[owner];
7439 let _main = engine.gpu.enter_main()?;
7440 let mut peer_input = engine.uninit(experts.input_width)?;
7441 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7442 peer_input
7443 };
7444 rank_inputs[owner] = Some(peer_input);
7445 }
7446
7447 let rank = &experts.ranks[owner];
7448 let engine = &self.ranks[owner];
7449 let owner_input = rank_inputs[owner]
7450 .as_ref()
7451 .ok_or("native EP owner input is absent after dispatch")?;
7452 let gate = run_resident_bank_expert_device(
7453 engine,
7454 &rank.gate,
7455 local_expert,
7456 owner_input,
7457 1,
7458 )?;
7459 let up = run_resident_bank_expert_device(
7460 engine,
7461 &rank.up,
7462 local_expert,
7463 owner_input,
7464 1,
7465 )?;
7466 let activated = {
7467 let _main = engine.gpu.enter_main()?;
7468 let mut activated = engine.uninit(experts.expert_width)?;
7469 if let Some(limit) = activation_limit {
7470 engine.silu_clamped_mul_host_expf(
7471 &gate,
7472 &up,
7473 limit,
7474 &mut activated,
7475 experts.expert_width,
7476 )?;
7477 } else {
7478 engine.silu_mul_host_expf(
7479 &gate,
7480 &up,
7481 &mut activated,
7482 experts.expert_width,
7483 )?;
7484 }
7485 activated
7486 };
7487 let down = run_resident_bank_expert_device(
7488 engine,
7489 &rank.down,
7490 local_expert,
7491 &activated,
7492 1,
7493 )?;
7494 let root_down = if owner == 0 {
7495 down
7496 } else {
7497 let _main = root.gpu.enter_main()?;
7498 let mut root_down = root.uninit(experts.input_width)?;
7499 root.stream().memcpy_dtod(&down, &mut root_down)?;
7500 remote_down_keepalive.push(down);
7504 root_down
7505 };
7506 let _main = root.gpu.enter_main()?;
7507 let mut destination = root_output.slice_mut(0..experts.input_width);
7508 root.axpy_host_into(
7509 &root_down.slice(0..root_down.len()),
7510 route_weights[pair],
7511 &mut destination,
7512 experts.input_width,
7513 )?;
7514 }
7515
7516 let _main = root.gpu.enter_main()?;
7517 let root_output = root.dtoh(&root_output)?;
7518 drop(remote_down_keepalive);
7519 output.extend(root_output);
7520 }
7521 Ok(output)
7522 }
7523}
7524
7525fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7526 if matrix.out_features % tp != 0 {
7527 return Err(format!(
7528 "column-parallel out_features {} is not divisible by TP={tp}",
7529 matrix.out_features
7530 ));
7531 }
7532 let local_out = matrix.out_features / tp;
7533 if local_out % FP8_BLOCK != 0 {
7534 return Err(format!(
7535 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7536 E4M3 scale block"
7537 ));
7538 }
7539 Ok(())
7540}
7541
7542fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7543 if !matches!(tp, 1 | 2 | 4 | 8) {
7544 return Err(format!(
7545 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7546 ));
7547 }
7548 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7549 return Err(format!(
7550 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7551 ));
7552 }
7553 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7554 let local_out = out_features / tp;
7555 if local_out % canonical_rows != 0 {
7556 return Err(format!(
7557 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7558 {canonical_rows}-row chunks"
7559 ));
7560 }
7561 Ok(canonical_rows)
7562}
7563
7564fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7565 if !matches!(tp, 1 | 2 | 4 | 8) {
7566 return Err(format!(
7567 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7568 ));
7569 }
7570 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7571 return Err(format!(
7572 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7573 ));
7574 }
7575 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7576 let local_in = in_features / tp;
7577 if local_in % canonical_cols != 0 {
7578 return Err(format!(
7579 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7580 {canonical_cols}-column chunks"
7581 ));
7582 }
7583 Ok(canonical_cols)
7584}
7585
7586fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7587 if matrix.in_features % tp != 0 {
7588 return Err(format!(
7589 "row-parallel in_features {} is not divisible by TP={tp}",
7590 matrix.in_features
7591 ));
7592 }
7593 let local_in = matrix.in_features / tp;
7594 if local_in % FP8_BLOCK != 0 {
7595 return Err(format!(
7596 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7597 E4M3 scale block"
7598 ));
7599 }
7600 Ok(())
7601}
7602
7603fn upload_rank(
7604 engine: &Engine,
7605 matrix: E4m3BlockMatrix<'_>,
7606) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7607 let _main = engine.gpu.enter_main()?;
7608 matrix.validate()?;
7609 Ok(ResidentE4m3Rank {
7610 codes: engine.htod_bytes(matrix.codes)?,
7611 scales: engine.htod(matrix.scales)?,
7612 out_features: matrix.out_features,
7613 in_features: matrix.in_features,
7614 })
7615}
7616
7617fn upload_bf16_rank(
7618 engine: &Engine,
7619 matrix: Bf16Matrix<'_>,
7620 f32_mirror: bool,
7621) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7622 let _main = engine.gpu.enter_main()?;
7623 matrix.validate()?;
7624 let bytes = engine.htod_bytes(matrix.bytes)?;
7625 let weight = if f32_mirror {
7626 let values = matrix
7627 .out_features
7628 .checked_mul(matrix.in_features)
7629 .ok_or("resident BF16 mirror element count overflow")?;
7630 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7631 } else {
7632 ResidentBf16Weight::Bf16(bytes)
7633 };
7634 let q8 = if crate::step_tp_w8_on() && matrix.in_features % 32 == 0 {
7638 if let ResidentBf16Weight::Bf16(bytes) = &weight {
7639 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
7646 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
7647 engine.encode_q8_0_from_bf16(
7648 bytes,
7649 &mut interleaved,
7650 matrix.in_features,
7651 matrix.out_features,
7652 )?;
7653 let mirror =
7654 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
7655 Some(mirror)
7656 } else {
7657 None
7658 }
7659 } else {
7660 None
7661 };
7662 Ok(ResidentBf16Rank {
7663 weight,
7664 out_features: matrix.out_features,
7665 in_features: matrix.in_features,
7666 q8,
7667 })
7668}
7669
7670fn upload_expert_bank_rank(
7671 engine: &Engine,
7672 bank: E4m3ExpertBank<'_>,
7673 expert_range: Range<usize>,
7674) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7675 let _main = engine.gpu.enter_main()?;
7676 bank.validate()?;
7677 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7678 return Err(format!(
7679 "invalid EP expert range {expert_range:?} for {} experts",
7680 bank.expert_count
7681 )
7682 .into());
7683 }
7684 let code_stride = bank.out_features * bank.in_features;
7685 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7686 Ok(ResidentE4m3ExpertBankRank {
7687 codes: engine.htod_bytes(
7688 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7689 )?,
7690 scales: engine.htod(
7691 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7692 )?,
7693 expert_range,
7694 out_features: bank.out_features,
7695 in_features: bank.in_features,
7696 code_stride,
7697 scale_stride,
7698 k_blocks: None,
7699 })
7700}
7701
7702fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7703 if bank.out_features % tp != 0 {
7704 return Err(format!(
7705 "TP expert output width {} is not divisible by TP={tp}",
7706 bank.out_features
7707 ));
7708 }
7709 let local_out = bank.out_features / tp;
7710 if local_out % FP8_BLOCK != 0 {
7711 return Err(format!(
7712 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7713 ));
7714 }
7715 Ok(())
7716}
7717
7718fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7719 if bank.in_features % tp != 0 {
7720 return Err(format!(
7721 "TP expert input width {} is not divisible by TP={tp}",
7722 bank.in_features
7723 ));
7724 }
7725 let local_in = bank.in_features / tp;
7726 if local_in % FP8_BLOCK != 0 {
7727 return Err(format!(
7728 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7729 ));
7730 }
7731 Ok(())
7732}
7733
7734fn upload_column_bank_rank(
7735 engine: &Engine,
7736 bank: E4m3ExpertBank<'_>,
7737 tp: usize,
7738 rank: usize,
7739) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7740 let _main = engine.gpu.enter_main()?;
7741 let packed = pack_column_bank_rank(bank, tp, rank)?;
7742 Ok(ResidentE4m3ExpertBankRank {
7743 codes: engine.htod_bytes(&packed.codes)?,
7744 scales: engine.htod(&packed.scales)?,
7745 expert_range: packed.expert_range,
7746 out_features: packed.out_features,
7747 in_features: packed.in_features,
7748 code_stride: packed.code_stride,
7749 scale_stride: packed.scale_stride,
7750 k_blocks: packed.k_blocks,
7751 })
7752}
7753
7754fn pack_column_bank_rank(
7755 bank: E4m3ExpertBank<'_>,
7756 tp: usize,
7757 rank: usize,
7758) -> Result<PackedE4m3ExpertBankRank, String> {
7759 bank.validate()?;
7760 validate_column_bank_shape(bank, tp)?;
7761 if rank >= tp {
7762 return Err(format!("TP rank {rank} outside 0..{tp}"));
7763 }
7764 let local_out = bank.out_features / tp;
7765 let full_code_stride = bank.out_features * bank.in_features;
7766 let local_code_stride = local_out * bank.in_features;
7767 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7768 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7769 let local_scale_rows = local_out / FP8_BLOCK;
7770 let local_scale_stride = local_scale_rows * scale_cols;
7771 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7772 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7773 let row_start = rank * local_out;
7774 let scale_row_start = rank * local_scale_rows;
7775 for expert in 0..bank.expert_count {
7776 let code_start = expert * full_code_stride + row_start * bank.in_features;
7777 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7778 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7779 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7780 }
7781 Ok(PackedE4m3ExpertBankRank {
7782 codes,
7783 scales,
7784 expert_range: 0..bank.expert_count,
7785 out_features: local_out,
7786 in_features: bank.in_features,
7787 code_stride: local_code_stride,
7788 scale_stride: local_scale_stride,
7789 k_blocks: None,
7790 })
7791}
7792
7793fn upload_row_bank_rank(
7794 engine: &Engine,
7795 bank: E4m3ExpertBank<'_>,
7796 tp: usize,
7797 rank: usize,
7798) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7799 let _main = engine.gpu.enter_main()?;
7800 let packed = pack_row_bank_rank(bank, tp, rank)?;
7801 Ok(ResidentE4m3ExpertBankRank {
7802 codes: engine.htod_bytes(&packed.codes)?,
7803 scales: engine.htod(&packed.scales)?,
7804 expert_range: packed.expert_range,
7805 out_features: packed.out_features,
7806 in_features: packed.in_features,
7807 code_stride: packed.code_stride,
7808 scale_stride: packed.scale_stride,
7809 k_blocks: packed.k_blocks,
7810 })
7811}
7812
7813fn pack_row_bank_rank(
7814 bank: E4m3ExpertBank<'_>,
7815 tp: usize,
7816 rank: usize,
7817) -> Result<PackedE4m3ExpertBankRank, String> {
7818 bank.validate()?;
7819 validate_row_bank_shape(bank, tp)?;
7820 if rank >= tp {
7821 return Err(format!("TP rank {rank} outside 0..{tp}"));
7822 }
7823 let local_in = bank.in_features / tp;
7824 let full_code_stride = bank.out_features * bank.in_features;
7825 let local_code_stride = bank.out_features * local_in;
7826 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7827 let local_scale_cols = local_in / FP8_BLOCK;
7828 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7829 let full_scale_stride = scale_rows * full_scale_cols;
7830 let local_scale_stride = scale_rows * local_scale_cols;
7831 let global_block_start = rank * local_scale_cols;
7832 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7833 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7834 for expert in 0..bank.expert_count {
7835 let expert_code_start = expert * full_code_stride;
7836 let expert_scale_start = expert * full_scale_stride;
7837 for local_block in 0..local_scale_cols {
7838 let global_block = global_block_start + local_block;
7839 let column_start = global_block * FP8_BLOCK;
7840 for row in 0..bank.out_features {
7841 let start = expert_code_start + row * bank.in_features + column_start;
7842 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7843 }
7844 for row in 0..scale_rows {
7845 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7846 }
7847 }
7848 }
7849 Ok(PackedE4m3ExpertBankRank {
7850 codes,
7851 scales,
7852 expert_range: 0..bank.expert_count,
7853 out_features: bank.out_features,
7854 in_features: local_in,
7855 code_stride: local_code_stride,
7856 scale_stride: local_scale_stride,
7857 k_blocks: Some(local_scale_cols),
7858 })
7859}
7860
7861fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7862 if engines.len() != ranks.len() {
7863 return Err(format!(
7864 "resident TP rank count {} != runtime rank count {}",
7865 ranks.len(),
7866 engines.len()
7867 ));
7868 }
7869 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7870 let device = engine.ctx().ordinal();
7871 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7872 return Err(format!(
7873 "resident TP rank {rank} is not owned by runtime device {device}"
7874 ));
7875 }
7876 }
7877 Ok(())
7878}
7879
7880fn validate_tp_bank_residency(
7881 engines: &[Engine],
7882 experts: &ResidentTpExpertBank,
7883) -> Result<(), String> {
7884 if engines.len() != experts.gate.len()
7885 || engines.len() != experts.up.len()
7886 || engines.len() != experts.down.len()
7887 {
7888 return Err(format!(
7889 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7890 experts.gate.len(),
7891 experts.up.len(),
7892 experts.down.len(),
7893 engines.len()
7894 ));
7895 }
7896 for (rank, engine) in engines.iter().enumerate() {
7897 let device = engine.ctx().ordinal();
7898 for (projection, bank) in [
7899 ("gate", &experts.gate[rank]),
7900 ("up", &experts.up[rank]),
7901 ("down", &experts.down[rank]),
7902 ] {
7903 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7904 return Err(format!(
7905 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7906 {device}"
7907 ));
7908 }
7909 }
7910 }
7911 Ok(())
7912}
7913
7914fn validate_ep_residency(
7915 engines: &[Engine],
7916 experts: &ResidentExpertParallel,
7917) -> Result<(), String> {
7918 if engines.len() != experts.ranks.len() {
7919 return Err(format!(
7920 "resident EP rank count {} != runtime rank count {}",
7921 experts.ranks.len(),
7922 engines.len()
7923 ));
7924 }
7925 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7926 let device = engine.ctx().ordinal();
7927 for (projection, bank) in [
7928 ("gate", &resident.gate),
7929 ("up", &resident.up),
7930 ("down", &resident.down),
7931 ] {
7932 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7933 return Err(format!(
7934 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7935 {device}"
7936 ));
7937 }
7938 }
7939 }
7940 Ok(())
7941}
7942
7943fn run_rank(
7944 engine: &Engine,
7945 matrix: E4m3BlockMatrix<'_>,
7946 activations: &[f32],
7947 tokens: usize,
7948) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7949 let _main = engine.gpu.enter_main()?;
7950 let codes = engine.htod_bytes(matrix.codes)?;
7951 let scales = engine.htod(matrix.scales)?;
7952 let activations = engine.htod(activations)?;
7953 let output = engine.qmatvec_mmq_fp8_blk(
7954 &codes,
7955 &scales,
7956 &activations,
7957 tokens,
7958 matrix.in_features,
7959 matrix.out_features,
7960 )?;
7961 engine.dtoh(&output)
7962}
7963
7964fn run_resident_rank(
7965 engine: &Engine,
7966 matrix: &ResidentE4m3Rank,
7967 activations: &[f32],
7968 tokens: usize,
7969) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7970 let _main = engine.gpu.enter_main()?;
7971 let activations = engine.htod(activations)?;
7972 let output = engine.qmatvec_mmq_fp8_blk(
7973 &matrix.codes,
7974 &matrix.scales,
7975 &activations,
7976 tokens,
7977 matrix.in_features,
7978 matrix.out_features,
7979 )?;
7980 engine.dtoh(&output)
7981}
7982
7983fn run_resident_bf16_rank(
7984 engine: &Engine,
7985 matrix: &ResidentBf16Rank,
7986 activations: &[f32],
7987 tokens: usize,
7988 canonical_chunk_rows: Option<usize>,
7989) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7990 let _main = engine.gpu.enter_main()?;
7991 let activations = engine.htod(activations)?;
7992 let output = run_resident_bf16_rank_device(
7993 engine,
7994 matrix,
7995 &activations,
7996 tokens,
7997 canonical_chunk_rows,
7998 false,
7999 )?;
8000 engine.dtoh(&output)
8001}
8002
8003fn run_resident_bf16_rank_device(
8004 engine: &Engine,
8005 matrix: &ResidentBf16Rank,
8006 activations: &CudaSlice<f32>,
8007 tokens: usize,
8008 canonical_chunk_rows: Option<usize>,
8009 strided_chunk_output: bool,
8010) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8011 let _main = engine.gpu.enter_main()?;
8012 if activations.ordinal() != engine.ctx().ordinal() {
8013 return Err(format!(
8014 "resident BF16 activation device {} != rank device {}",
8015 activations.ordinal(),
8016 engine.ctx().ordinal()
8017 )
8018 .into());
8019 }
8020 if activations.len() != tokens * matrix.in_features {
8021 return Err(format!(
8022 "resident BF16 activation count {} != {tokens}x{}",
8023 activations.len(),
8024 matrix.in_features
8025 )
8026 .into());
8027 }
8028 match (&matrix.weight, canonical_chunk_rows) {
8029 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8030 .linear_bf16_resident_canonical_rows(
8031 activations,
8032 bytes,
8033 tokens,
8034 matrix.in_features,
8035 matrix.out_features,
8036 rows,
8037 ),
8038 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8039 activations,
8040 bytes,
8041 tokens,
8042 matrix.in_features,
8043 matrix.out_features,
8044 ),
8045 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8046 .linear_f32_resident_canonical_rows_strided(
8047 activations,
8048 values,
8049 tokens,
8050 matrix.in_features,
8051 matrix.out_features,
8052 rows,
8053 ),
8054 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8055 activations,
8056 values,
8057 tokens,
8058 matrix.in_features,
8059 matrix.out_features,
8060 rows,
8061 ),
8062 (ResidentBf16Weight::F32(values), None) => engine.linear(
8063 activations,
8064 values,
8065 tokens,
8066 matrix.in_features,
8067 matrix.out_features,
8068 ),
8069 }
8070}
8071
8072fn validate_resident_bf16_ranks(
8073 engines: &[Engine],
8074 ranks: &[ResidentBf16Rank],
8075) -> Result<(), String> {
8076 if engines.len() != ranks.len() {
8077 return Err(format!(
8078 "resident BF16 TP rank count {} != runtime rank count {}",
8079 ranks.len(),
8080 engines.len(),
8081 ));
8082 }
8083 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8084 let device = engine.ctx().ordinal();
8085 if matrix.weight.ordinal() != device {
8086 return Err(format!(
8087 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8088 ));
8089 }
8090 }
8091 Ok(())
8092}
8093
8094fn validate_step_bf16_row_residency(
8095 engines: &[Engine],
8096 matrix: &ResidentStepBf16RowParallel,
8097) -> Result<(), String> {
8098 if engines.len() != matrix.ranks.len() {
8099 return Err(format!(
8100 "resident Step BF16 row rank count {} != runtime rank count {}",
8101 matrix.ranks.len(),
8102 engines.len(),
8103 ));
8104 }
8105 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8106 if matrix.canonical_chunk_cols != canonical_cols {
8107 return Err(format!(
8108 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8109 matrix.canonical_chunk_cols
8110 ));
8111 }
8112 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8113 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8114 if blocks.len() != blocks_per_rank {
8115 return Err(format!(
8116 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8117 blocks.len()
8118 ));
8119 }
8120 let device = engine.ctx().ordinal();
8121 for (block, resident) in blocks.iter().enumerate() {
8122 if resident.weight.ordinal() != device
8123 || resident.in_features != canonical_cols
8124 || resident.out_features != matrix.out_features
8125 {
8126 return Err(format!(
8127 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8128 device or geometry"
8129 ));
8130 }
8131 }
8132 }
8133 Ok(())
8134}
8135
8136fn validate_replicated_device_rows(
8137 engines: &[Engine],
8138 rows: &ResidentReplicatedDeviceRows,
8139) -> Result<(), String> {
8140 let rank_lengths = rows
8141 .ranks
8142 .iter()
8143 .map(|rank_rows| rank_rows.len())
8144 .collect::<Vec<_>>();
8145 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8146 if rows
8147 .ranks
8148 .iter()
8149 .zip(engines)
8150 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8151 {
8152 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8153 }
8154 Ok(())
8155}
8156
8157fn replicated_device_row_values(
8158 tokens: usize,
8159 width: usize,
8160 expected_ranks: usize,
8161 rank_lengths: &[usize],
8162) -> Result<usize, String> {
8163 let values = tokens
8164 .checked_mul(width)
8165 .ok_or("replicated device row size overflow")?;
8166 if tokens == 0
8167 || width == 0
8168 || expected_ranks == 0
8169 || rank_lengths.len() != expected_ranks
8170 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8171 {
8172 return Err(format!(
8173 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8174 tokens,
8175 width,
8176 rank_lengths.len(),
8177 expected_ranks
8178 ));
8179 }
8180 Ok(values)
8181}
8182
8183fn replicated_device_row_source_values(
8184 tokens: usize,
8185 width: usize,
8186 source_len: usize,
8187 source_device: usize,
8188 root_device: usize,
8189) -> Result<usize, String> {
8190 let values = tokens
8191 .checked_mul(width)
8192 .ok_or("replicated device row size overflow")?;
8193 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8194 return Err(format!(
8195 "replicated device row source has inconsistent geometry/device \
8196 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8197 ));
8198 }
8199 Ok(values)
8200}
8201
8202fn bf16_column_shard(
8203 matrix: Bf16Matrix<'_>,
8204 tp: usize,
8205 rank: usize,
8206) -> Result<Bf16Matrix<'_>, String> {
8207 matrix.validate()?;
8208 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8209 return Err(format!(
8210 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8211 matrix.out_features
8212 ));
8213 }
8214 let local_out = matrix.out_features / tp;
8215 let row_bytes = matrix.in_features * 2;
8216 let start = rank * local_out * row_bytes;
8217 Ok(Bf16Matrix {
8218 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8219 out_features: local_out,
8220 in_features: matrix.in_features,
8221 })
8222}
8223
8224fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8225 matrix.validate()?;
8226 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8227 return Err(format!(
8228 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8229 matrix.in_features
8230 ));
8231 }
8232 let local_in = matrix.in_features / tp;
8233 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8234 for row in 0..matrix.out_features {
8235 let start = (row * matrix.in_features + rank * local_in) * 2;
8236 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8237 }
8238 Ok(bytes)
8239}
8240
8241fn bf16_row_block(
8242 matrix: Bf16Matrix<'_>,
8243 col_start: usize,
8244 block_cols: usize,
8245) -> Result<Vec<u8>, String> {
8246 matrix.validate()?;
8247 let col_end = col_start
8248 .checked_add(block_cols)
8249 .ok_or("BF16 row block column overflow")?;
8250 if block_cols == 0 || col_end > matrix.in_features {
8251 return Err(format!(
8252 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8253 matrix.in_features
8254 ));
8255 }
8256 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8257 for row in 0..matrix.out_features {
8258 let start = (row * matrix.in_features + col_start) * 2;
8259 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8260 }
8261 Ok(bytes)
8262}
8263
8264fn run_resident_bank_expert(
8265 engine: &Engine,
8266 bank: &ResidentE4m3ExpertBankRank,
8267 local_expert: usize,
8268 activations: &[f32],
8269 tokens: usize,
8270) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8271 let _main = engine.gpu.enter_main()?;
8272 if bank.k_blocks.is_some() {
8273 return Err("block-major TP row bank requires canonical block execution".into());
8274 }
8275 let local_count = bank.expert_range.end - bank.expert_range.start;
8276 if local_expert >= local_count {
8277 return Err(format!(
8278 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8279 bank.expert_range
8280 )
8281 .into());
8282 }
8283 validate_activations(activations, tokens, bank.in_features)?;
8284 let activations = engine.htod(activations)?;
8285 let weight = bank
8286 .codes
8287 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8288 let scales = bank
8289 .scales
8290 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8291 let input = activations.slice(0..activations.len());
8292 let output = engine.qmatvec_mmq_fp8_blk_view(
8293 &weight,
8294 &scales,
8295 &input,
8296 tokens,
8297 bank.in_features,
8298 bank.out_features,
8299 )?;
8300 engine.dtoh(&output)
8301}
8302
8303fn run_resident_bank_expert_block(
8304 engine: &Engine,
8305 bank: &ResidentE4m3ExpertBankRank,
8306 local_expert: usize,
8307 block: usize,
8308 activations: &[f32],
8309) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8310 let _main = engine.gpu.enter_main()?;
8311 let local_count = bank.expert_range.end - bank.expert_range.start;
8312 if local_expert >= local_count {
8313 return Err(format!(
8314 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8315 bank.expert_range
8316 )
8317 .into());
8318 }
8319 let blocks = bank
8320 .k_blocks
8321 .ok_or("TP row bank is not packed in native K-block order")?;
8322 if block >= blocks {
8323 return Err(format!("TP row block {block} outside 0..{blocks}").into());
8324 }
8325 validate_activations(activations, 1, FP8_BLOCK)?;
8326 let block_code_stride = bank.out_features * FP8_BLOCK;
8327 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8328 if bank.in_features != blocks * FP8_BLOCK
8329 || bank.code_stride != blocks * block_code_stride
8330 || bank.scale_stride != blocks * block_scale_stride
8331 {
8332 return Err("TP row bank block-major geometry is inconsistent".into());
8333 }
8334
8335 let expert_code_start = local_expert * bank.code_stride;
8336 let expert_scale_start = local_expert * bank.scale_stride;
8337 let weight = bank.codes.slice(
8338 expert_code_start + block * block_code_stride
8339 ..expert_code_start + (block + 1) * block_code_stride,
8340 );
8341 let scales = bank.scales.slice(
8342 expert_scale_start + block * block_scale_stride
8343 ..expert_scale_start + (block + 1) * block_scale_stride,
8344 );
8345 let activations = engine.htod(activations)?;
8346 let input = activations.slice(0..activations.len());
8347 let output = engine.qmatvec_mmq_fp8_blk_view(
8348 &weight,
8349 &scales,
8350 &input,
8351 1,
8352 FP8_BLOCK,
8353 bank.out_features,
8354 )?;
8355 engine.dtoh(&output)
8356}
8357
8358fn run_resident_bank_expert_device(
8359 engine: &Engine,
8360 bank: &ResidentE4m3ExpertBankRank,
8361 local_expert: usize,
8362 activations: &CudaSlice<f32>,
8363 tokens: usize,
8364) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8365 let _main = engine.gpu.enter_main()?;
8366 if bank.k_blocks.is_some() {
8367 return Err("block-major TP row bank requires canonical block execution".into());
8368 }
8369 let local_count = bank.expert_range.end - bank.expert_range.start;
8370 if local_expert >= local_count {
8371 return Err(format!(
8372 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8373 bank.expert_range
8374 )
8375 .into());
8376 }
8377 let expected = tokens
8378 .checked_mul(bank.in_features)
8379 .ok_or("native TP activation size overflow")?;
8380 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
8381 return Err(format!(
8382 "native TP activation len/device {}/{} != expected {expected}/{}",
8383 activations.len(),
8384 activations.ordinal(),
8385 engine.ctx().ordinal()
8386 )
8387 .into());
8388 }
8389 let weight = bank
8390 .codes
8391 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8392 let scales = bank
8393 .scales
8394 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8395 let input = activations.slice(0..activations.len());
8396 engine.qmatvec_mmq_fp8_blk_view(
8397 &weight,
8398 &scales,
8399 &input,
8400 tokens,
8401 bank.in_features,
8402 bank.out_features,
8403 )
8404}
8405
8406fn run_resident_bank_expert_block_device(
8407 engine: &Engine,
8408 bank: &ResidentE4m3ExpertBankRank,
8409 local_expert: usize,
8410 block: usize,
8411 activations: &cudarc::driver::CudaView<'_, f32>,
8412) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8413 let _main = engine.gpu.enter_main()?;
8414 let local_count = bank.expert_range.end - bank.expert_range.start;
8415 if local_expert >= local_count {
8416 return Err(format!(
8417 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8418 bank.expert_range
8419 )
8420 .into());
8421 }
8422 let blocks = bank
8423 .k_blocks
8424 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
8425 if block >= blocks {
8426 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
8427 }
8428 let activation_device = activations.stream().context().ordinal();
8429 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
8430 return Err(format!(
8431 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
8432 activations.len(),
8433 activation_device,
8434 engine.ctx().ordinal()
8435 )
8436 .into());
8437 }
8438 let block_code_stride = bank.out_features * FP8_BLOCK;
8439 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8440 if bank.in_features != blocks * FP8_BLOCK
8441 || bank.code_stride != blocks * block_code_stride
8442 || bank.scale_stride != blocks * block_scale_stride
8443 {
8444 return Err("native TP row bank block-major geometry is inconsistent".into());
8445 }
8446 let expert_code_start = local_expert * bank.code_stride;
8447 let expert_scale_start = local_expert * bank.scale_stride;
8448 let weight = bank.codes.slice(
8449 expert_code_start + block * block_code_stride
8450 ..expert_code_start + (block + 1) * block_code_stride,
8451 );
8452 let scales = bank.scales.slice(
8453 expert_scale_start + block * block_scale_stride
8454 ..expert_scale_start + (block + 1) * block_scale_stride,
8455 );
8456 engine.qmatvec_mmq_fp8_blk_view(
8457 &weight,
8458 &scales,
8459 activations,
8460 1,
8461 FP8_BLOCK,
8462 bank.out_features,
8463 )
8464}
8465
8466fn configure_native_p2p(
8467 ranks: &[Engine],
8468 devices: &[usize],
8469) -> Result<(), Box<dyn std::error::Error>> {
8470 if ranks.len() != devices.len() || ranks.len() < 2 {
8471 return Err("native TP P2P setup requires matching multi-rank devices".into());
8472 }
8473 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8474 if engine.ctx().ordinal() != device {
8475 return Err(format!(
8476 "native TP rank {rank} context device {} != requested device {device}",
8477 engine.ctx().ordinal()
8478 )
8479 .into());
8480 }
8481 }
8482
8483 for src in 0..ranks.len() {
8484 for dst in 0..ranks.len() {
8485 if src == dst {
8486 continue;
8487 }
8488 let mut can_access = 0;
8489 unsafe {
8490 cudarc::driver::sys::cuDeviceCanAccessPeer(
8491 &mut can_access,
8492 ranks[src].ctx().cu_device(),
8493 ranks[dst].ctx().cu_device(),
8494 )
8495 .result()?;
8496 }
8497 if can_access == 0 {
8498 return Err(format!(
8499 "native TP requires P2P, but dev{} cannot access dev{}",
8500 devices[src], devices[dst]
8501 )
8502 .into());
8503 }
8504 ranks[src].ctx().bind_to_thread()?;
8505 let rc =
8506 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8507 use cudarc::driver::sys::cudaError_enum as E;
8508 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8509 return Err(format!(
8510 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8511 devices[src], devices[dst]
8512 )
8513 .into());
8514 }
8515 }
8516 }
8517
8518 for &owner in devices {
8519 for &accessor in devices {
8520 if owner == accessor {
8521 continue;
8522 }
8523 let device = cudarc::driver::result::device::get(owner as i32)?;
8524 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8525 unsafe {
8526 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8527 }
8528 let desc = cudarc::driver::sys::CUmemAccessDesc {
8529 location: cudarc::driver::sys::CUmemLocation {
8530 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8531 id: accessor as i32,
8532 },
8533 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8534 };
8535 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8536 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8537 return Err(format!(
8538 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8539 {rc:?}"
8540 )
8541 .into());
8542 }
8543 }
8544 }
8545
8546 for src in 0..ranks.len() {
8547 for dst in 0..ranks.len() {
8548 if src == dst {
8549 continue;
8550 }
8551 let expected = (0..NATIVE_P2P_PROBE_WORDS)
8552 .map(|index| {
8553 (index as u32)
8554 .wrapping_mul(0x9e37_79b9)
8555 .wrapping_add(((src as u32) << 16) | dst as u32)
8556 })
8557 .collect::<Vec<_>>();
8558 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8559 let source = ranks[src].htod_u32_v(&expected)?;
8560 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8561 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8562 let actual = ranks[dst].dtoh_u32(&destination)?;
8563 if actual != expected {
8564 let mismatches = actual
8565 .iter()
8566 .zip(&expected)
8567 .filter(|(actual, expected)| actual != expected)
8568 .count();
8569 return Err(format!(
8570 "native TP peer probe dev{}->dev{} failed: {mismatches}/{} words differ",
8571 devices[src],
8572 devices[dst],
8573 expected.len()
8574 )
8575 .into());
8576 }
8577 }
8578 }
8579 ranks[0].ctx().bind_to_thread()?;
8580 eprintln!(
8581 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8582 directions={} bytes={} mismatches=0",
8583 ranks.len() * (ranks.len() - 1),
8584 NATIVE_P2P_PROBE_WORDS * std::mem::size_of::<u32>(),
8585 );
8586 Ok(())
8587}
8588
8589fn validate_activations(
8590 activations: &[f32],
8591 tokens: usize,
8592 in_features: usize,
8593) -> Result<(), String> {
8594 let expected = tokens
8595 .checked_mul(in_features)
8596 .ok_or_else(|| "activation size overflow".to_string())?;
8597 if activations.len() != expected {
8598 return Err(format!(
8599 "activation count {} != {tokens}x{in_features} ({expected})",
8600 activations.len()
8601 ));
8602 }
8603 if !activations.iter().all(|value| value.is_finite()) {
8604 return Err("activations contain a non-finite value".to_string());
8605 }
8606 Ok(())
8607}
8608
8609fn column_shard(
8610 matrix: E4m3BlockMatrix<'_>,
8611 tp: usize,
8612 rank: usize,
8613) -> Result<E4m3BlockMatrix<'_>, String> {
8614 let local_out = matrix.out_features / tp;
8615 let row_start = rank * local_out;
8616 let code_start = row_start * matrix.in_features;
8617 let code_end = code_start + local_out * matrix.in_features;
8618 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8619 let local_scale_rows = local_out / FP8_BLOCK;
8620 let scale_start = rank * local_scale_rows * scale_cols;
8621 let scale_end = scale_start + local_scale_rows * scale_cols;
8622 Ok(E4m3BlockMatrix {
8623 codes: &matrix.codes[code_start..code_end],
8624 scales: &matrix.scales[scale_start..scale_end],
8625 out_features: local_out,
8626 in_features: matrix.in_features,
8627 })
8628}
8629
8630fn row_shard(
8631 matrix: E4m3BlockMatrix<'_>,
8632 tp: usize,
8633 rank: usize,
8634) -> Result<(Vec<u8>, Vec<f32>), String> {
8635 let local_in = matrix.in_features / tp;
8636 let col_start = rank * local_in;
8637 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8638 for row in 0..matrix.out_features {
8639 let start = row * matrix.in_features + col_start;
8640 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8641 }
8642
8643 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8644 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8645 let local_scale_cols = local_in / FP8_BLOCK;
8646 let scale_col_start = rank * local_scale_cols;
8647 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8648 for row in 0..scale_rows {
8649 let start = row * scale_cols + scale_col_start;
8650 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8651 }
8652 Ok((codes, scales))
8653}
8654
8655fn activation_shard(
8656 activations: &[f32],
8657 tokens: usize,
8658 in_features: usize,
8659 tp: usize,
8660 rank: usize,
8661) -> Vec<f32> {
8662 let local_in = in_features / tp;
8663 let col_start = rank * local_in;
8664 let mut shard = Vec::with_capacity(tokens * local_in);
8665 for token in 0..tokens {
8666 let start = token * in_features + col_start;
8667 shard.extend_from_slice(&activations[start..start + local_in]);
8668 }
8669 shard
8670}
8671
8672#[derive(Clone, Copy)]
8692pub struct Nvfp4BlockMatrix<'a> {
8693 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8697 pub in_features: usize,
8698}
8699
8700impl Nvfp4BlockMatrix<'_> {
8701 pub fn validate(&self) -> Result<(), String> {
8702 if self.in_features == 0 || self.out_features == 0 {
8703 return Err("NVFP4 matrix has a zero dimension".to_string());
8704 }
8705 if self.in_features % 64 != 0 {
8706 return Err(format!(
8707 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8708 self.in_features
8709 ));
8710 }
8711 if self.codes.len() != self.out_features * self.in_features / 2 {
8712 return Err(format!(
8713 "NVFP4 code bytes {} != {}x{}/2",
8714 self.codes.len(),
8715 self.out_features,
8716 self.in_features
8717 ));
8718 }
8719 if self.scales.len() != self.out_features * self.in_features / 16 {
8720 return Err(format!(
8721 "NVFP4 scale bytes {} != {}x{}/16",
8722 self.scales.len(),
8723 self.out_features,
8724 self.in_features
8725 ));
8726 }
8727 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8728 return Err(format!(
8729 "NVFP4 macro scale {} is not finite-positive",
8730 self.macro_scale
8731 ));
8732 }
8733 Ok(())
8734 }
8735}
8736
8737#[derive(Clone, Copy)]
8739pub struct Nvfp4ExpertBank<'a> {
8740 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8744 pub out_features: usize,
8745 pub in_features: usize,
8746}
8747
8748impl Nvfp4ExpertBank<'_> {
8749 pub fn validate(&self) -> Result<(), String> {
8750 if self.expert_count == 0 {
8751 return Err("NVFP4 expert bank is empty".to_string());
8752 }
8753 if self.macros.len() != self.expert_count {
8754 return Err(format!(
8755 "NVFP4 bank macros {} != expert count {}",
8756 self.macros.len(),
8757 self.expert_count
8758 ));
8759 }
8760 self.expert(0).map(|_| ())
8761 }
8762
8763 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8764 if expert >= self.expert_count {
8765 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8766 }
8767 let code_stride = self.out_features * self.in_features / 2;
8768 let scale_stride = self.out_features * self.in_features / 16;
8769 if self.codes.len() != self.expert_count * code_stride
8770 || self.scales.len() != self.expert_count * scale_stride
8771 {
8772 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8773 }
8774 let matrix = Nvfp4BlockMatrix {
8775 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8776 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8777 macro_scale: self.macros[expert],
8778 out_features: self.out_features,
8779 in_features: self.in_features,
8780 };
8781 matrix.validate()?;
8782 Ok(matrix)
8783 }
8784}
8785
8786pub struct ResidentNvfp4Rank {
8788 blocks: crate::CudaSlice<u8>,
8789 macro_scale: f32,
8790 out_features: usize,
8791 in_features: usize,
8792 row_bytes: usize,
8793}
8794
8795pub struct ResidentNvfp4ColumnParallel {
8796 ranks: Vec<ResidentNvfp4Rank>,
8797 pub out_features: usize,
8798 pub in_features: usize,
8799}
8800
8801pub struct ResidentNvfp4RowParallel {
8802 ranks: Vec<ResidentNvfp4Rank>,
8803 pub out_features: usize,
8804 pub in_features: usize,
8805}
8806
8807pub struct ResidentTpNvfp4Expert {
8808 gate: ResidentNvfp4ColumnParallel,
8809 up: ResidentNvfp4ColumnParallel,
8810 down: ResidentNvfp4RowParallel,
8811 pub input_width: usize,
8812 pub expert_width: usize,
8813}
8814
8815pub struct ResidentNvfp4ColumnBankRank {
8819 bank: crate::CudaSlice<u8>,
8823 expert_bytes: usize,
8824 local_out: usize,
8825 in_features: usize,
8826 row_bytes: usize,
8827}
8828
8829impl ResidentNvfp4ColumnBankRank {
8830 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8831 self.bank
8832 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8833 }
8834}
8835
8836pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8842
8843pub struct ResidentNvfp4RowBankRank {
8844 bank: crate::CudaSlice<u8>,
8846 expert_bytes: usize,
8847 device_rank: usize, out_features: usize,
8849 local_in: usize,
8850 row_bytes: usize,
8851}
8852
8853impl ResidentNvfp4RowBankRank {
8854 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8855 self.bank
8856 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8857 }
8858}
8859
8860impl ResidentNvfp4TensorParallel {
8861 pub(crate) fn device_workspace_handle(
8862 &self,
8863 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8864 &self.device_workspace
8865 }
8866}
8867
8868pub struct ResidentNvfp4TensorParallel {
8869 gate: Vec<ResidentNvfp4ColumnBankRank>,
8870 up: Vec<ResidentNvfp4ColumnBankRank>,
8871 down: Vec<ResidentNvfp4RowBankRank>,
8872 macros_gate: Vec<f32>,
8873 macros_up: Vec<f32>,
8874 macros_down: Vec<f32>,
8875 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8879 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8880 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8881 pub expert_count: usize,
8882 pub input_width: usize,
8883 pub expert_width: usize,
8884 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8887 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8891 pub(crate) ep2: bool,
8895}
8896
8897pub struct Nvfp4T2Workspace {
8901 input2: Vec<crate::CudaSlice<f32>>,
8902 in_q2: Vec<crate::CudaSlice<i8>>,
8903 in_d2: Vec<crate::CudaSlice<f32>>,
8904 sel2: Vec<crate::CudaSlice<i32>>,
8905 route_w2: Vec<crate::CudaSlice<f32>>,
8906 gate_out2: Vec<crate::CudaSlice<f32>>,
8907 up_out2: Vec<crate::CudaSlice<f32>>,
8908 act_q2: Vec<crate::CudaSlice<i8>>,
8909 act_d2: Vec<crate::CudaSlice<f32>>,
8910 partial2: Vec<crate::CudaSlice<f32>>,
8911 acc_a: Vec<crate::CudaSlice<f32>>,
8913 acc_b: Vec<crate::CudaSlice<f32>>,
8914 acc2: Vec<crate::CudaSlice<f32>>,
8917 peer2: crate::CudaSlice<f32>,
8918 omix2: crate::CudaSlice<f32>,
8919 peer_a: crate::CudaSlice<f32>,
8921 peer_b: crate::CudaSlice<f32>,
8922 omix_a: crate::CudaSlice<f32>,
8923 omix_b: crate::CudaSlice<f32>,
8924 ev_entry: CudaEvent,
8925 ev_rank: Vec<CudaEvent>,
8926 ev_root: CudaEvent,
8927 n_sel: usize,
8928 e_device: usize,
8929}
8930
8931struct RoutesGraph {
8938 exec: cudarc::driver::sys::CUgraphExec,
8939 parent: cudarc::driver::sys::CUgraph,
8940 _children: Vec<cudarc::driver::CudaGraph>,
8941}
8942unsafe impl Send for RoutesGraph {}
8945
8946impl Drop for RoutesGraph {
8947 fn drop(&mut self) {
8948 unsafe {
8949 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8950 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8951 }
8952 }
8953}
8954
8955impl Nvfp4DeviceRoutesWorkspace {
8956 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8957 self.in_stage_e.as_ref()
8958 }
8959 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8960 self.in_stage_e.as_mut()
8961 }
8962 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8963 self.out_stage_e.as_mut()
8964 }
8965 pub(crate) fn arm_stages(
8967 &mut self,
8968 e: &Engine,
8969 width: usize,
8970 n_sel: usize,
8971 ) -> Result<(), Box<dyn std::error::Error>> {
8972 let _main = e.gpu.enter_main()?;
8973 if self.in_stage_e.is_none() {
8974 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8975 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8976 }
8977 if self.dev_route_e.is_none() {
8978 self.dev_route_e = Some((
8979 e.htod_i32(&vec![0i32; n_sel])?,
8980 e.htod(&vec![0.0f32; n_sel])?,
8981 ));
8982 }
8983 Ok(())
8984 }
8985
8986 pub(crate) fn in_and_out_stages_mut(
8988 &mut self,
8989 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
8990 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
8991 (Some(input), Some(output)) => Some((input, output)),
8992 _ => None,
8993 }
8994 }
8995 pub(crate) fn dev_route_e_mut(
8996 &mut self,
8997 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
8998 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
8999 }
9000}
9001
9002pub struct Nvfp4DeviceRoutesWorkspace {
9003 gate_out: Vec<crate::CudaSlice<f32>>,
9006 up_out: Vec<crate::CudaSlice<f32>>,
9007 act_q: Vec<crate::CudaSlice<i8>>,
9008 act_d: Vec<crate::CudaSlice<f32>>,
9009 sel: Vec<crate::CudaSlice<i32>>,
9010 partial: Vec<crate::CudaSlice<f32>>,
9011 accumulator: Vec<crate::CudaSlice<f32>>,
9012 combine_w: Vec<crate::CudaSlice<f32>>,
9014 route_w: Vec<crate::CudaSlice<f32>>,
9017 in_q: Vec<crate::CudaSlice<i8>>,
9020 in_d: Vec<crate::CudaSlice<f32>>,
9021 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9025 prestaged: bool,
9028 rank1_routed: bool,
9031 fence_flags_raw: u64,
9035 fence_ticket: u32,
9036 ev_input: Option<(CudaEvent, usize)>,
9038 in_stage_e: Option<crate::CudaSlice<f32>>,
9041 out_stage_e: Option<crate::CudaSlice<f32>>,
9042 routes_graph: Option<RoutesGraph>,
9043 raw_dev_route_e: Option<(u64, u64)>,
9045 raw_combine: Option<(u64, u64, u64, u64)>,
9046 raw_input: Vec<u64>,
9047 raw_sel: Vec<u64>,
9048 raw_route_w: Vec<u64>,
9049 remote: crate::CudaSlice<f32>,
9050 combined: crate::CudaSlice<f32>,
9051 n_sel: usize,
9052 input: Vec<crate::CudaSlice<f32>>,
9056 ev_rank: Vec<CudaEvent>,
9057 ev_done: Option<CudaEvent>,
9058 ev_entry: Option<(CudaEvent, usize)>,
9059}
9060
9061struct ResidentNvfp4EpRank {
9063 gate: Vec<crate::CudaSlice<u8>>,
9064 up: Vec<crate::CudaSlice<u8>>,
9065 down: Vec<crate::CudaSlice<u8>>,
9066 #[allow(dead_code)]
9067 expert_range: Range<usize>,
9068}
9069
9070pub struct ResidentNvfp4ExpertParallel {
9071 ranks: Vec<ResidentNvfp4EpRank>,
9072 macros_gate: Vec<f32>,
9073 macros_up: Vec<f32>,
9074 macros_down: Vec<f32>,
9075 pub expert_count: usize,
9076 pub input_width: usize,
9077 pub expert_width: usize,
9078 gate_row_bytes: usize,
9079 down_row_bytes: usize,
9080}
9081
9082fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9083 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9084 matrix.codes,
9085 matrix.scales,
9086 matrix.out_features,
9087 matrix.in_features,
9088 )
9089}
9090
9091fn nvfp4_row_bytes(in_features: usize) -> usize {
9092 in_features / 64 * 36 }
9094
9095pub(crate) fn fuse_rope_append_on() -> bool {
9103 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9104 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9105}
9106
9107pub(crate) fn no_local_shadow_on() -> bool {
9108 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9109 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9110}
9111
9112pub(crate) fn nvfp4_bank_v2_on() -> bool {
9113 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9114 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
9115}
9116
9117fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9121 let row_bytes = nvfp4_row_bytes(in_features);
9122 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
9123 let n_slots = in_features / 32;
9124 let mut out = Vec::with_capacity(v1.len());
9125 for row in 0..out_features {
9126 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
9127 for g in 0..n_slots {
9128 let (sblk, h) = (g / 2, g % 2);
9129 let b = &r[sblk * 36..sblk * 36 + 36];
9130 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
9131 }
9132 for g in 0..n_slots {
9133 let (sblk, h) = (g / 2, g % 2);
9134 let b = &r[sblk * 36..sblk * 36 + 36];
9135 out.push(b[2 * h]);
9136 out.push(b[2 * h + 1]);
9137 }
9138 }
9139 out
9140}
9141
9142fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9144 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9145 let v1 = nvfp4_repack_matrix(matrix);
9146 if nvfp4_bank_v2_on() {
9147 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9148 } else {
9149 v1
9150 }
9151}
9152
9153fn nvfp4_column_shard<'a>(
9156 matrix: Nvfp4BlockMatrix<'a>,
9157 tp: usize,
9158 rank: usize,
9159) -> Result<Nvfp4BlockMatrix<'a>, String> {
9160 if matrix.out_features % tp != 0 {
9161 return Err(format!(
9162 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
9163 matrix.out_features
9164 ));
9165 }
9166 let local_out = matrix.out_features / tp;
9167 let code_row = matrix.in_features / 2;
9168 let scale_row = matrix.in_features / 16;
9169 Ok(Nvfp4BlockMatrix {
9170 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
9171 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
9172 macro_scale: matrix.macro_scale,
9173 out_features: local_out,
9174 in_features: matrix.in_features,
9175 })
9176}
9177
9178fn nvfp4_row_shard(
9181 matrix: Nvfp4BlockMatrix<'_>,
9182 tp: usize,
9183 rank: usize,
9184) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
9185 if matrix.in_features % tp != 0 {
9186 return Err(format!(
9187 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
9188 matrix.in_features
9189 ));
9190 }
9191 let local_in = matrix.in_features / tp;
9192 if local_in % 64 != 0 {
9193 return Err(format!(
9194 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
9195 ));
9196 }
9197 let code_row = matrix.in_features / 2;
9198 let scale_row = matrix.in_features / 16;
9199 let local_code = local_in / 2;
9200 let local_scale = local_in / 16;
9201 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
9202 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
9203 for row in 0..matrix.out_features {
9204 let code_start = row * code_row + rank * local_code;
9205 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
9206 let scale_start = row * scale_row + rank * local_scale;
9207 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
9208 }
9209 Ok((codes, scales, local_in))
9210}
9211
9212fn run_rank_nvfp4(
9216 engine: &Engine,
9217 matrix: Nvfp4BlockMatrix<'_>,
9218 activations: &[f32],
9219 tokens: usize,
9220) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9221 matrix.validate()?;
9222 validate_activations(activations, tokens, matrix.in_features)?;
9223 let _main = engine.gpu.enter_main()?;
9224 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
9225 let activations = engine.htod(activations)?;
9226 let output = engine.qmatvec_nvfp4_fast(
9227 &blocks.slice(0..blocks.len()),
9228 &activations,
9229 tokens,
9230 matrix.in_features,
9231 matrix.out_features,
9232 nvfp4_row_bytes(matrix.in_features),
9233 )?;
9234 engine.dtoh(&output)
9235}
9236
9237fn upload_rank_nvfp4(
9238 engine: &Engine,
9239 matrix: Nvfp4BlockMatrix<'_>,
9240) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
9241 matrix.validate()?;
9242 let _main = engine.gpu.enter_main()?;
9243 Ok(ResidentNvfp4Rank {
9244 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
9245 macro_scale: matrix.macro_scale,
9246 out_features: matrix.out_features,
9247 in_features: matrix.in_features,
9248 row_bytes: nvfp4_row_bytes(matrix.in_features),
9249 })
9250}
9251
9252fn run_resident_rank_nvfp4(
9253 engine: &Engine,
9254 rank: &ResidentNvfp4Rank,
9255 activations: &[f32],
9256 tokens: usize,
9257) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9258 validate_activations(activations, tokens, rank.in_features)?;
9259 let _main = engine.gpu.enter_main()?;
9260 let activations = engine.htod(activations)?;
9261 let output = engine.qmatvec_nvfp4_fast(
9262 &rank.blocks.slice(0..rank.blocks.len()),
9263 &activations,
9264 tokens,
9265 rank.in_features,
9266 rank.out_features,
9267 rank.row_bytes,
9268 )?;
9269 engine.dtoh(&output)
9270}
9271
9272fn apply_macro(values: &mut [f32], macro_scale: f32) {
9273 for value in values.iter_mut() {
9274 *value *= macro_scale;
9275 }
9276}
9277
9278impl TpE4m3HostBounce {
9279 pub fn full_nvfp4(
9281 &self,
9282 matrix: Nvfp4BlockMatrix<'_>,
9283 activations: &[f32],
9284 tokens: usize,
9285 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9286 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
9287 apply_macro(&mut output, matrix.macro_scale);
9288 Ok(output)
9289 }
9290
9291 pub fn column_parallel_nvfp4(
9294 &self,
9295 matrix: Nvfp4BlockMatrix<'_>,
9296 activations: &[f32],
9297 tokens: usize,
9298 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
9299 matrix.validate()?;
9300 validate_activations(activations, tokens, matrix.in_features)?;
9301 let tp = self.ranks.len();
9302 let local_out = matrix.out_features / tp;
9303 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9304 let mut rank_outputs = Vec::with_capacity(tp);
9305 for (rank_index, rank) in self.ranks.iter().enumerate() {
9306 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
9307 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
9308 let row_start = rank_index * local_out;
9309 for token in 0..tokens {
9310 gathered[token * matrix.out_features + row_start
9311 ..token * matrix.out_features + row_start + local_out]
9312 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9313 }
9314 rank_outputs.push(output);
9315 }
9316 apply_macro(&mut gathered, matrix.macro_scale);
9317 Ok(ColumnParallelResult {
9318 gathered,
9319 rank_outputs,
9320 })
9321 }
9322
9323 pub fn row_parallel_nvfp4(
9326 &self,
9327 matrix: Nvfp4BlockMatrix<'_>,
9328 activations: &[f32],
9329 tokens: usize,
9330 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
9331 matrix.validate()?;
9332 validate_activations(activations, tokens, matrix.in_features)?;
9333 let tp = self.ranks.len();
9334 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9335 let mut rank_partials = Vec::with_capacity(tp);
9336 for (rank_index, rank) in self.ranks.iter().enumerate() {
9337 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
9338 let local_activations =
9339 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9340 let shard = Nvfp4BlockMatrix {
9341 codes: &codes,
9342 scales: &scales,
9343 macro_scale: matrix.macro_scale,
9344 out_features: matrix.out_features,
9345 in_features: local_in,
9346 };
9347 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
9348 for (sum, value) in reduced.iter_mut().zip(&partial) {
9349 *sum += *value;
9350 }
9351 rank_partials.push(partial);
9352 }
9353 apply_macro(&mut reduced, matrix.macro_scale);
9354 Ok(RowParallelResult {
9355 reduced,
9356 rank_partials,
9357 })
9358 }
9359
9360 pub fn upload_expert_nvfp4(
9361 &self,
9362 gate: Nvfp4BlockMatrix<'_>,
9363 up: Nvfp4BlockMatrix<'_>,
9364 down: Nvfp4BlockMatrix<'_>,
9365 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
9366 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9367 return Err("NVFP4 TP expert gate/up dimensions differ".into());
9368 }
9369 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9370 return Err(format!(
9371 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
9372 down.out_features, down.in_features, gate.out_features, gate.in_features
9373 )
9374 .into());
9375 }
9376 let tp = self.ranks.len();
9377 let mut gate_ranks = Vec::with_capacity(tp);
9378 let mut up_ranks = Vec::with_capacity(tp);
9379 let mut down_ranks = Vec::with_capacity(tp);
9380 for (rank_index, engine) in self.ranks.iter().enumerate() {
9381 gate_ranks.push(upload_rank_nvfp4(
9382 engine,
9383 nvfp4_column_shard(gate, tp, rank_index)?,
9384 )?);
9385 up_ranks.push(upload_rank_nvfp4(
9386 engine,
9387 nvfp4_column_shard(up, tp, rank_index)?,
9388 )?);
9389 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
9390 down_ranks.push(upload_rank_nvfp4(
9391 engine,
9392 Nvfp4BlockMatrix {
9393 codes: &codes,
9394 scales: &scales,
9395 macro_scale: down.macro_scale,
9396 out_features: down.out_features,
9397 in_features: local_in,
9398 },
9399 )?);
9400 }
9401 Ok(ResidentTpNvfp4Expert {
9402 gate: ResidentNvfp4ColumnParallel {
9403 ranks: gate_ranks,
9404 out_features: gate.out_features,
9405 in_features: gate.in_features,
9406 },
9407 up: ResidentNvfp4ColumnParallel {
9408 ranks: up_ranks,
9409 out_features: up.out_features,
9410 in_features: up.in_features,
9411 },
9412 down: ResidentNvfp4RowParallel {
9413 ranks: down_ranks,
9414 out_features: down.out_features,
9415 in_features: down.in_features,
9416 },
9417 input_width: gate.in_features,
9418 expert_width: gate.out_features,
9419 })
9420 }
9421
9422 fn column_parallel_resident_nvfp4(
9423 &self,
9424 matrix: &ResidentNvfp4ColumnParallel,
9425 activations: &[f32],
9426 tokens: usize,
9427 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9428 validate_activations(activations, tokens, matrix.in_features)?;
9429 let local_out = matrix.out_features / self.ranks.len();
9430 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9431 let mut macro_scale = None;
9432 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9433 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
9434 let row_start = rank_index * local_out;
9435 for token in 0..tokens {
9436 gathered[token * matrix.out_features + row_start
9437 ..token * matrix.out_features + row_start + local_out]
9438 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9439 }
9440 macro_scale = Some(shard.macro_scale);
9441 }
9442 apply_macro(
9443 &mut gathered,
9444 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
9445 );
9446 Ok(gathered)
9447 }
9448
9449 fn row_parallel_resident_nvfp4(
9450 &self,
9451 matrix: &ResidentNvfp4RowParallel,
9452 activations: &[f32],
9453 tokens: usize,
9454 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9455 validate_activations(activations, tokens, matrix.in_features)?;
9456 let tp = self.ranks.len();
9457 let local_in = matrix.in_features / tp;
9458 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9459 let mut macro_scale = None;
9460 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9461 if shard.in_features != local_in {
9462 return Err(format!(
9463 "NVFP4 resident row shard in_features {} != expected {local_in}",
9464 shard.in_features
9465 )
9466 .into());
9467 }
9468 let local_activations =
9469 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9470 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9471 for (sum, value) in reduced.iter_mut().zip(&partial) {
9472 *sum += *value;
9473 }
9474 macro_scale = Some(shard.macro_scale);
9475 }
9476 apply_macro(
9477 &mut reduced,
9478 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9479 );
9480 Ok(reduced)
9481 }
9482
9483 pub fn run_expert_nvfp4(
9484 &self,
9485 expert: &ResidentTpNvfp4Expert,
9486 input: &[f32],
9487 tokens: usize,
9488 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9489 validate_activations(input, tokens, expert.input_width)?;
9490 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9491 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9492 let activated: Vec<f32> = gate
9493 .iter()
9494 .zip(&up)
9495 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9496 .collect();
9497 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9498 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9499 }
9500
9501 pub fn upload_tensor_parallel_nvfp4(
9503 &self,
9504 gate: Nvfp4ExpertBank<'_>,
9505 up: Nvfp4ExpertBank<'_>,
9506 down: Nvfp4ExpertBank<'_>,
9507 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9508 gate.validate()?;
9509 up.validate()?;
9510 down.validate()?;
9511 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9512 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9513 }
9514 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9515 return Err("NVFP4 TP gate/up dimensions differ".into());
9516 }
9517 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9518 return Err(format!(
9519 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9520 down.out_features, down.in_features, gate.out_features, gate.in_features
9521 )
9522 .into());
9523 }
9524 let tp = self.ranks.len();
9525 if gate.out_features % tp != 0 {
9526 return Err(format!(
9527 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9528 gate.out_features
9529 )
9530 .into());
9531 }
9532 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9533 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9534 {
9535 return Err(format!(
9536 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9537 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9538 down.in_features
9539 )
9540 .into());
9541 }
9542 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9543 return Err(format!(
9544 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9545 ({NVFP4_CANONICAL_ROW_SHARDS})"
9546 )
9547 .into());
9548 }
9549
9550 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9551 let mut gate_ranks = Vec::with_capacity(tp);
9552 let mut up_ranks = Vec::with_capacity(tp);
9553 let mut macros_gate_dev = Vec::with_capacity(tp);
9554 let mut macros_up_dev = Vec::with_capacity(tp);
9555 let mut macros_down_dev = Vec::with_capacity(tp);
9556 for (rank_index, engine) in self.ranks.iter().enumerate() {
9557 let _main = engine.gpu.enter_main()?;
9558 let mut gate_host: Vec<u8> = Vec::new();
9564 let mut up_host: Vec<u8> = Vec::new();
9565 let mut owned = 0usize;
9566 for expert in 0..gate.expert_count {
9567 if ep2 {
9568 if expert % 2 != rank_index {
9569 continue;
9570 }
9571 owned += 1;
9572 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
9573 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
9574 } else {
9575 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9576 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
9577 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9578 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
9579 }
9580 }
9581 let bank_experts = if ep2 { owned } else { gate.expert_count };
9582 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9583 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9584 let local_out = if ep2 {
9585 gate.out_features
9586 } else {
9587 gate.out_features / tp
9588 };
9589 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9590 bank: engine.htod_bytes(&gate_host)?,
9591 expert_bytes: gate_expert_bytes,
9592 local_out,
9593 in_features: gate.in_features,
9594 row_bytes: nvfp4_row_bytes(gate.in_features),
9595 });
9596 up_ranks.push(ResidentNvfp4ColumnBankRank {
9597 bank: engine.htod_bytes(&up_host)?,
9598 expert_bytes: up_expert_bytes,
9599 local_out,
9600 in_features: up.in_features,
9601 row_bytes: nvfp4_row_bytes(up.in_features),
9602 });
9603 macros_gate_dev.push(engine.htod(gate.macros)?);
9604 macros_up_dev.push(engine.htod(up.macros)?);
9605 macros_down_dev.push(engine.htod(down.macros)?);
9606 }
9607 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9611 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9612 let device_rank = shard_index % tp;
9613 let engine = &self.ranks[device_rank];
9614 let _main = engine.gpu.enter_main()?;
9615 let mut down_host: Vec<u8> = Vec::new();
9616 let mut owned = 0usize;
9617 for expert in 0..down.expert_count {
9618 let down_matrix = down.expert(expert)?;
9619 if ep2 {
9620 if expert % 2 != device_rank {
9623 continue;
9624 }
9625 owned += 1;
9626 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
9627 } else {
9628 let (codes, scales, local_in) =
9629 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9630 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
9631 codes: &codes,
9632 scales: &scales,
9633 macro_scale: down_matrix.macro_scale,
9634 out_features: down_matrix.out_features,
9635 in_features: local_in,
9636 }));
9637 }
9638 }
9639 let bank_experts = if ep2 { owned } else { down.expert_count };
9640 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9641 let local_in = if ep2 {
9642 down.in_features
9643 } else {
9644 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9645 };
9646 down_ranks.push(ResidentNvfp4RowBankRank {
9647 bank: engine.htod_bytes(&down_host)?,
9648 expert_bytes: down_expert_bytes,
9649 device_rank,
9650 out_features: down.out_features,
9651 local_in,
9652 row_bytes: nvfp4_row_bytes(local_in),
9653 });
9654 }
9655 Ok(ResidentNvfp4TensorParallel {
9656 gate: gate_ranks,
9657 up: up_ranks,
9658 down: down_ranks,
9659 macros_gate: gate.macros.to_vec(),
9660 macros_up: up.macros.to_vec(),
9661 macros_down: down.macros.to_vec(),
9662 macros_gate_dev,
9663 macros_up_dev,
9664 macros_down_dev,
9665 expert_count: gate.expert_count,
9666 input_width: gate.in_features,
9667 expert_width: gate.out_features,
9668 device_workspace: std::sync::Mutex::new(None),
9669 t2_workspace: std::sync::Mutex::new(None),
9670 ep2,
9671 })
9672 }
9673
9674 fn run_full_bank_expert_nvfp4(
9678 &self,
9679 ranks: &[ResidentNvfp4ColumnBankRank],
9680 macros: &[f32],
9681 expert: usize,
9682 input: &[f32],
9683 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9684 let owner = expert & 1;
9685 let slot = expert >> 1;
9686 let bank = ranks
9687 .get(owner)
9688 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9689 let engine = &self.ranks[owner];
9690 let _main = engine.gpu.enter_main()?;
9691 let activations = engine.htod(input)?;
9692 let output = if nvfp4_bank_v2_on() {
9693 engine.qmatvec_nvfp4_fast_v2(
9694 &bank.expert(slot),
9695 &activations,
9696 1,
9697 bank.in_features,
9698 bank.local_out,
9699 bank.row_bytes,
9700 )?
9701 } else {
9702 engine.qmatvec_nvfp4_fast(
9703 &bank.expert(slot),
9704 &activations,
9705 1,
9706 bank.in_features,
9707 bank.local_out,
9708 bank.row_bytes,
9709 )?
9710 };
9711 let mut out = engine.dtoh(&output)?;
9712 apply_macro(&mut out, macros[expert]);
9713 Ok(out)
9714 }
9715
9716 fn run_full_down_expert_nvfp4(
9719 &self,
9720 shards: &[ResidentNvfp4RowBankRank],
9721 macros: &[f32],
9722 expert: usize,
9723 input: &[f32],
9724 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9725 let owner = expert & 1;
9726 let slot = expert >> 1;
9727 let shard = shards
9728 .get(owner)
9729 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9730 let engine = &self.ranks[owner];
9731 let _main = engine.gpu.enter_main()?;
9732 let activations = engine.htod(input)?;
9733 let output = if nvfp4_bank_v2_on() {
9734 engine.qmatvec_nvfp4_fast_v2(
9735 &shard.expert(slot),
9736 &activations,
9737 1,
9738 shard.local_in,
9739 shard.out_features,
9740 shard.row_bytes,
9741 )?
9742 } else {
9743 engine.qmatvec_nvfp4_fast(
9744 &shard.expert(slot),
9745 &activations,
9746 1,
9747 shard.local_in,
9748 shard.out_features,
9749 shard.row_bytes,
9750 )?
9751 };
9752 let mut out = engine.dtoh(&output)?;
9753 apply_macro(&mut out, macros[expert]);
9754 Ok(out)
9755 }
9756
9757 fn run_column_bank_expert_nvfp4(
9758 &self,
9759 ranks: &[ResidentNvfp4ColumnBankRank],
9760 macros: &[f32],
9761 expert: usize,
9762 input: &[f32],
9763 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9764 let local_out = ranks
9765 .first()
9766 .ok_or("NVFP4 TP column bank has no ranks")?
9767 .local_out;
9768 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9769 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9770 let _main = engine.gpu.enter_main()?;
9771 let activations = engine.htod(input)?;
9772 let output = if nvfp4_bank_v2_on() {
9773 engine.qmatvec_nvfp4_fast_v2(
9774 &bank.expert(expert),
9775 &activations,
9776 1,
9777 bank.in_features,
9778 bank.local_out,
9779 bank.row_bytes,
9780 )?
9781 } else {
9782 engine.qmatvec_nvfp4_fast(
9783 &bank.expert(expert),
9784 &activations,
9785 1,
9786 bank.in_features,
9787 bank.local_out,
9788 bank.row_bytes,
9789 )?
9790 };
9791 let output = engine.dtoh(&output)?;
9792 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9793 }
9794 apply_macro(&mut gathered, macros[expert]);
9795 Ok(gathered)
9796 }
9797
9798 fn run_row_bank_expert_nvfp4(
9802 &self,
9803 shards: &[ResidentNvfp4RowBankRank],
9804 macros: &[f32],
9805 expert: usize,
9806 input: &[f32],
9807 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9808 let out_features = shards
9809 .first()
9810 .ok_or("NVFP4 TP row bank has no canonical shards")?
9811 .out_features;
9812 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
9813 let mut reduced = vec![0.0f32; out_features];
9814 for (shard_index, shard) in shards.iter().enumerate() {
9815 let engine = self
9816 .ranks
9817 .get(shard.device_rank)
9818 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
9819 let _main = engine.gpu.enter_main()?;
9820 let local_activations =
9821 activation_shard(input, 1, in_features, shards.len(), shard_index);
9822 let activations = engine.htod(&local_activations)?;
9823 let output = if nvfp4_bank_v2_on() {
9824 engine.qmatvec_nvfp4_fast_v2(
9825 &shard.expert(expert),
9826 &activations,
9827 1,
9828 shard.local_in,
9829 shard.out_features,
9830 shard.row_bytes,
9831 )?
9832 } else {
9833 engine.qmatvec_nvfp4_fast(
9834 &shard.expert(expert),
9835 &activations,
9836 1,
9837 shard.local_in,
9838 shard.out_features,
9839 shard.row_bytes,
9840 )?
9841 };
9842 let partial = engine.dtoh(&output)?;
9843 for (sum, value) in reduced.iter_mut().zip(&partial) {
9844 *sum += *value;
9845 }
9846 }
9847 apply_macro(&mut reduced, macros[expert]);
9848 Ok(reduced)
9849 }
9850
9851 pub fn upload_expert_parallel_nvfp4(
9855 &self,
9856 gate: Nvfp4ExpertBank<'_>,
9857 up: Nvfp4ExpertBank<'_>,
9858 down: Nvfp4ExpertBank<'_>,
9859 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
9860 gate.validate()?;
9861 up.validate()?;
9862 down.validate()?;
9863 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9864 return Err("NVFP4 EP gate/up/down expert counts differ".into());
9865 }
9866 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9867 return Err("NVFP4 EP gate/up dimensions differ".into());
9868 }
9869 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9870 return Err(format!(
9871 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9872 down.out_features, down.in_features, gate.out_features, gate.in_features
9873 )
9874 .into());
9875 }
9876 let world = self.ranks.len();
9877 if gate.expert_count % world != 0 {
9878 return Err(format!(
9879 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9880 gate.expert_count
9881 )
9882 .into());
9883 }
9884 let experts_per_rank = gate.expert_count / world;
9885 let mut ranks = Vec::with_capacity(world);
9886 for (rank_index, engine) in self.ranks.iter().enumerate() {
9887 let _main = engine.gpu.enter_main()?;
9888 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9889 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9890 let mut up_experts = Vec::with_capacity(experts_per_rank);
9891 let mut down_experts = Vec::with_capacity(experts_per_rank);
9892 for expert in expert_range.clone() {
9893 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9894 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9895 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9896 }
9897 ranks.push(ResidentNvfp4EpRank {
9898 gate: gate_experts,
9899 up: up_experts,
9900 down: down_experts,
9901 expert_range,
9902 });
9903 }
9904 Ok(ResidentNvfp4ExpertParallel {
9905 ranks,
9906 macros_gate: gate.macros.to_vec(),
9907 macros_up: up.macros.to_vec(),
9908 macros_down: down.macros.to_vec(),
9909 expert_count: gate.expert_count,
9910 input_width: gate.in_features,
9911 expert_width: gate.out_features,
9912 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9913 down_row_bytes: nvfp4_row_bytes(down.in_features),
9914 })
9915 }
9916
9917 #[allow(clippy::too_many_arguments)]
9923 pub fn run_routed_experts_nvfp4(
9924 &self,
9925 experts: &ResidentNvfp4ExpertParallel,
9926 input: &[f32],
9927 tokens: usize,
9928 selected: &[usize],
9929 route_weights: &[f32],
9930 experts_per_token: usize,
9931 activation_limit: Option<f32>,
9932 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9933 validate_activations(input, tokens, experts.input_width)?;
9934 let pairs = tokens
9935 .checked_mul(experts_per_token)
9936 .ok_or("NVFP4 EP route count overflow")?;
9937 if selected.len() != pairs || route_weights.len() != pairs {
9938 return Err(format!(
9939 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9940 {experts_per_token} ({pairs})",
9941 selected.len(),
9942 route_weights.len(),
9943 )
9944 .into());
9945 }
9946 if !route_weights.iter().all(|weight| weight.is_finite()) {
9947 return Err("NVFP4 EP route weights contain a non-finite value".into());
9948 }
9949 let experts_per_rank = experts.expert_count / experts.ranks.len();
9950 let mut output = vec![0.0f32; tokens * experts.input_width];
9951 for token in 0..tokens {
9952 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9953 for slot in 0..experts_per_token {
9954 let pair = token * experts_per_token + slot;
9955 let expert = selected[pair];
9956 if expert >= experts.expert_count {
9957 return Err(format!(
9958 "NVFP4 EP selected expert {expert} outside 0..{}",
9959 experts.expert_count
9960 )
9961 .into());
9962 }
9963 let owner = expert / experts_per_rank;
9964 let local = expert - owner * experts_per_rank;
9965 let rank = &experts.ranks[owner];
9966 let engine = &self.ranks[owner];
9967 let _main = engine.gpu.enter_main()?;
9968 let device_input = engine.htod(input_row)?;
9969 let gate_out = engine.qmatvec_nvfp4_fast(
9970 &rank.gate[local].slice(0..rank.gate[local].len()),
9971 &device_input,
9972 1,
9973 experts.input_width,
9974 experts.expert_width,
9975 experts.gate_row_bytes,
9976 )?;
9977 let up_out = engine.qmatvec_nvfp4_fast(
9978 &rank.up[local].slice(0..rank.up[local].len()),
9979 &device_input,
9980 1,
9981 experts.input_width,
9982 experts.expert_width,
9983 experts.gate_row_bytes,
9984 )?;
9985 let mut gate_host = engine.dtoh(&gate_out)?;
9986 let mut up_host = engine.dtoh(&up_out)?;
9987 apply_macro(&mut gate_host, experts.macros_gate[expert]);
9988 apply_macro(&mut up_host, experts.macros_up[expert]);
9989 let activated: Vec<f32> = gate_host
9990 .iter()
9991 .zip(&up_host)
9992 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
9993 .collect();
9994 let device_activated = engine.htod(&activated)?;
9995 let down_out = engine.qmatvec_nvfp4_fast(
9996 &rank.down[local].slice(0..rank.down[local].len()),
9997 &device_activated,
9998 1,
9999 experts.expert_width,
10000 experts.input_width,
10001 experts.down_row_bytes,
10002 )?;
10003 let mut down_host = engine.dtoh(&down_out)?;
10004 apply_macro(&mut down_host, experts.macros_down[expert]);
10005 let weight = route_weights[pair];
10006 for (sum, value) in output
10007 [token * experts.input_width..(token + 1) * experts.input_width]
10008 .iter_mut()
10009 .zip(down_host)
10010 {
10011 *sum += weight * value;
10012 }
10013 }
10014 }
10015 Ok(output)
10016 }
10017
10018 pub fn run_tensor_parallel_routes_nvfp4_device(
10032 &self,
10033 experts: &ResidentNvfp4TensorParallel,
10034 input: &[f32],
10035 selected: &[usize],
10036 route_weights: &[f32],
10037 experts_per_token: usize,
10038 activation_limit: Option<f32>,
10039 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10040 validate_activations(input, 1, experts.input_width)?;
10041 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10042 return Err(format!(
10043 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
10044 selected.len(),
10045 route_weights.len(),
10046 )
10047 .into());
10048 }
10049 if !route_weights.iter().all(|weight| weight.is_finite()) {
10050 return Err("NVFP4 device route weights contain a non-finite value".into());
10051 }
10052 let world = self.ranks.len();
10053 if world != NVFP4_CANONICAL_ROW_SHARDS {
10054 return Err(format!(
10055 "NVFP4 device routes require world == canonical shard grid \
10056 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10057 )
10058 .into());
10059 }
10060 let local_out = if experts.ep2 {
10061 experts.expert_width
10062 } else {
10063 experts.expert_width / world
10064 };
10065
10066 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10070 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10071 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10072 let started = timing.then(std::time::Instant::now);
10073
10074 let n_sel = experts_per_token;
10075 let mut workspace_guard = experts
10076 .device_workspace
10077 .lock()
10078 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10079 if workspace_guard.is_none() {
10080 let mut gate_out = Vec::with_capacity(world);
10081 let mut up_out = Vec::with_capacity(world);
10082 let mut act_q = Vec::with_capacity(world);
10083 let mut act_d = Vec::with_capacity(world);
10084 let mut sel = Vec::with_capacity(world);
10085 let mut partial = Vec::with_capacity(world);
10086 let mut accumulator = Vec::with_capacity(world);
10087 let mut combine_w = Vec::with_capacity(world);
10088 let mut route_w = Vec::with_capacity(world);
10089 let mut in_q = Vec::with_capacity(world);
10090 let mut in_d = Vec::with_capacity(world);
10091 let mut input = Vec::with_capacity(world);
10092 let mut ev_rank = Vec::with_capacity(world);
10093 let moe_direct = moe_direct_on();
10094 for (rank, engine) in self.ranks.iter().enumerate() {
10095 let _main = engine.gpu.enter_main()?;
10096 gate_out.push(engine.uninit(n_sel * local_out)?);
10097 up_out.push(engine.uninit(n_sel * local_out)?);
10098 act_q.push(engine.uninit_i8(n_sel * local_out)?);
10099 act_d.push(engine.uninit(n_sel * local_out / 32)?);
10100 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
10101 partial.push(engine.uninit(n_sel * experts.input_width)?);
10102 if moe_direct && rank != 0 {
10104 let root = &self.ranks[0];
10105 let _root_main = root.gpu.enter_main()?;
10106 accumulator.push(root.zeros(experts.input_width)?);
10107 } else {
10108 accumulator.push(engine.zeros(experts.input_width)?);
10109 }
10110 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10111 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10112 in_q.push(engine.uninit_i8(experts.input_width)?);
10113 in_d.push(engine.uninit(experts.input_width / 32)?);
10114 input.push(engine.uninit(experts.input_width)?);
10115 ev_rank.push(engine.ctx().new_event(None)?);
10116 }
10117 let root = &self.ranks[0];
10118 let _main = root.gpu.enter_main()?;
10119 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
10120 prestaged: false,
10121 rank1_routed: false,
10122 ev_input: None,
10123 fence_flags_raw: 0,
10124 fence_ticket: 0,
10125 gate_out,
10126 up_out,
10127 act_q,
10128 act_d,
10129 sel,
10130 partial,
10131 accumulator,
10132 combine_w,
10133 route_w,
10134 in_q,
10135 in_d,
10136 dev_route_e: None,
10137 in_stage_e: None,
10138 out_stage_e: None,
10139 routes_graph: None,
10140 raw_dev_route_e: None,
10141 raw_combine: None,
10142 raw_input: Vec::new(),
10143 raw_sel: Vec::new(),
10144 raw_route_w: Vec::new(),
10145 remote: root.uninit(experts.input_width)?,
10146 combined: root.uninit(experts.input_width)?,
10147 n_sel,
10148 input,
10149 ev_rank,
10150 ev_done: Some(root.ctx().new_event(None)?),
10151 ev_entry: None,
10152 });
10153 }
10154 let workspace = workspace_guard
10155 .as_mut()
10156 .expect("NVFP4 device routes workspace initialized above");
10157 if experts.ep2 {
10160 return Ok(vec![0.0f32; experts.input_width]);
10161 }
10162 if workspace.n_sel != n_sel {
10163 return Err(format!(
10164 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10165 workspace.n_sel
10166 )
10167 .into());
10168 }
10169 for &expert in selected {
10170 if expert >= experts.expert_count {
10171 return Err(format!(
10172 "NVFP4 device selected expert {expert} outside 0..{}",
10173 experts.expert_count
10174 )
10175 .into());
10176 }
10177 }
10178 let sel_i32 = selected
10179 .iter()
10180 .map(|&expert| expert as i32)
10181 .collect::<Vec<_>>();
10182
10183 for (rank_index, engine) in self.ranks.iter().enumerate() {
10190 let _main = engine.gpu.enter_main()?;
10191 let device_input = engine.htod(input)?;
10192 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
10193 engine.quantize_q8_1_into(
10194 &device_input,
10195 1,
10196 experts.input_width,
10197 &mut in_q[rank_index],
10198 &mut in_d[rank_index],
10199 )?;
10200 }
10202 self.nvfp4_routes_batched_sweeps(
10203 experts,
10204 workspace,
10205 selected,
10206 route_weights,
10207 &sel_i32,
10208 local_out,
10209 n_sel,
10210 activation_limit,
10211 false,
10212 )?;
10213
10214 let root = &self.ranks[0];
10217 for engine in &self.ranks[1..] {
10218 let _main = engine.gpu.enter_main()?;
10219 engine.stream().synchronize()?;
10220 }
10221 let _main = root.gpu.enter_main()?;
10222 root.stream()
10223 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10224 root.add(
10225 &workspace.accumulator[0],
10226 &workspace.remote,
10227 &mut workspace.combined,
10228 experts.input_width,
10229 )?;
10230 let output = root.dtoh(&workspace.combined)?;
10231 if let Some(started) = started {
10232 use std::sync::atomic::Ordering;
10233 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10234 + started.elapsed().as_nanos() as u64;
10235 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10236 if calls % 430 == 0 {
10237 eprintln!(
10238 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10239 ns as f64 / 1.0e6,
10240 ns as f64 / calls as f64 / 1.0e3,
10241 );
10242 }
10243 }
10244 Ok(output)
10245 }
10246
10247 #[allow(clippy::too_many_arguments)]
10252 fn nvfp4_routes_batched_sweeps(
10253 &self,
10254 experts: &ResidentNvfp4TensorParallel,
10255 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10256 selected: &[usize],
10257 route_weights: &[f32],
10258 sel_i32: &[i32],
10259 local_out: usize,
10260 n_sel: usize,
10261 activation_limit: Option<f32>,
10262 device_routed: bool,
10263 ) -> Result<(), Box<dyn std::error::Error>> {
10264 for rank_index in 0..self.ranks.len() {
10265 self.nvfp4_routes_batched_sweeps_rank(
10266 experts,
10267 workspace,
10268 selected,
10269 route_weights,
10270 sel_i32,
10271 local_out,
10272 n_sel,
10273 activation_limit,
10274 device_routed,
10275 rank_index,
10276 )?;
10277 }
10278 Ok(())
10279 }
10280
10281 #[allow(clippy::too_many_arguments)]
10284 fn nvfp4_routes_batched_sweeps_rank(
10285 &self,
10286 experts: &ResidentNvfp4TensorParallel,
10287 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10288 selected: &[usize],
10289 route_weights: &[f32],
10290 sel_i32: &[i32],
10291 local_out: usize,
10292 n_sel: usize,
10293 activation_limit: Option<f32>,
10294 device_routed: bool,
10295 rank_index: usize,
10296 ) -> Result<(), Box<dyn std::error::Error>> {
10297 {
10298 let engine = &self.ranks[rank_index];
10299 let _main = engine.gpu.enter_main()?;
10300 if experts.ep2 {
10305 if !device_routed {
10306 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
10307 }
10308 let gate_bank = &experts.gate[rank_index];
10309 let up_bank = &experts.up[rank_index];
10310 if gate_bank.local_out != experts.expert_width
10311 || gate_bank.expert_bytes != up_bank.expert_bytes
10312 {
10313 return Err("NVFP4 EP2 bank geometry drifted".into());
10314 }
10315 {
10316 let Nvfp4DeviceRoutesWorkspace {
10317 sel,
10318 gate_out,
10319 up_out,
10320 in_q,
10321 in_d,
10322 ..
10323 } = &mut *workspace;
10324 engine.qmatvec_nvfp4_sel_gu_ep_into(
10325 &gate_bank.bank,
10326 &up_bank.bank,
10327 &sel[rank_index],
10328 &in_q[rank_index],
10329 &in_d[rank_index],
10330 &mut gate_out[rank_index],
10331 &mut up_out[rank_index],
10332 n_sel,
10333 gate_bank.in_features,
10334 gate_bank.local_out,
10335 gate_bank.row_bytes,
10336 gate_bank.expert_bytes,
10337 rank_index,
10338 )?;
10339 }
10340 {
10341 let Nvfp4DeviceRoutesWorkspace {
10342 gate_out,
10343 up_out,
10344 sel,
10345 act_q,
10346 act_d,
10347 ..
10348 } = &mut *workspace;
10349 engine.silu_mul_scaled_q8_1_sel_ep_into(
10350 &gate_out[rank_index],
10351 &up_out[rank_index],
10352 &experts.macros_gate_dev[rank_index],
10353 &experts.macros_up_dev[rank_index],
10354 &sel[rank_index],
10355 activation_limit,
10356 &mut act_q[rank_index],
10357 &mut act_d[rank_index],
10358 local_out,
10359 n_sel,
10360 rank_index,
10361 )?;
10362 }
10363 let shard = &experts.down[rank_index];
10364 if shard.device_rank != rank_index || shard.local_in != local_out {
10365 return Err("NVFP4 EP2 down bank placement drifted".into());
10366 }
10367 {
10368 let Nvfp4DeviceRoutesWorkspace {
10369 sel,
10370 act_q,
10371 act_d,
10372 route_w,
10373 accumulator,
10374 ..
10375 } = &mut *workspace;
10376 engine.qmatvec_nvfp4_sel_down8_ep_into(
10377 &shard.bank,
10378 &sel[rank_index],
10379 &act_q[rank_index],
10380 &act_d[rank_index],
10381 &route_w[rank_index],
10382 &experts.macros_down_dev[rank_index],
10383 &mut accumulator[rank_index],
10384 n_sel,
10385 shard.local_in,
10386 shard.out_features,
10387 shard.row_bytes,
10388 shard.expert_bytes,
10389 local_out,
10390 local_out / 32,
10391 rank_index,
10392 )?;
10393 }
10394 return Ok(());
10395 }
10396 if !device_routed {
10397 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
10398 let folded = (0..n_sel)
10401 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
10402 .collect::<Vec<_>>();
10403 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
10404 engine.stream().memcpy_htod(&folded, &mut view)?;
10405 }
10406 let gate_bank = &experts.gate[rank_index];
10407 let up_bank = &experts.up[rank_index];
10408 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
10409 let gu_fused = nvfp4_bank_v2_on()
10412 && gate_bank.in_features == up_bank.in_features
10413 && gate_bank.local_out == up_bank.local_out
10414 && gate_bank.row_bytes == up_bank.row_bytes
10415 && gate_bank.expert_bytes == up_bank.expert_bytes;
10416 if gu_fused {
10417 let Nvfp4DeviceRoutesWorkspace {
10418 sel,
10419 gate_out,
10420 up_out,
10421 in_q,
10422 in_d,
10423 ..
10424 } = &mut *workspace;
10425 engine.qmatvec_nvfp4_sel_gu_into(
10426 &gate_bank.bank,
10427 &up_bank.bank,
10428 &sel[rank_index],
10429 &in_q[rank_index],
10430 &in_d[rank_index],
10431 &mut gate_out[rank_index],
10432 &mut up_out[rank_index],
10433 n_sel,
10434 gate_bank.in_features,
10435 gate_bank.local_out,
10436 gate_bank.row_bytes,
10437 gate_bank.expert_bytes,
10438 )?;
10439 } else {
10440 engine.qmatvec_nvfp4_sel_into(
10441 &gate_bank.bank,
10442 &workspace.sel[rank_index],
10443 aq,
10444 ad,
10445 &mut workspace.gate_out[rank_index],
10446 n_sel,
10447 gate_bank.in_features,
10448 gate_bank.local_out,
10449 gate_bank.row_bytes,
10450 gate_bank.expert_bytes,
10451 0,
10452 0,
10453 )?;
10454 engine.qmatvec_nvfp4_sel_into(
10455 &up_bank.bank,
10456 &workspace.sel[rank_index],
10457 aq,
10458 ad,
10459 &mut workspace.up_out[rank_index],
10460 n_sel,
10461 up_bank.in_features,
10462 up_bank.local_out,
10463 up_bank.row_bytes,
10464 up_bank.expert_bytes,
10465 0,
10466 0,
10467 )?;
10468 }
10469 {
10473 let Nvfp4DeviceRoutesWorkspace {
10474 gate_out,
10475 up_out,
10476 sel,
10477 act_q,
10478 act_d,
10479 ..
10480 } = &mut *workspace;
10481 engine.silu_mul_scaled_q8_1_sel_into(
10482 &gate_out[rank_index],
10483 &up_out[rank_index],
10484 &experts.macros_gate_dev[rank_index],
10485 &experts.macros_up_dev[rank_index],
10486 &sel[rank_index],
10487 activation_limit,
10488 &mut act_q[rank_index],
10489 &mut act_d[rank_index],
10490 local_out,
10491 n_sel,
10492 )?;
10493 }
10494 let shard = &experts.down[rank_index];
10495 if shard.device_rank != rank_index || shard.local_in != local_out {
10496 return Err(
10497 "NVFP4 device routes: down canonical shard placement drifted from \
10498 the gate/up column split"
10499 .into(),
10500 );
10501 }
10502 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
10509 {
10510 static SEEN: std::sync::Mutex<Vec<(bool, bool)>> =
10514 std::sync::Mutex::new(Vec::new());
10515 if std::env::var("MEMRA_SWEEP_TRACE").as_deref() == Ok("1") {
10516 let mut seen = SEEN.lock().unwrap();
10517 if !seen.contains(&(down8, device_routed)) {
10518 seen.push((down8, device_routed));
10519 eprintln!(
10520 "[sweep-trace] down8={down8} device_routed={device_routed} \
10521 sel_down8_on={} local_in={} n_sel={n_sel}",
10522 sel_down8_on(),
10523 shard.local_in
10524 );
10525 }
10526 }
10527 }
10528 if down8 {
10529 let Nvfp4DeviceRoutesWorkspace {
10530 sel,
10531 act_q,
10532 act_d,
10533 route_w,
10534 accumulator,
10535 ..
10536 } = &mut *workspace;
10537 engine.qmatvec_nvfp4_sel_down8_into(
10538 &shard.bank,
10539 &sel[rank_index],
10540 &act_q[rank_index],
10541 &act_d[rank_index],
10542 &route_w[rank_index],
10543 &experts.macros_down_dev[rank_index],
10544 &mut accumulator[rank_index],
10545 n_sel,
10546 shard.local_in,
10547 shard.out_features,
10548 shard.row_bytes,
10549 shard.expert_bytes,
10550 local_out,
10551 local_out / 32,
10552 )?;
10553 } else {
10554 let Nvfp4DeviceRoutesWorkspace {
10555 sel,
10556 act_q,
10557 act_d,
10558 partial,
10559 ..
10560 } = &mut *workspace;
10561 engine.qmatvec_nvfp4_sel_into(
10562 &shard.bank,
10563 &sel[rank_index],
10564 &act_q[rank_index],
10565 &act_d[rank_index],
10566 &mut partial[rank_index],
10567 n_sel,
10568 shard.local_in,
10569 shard.out_features,
10570 shard.row_bytes,
10571 shard.expert_bytes,
10572 local_out,
10573 local_out / 32,
10574 )?;
10575 }
10576 if !down8 {
10581 let Nvfp4DeviceRoutesWorkspace {
10582 partial,
10583 combine_w,
10584 route_w,
10585 sel,
10586 accumulator,
10587 ..
10588 } = &mut *workspace;
10589 if device_routed {
10590 engine.axpy_rows_seq_md_into(
10591 &partial[rank_index],
10592 &route_w[rank_index],
10593 &experts.macros_down_dev[rank_index],
10594 &sel[rank_index],
10595 &mut accumulator[rank_index],
10596 experts.input_width,
10597 n_sel,
10598 )?;
10599 } else {
10600 engine.axpy_rows_seq_into(
10601 &partial[rank_index],
10602 &combine_w[rank_index],
10603 &mut accumulator[rank_index],
10604 experts.input_width,
10605 n_sel,
10606 )?;
10607 }
10608 }
10609 }
10610 Ok(())
10611 }
10612
10613 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10621 &self,
10622 experts: &ResidentNvfp4TensorParallel,
10623 e: &Engine,
10624 input_dev: &crate::CudaSlice<f32>,
10625 selected: &[usize],
10626 route_weights: &[f32],
10627 experts_per_token: usize,
10628 activation_limit: Option<f32>,
10629 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10630 if input_dev.len() != experts.input_width {
10631 return Err(format!(
10632 "NVFP4 device-io routes input {} != width {}",
10633 input_dev.len(),
10634 experts.input_width
10635 )
10636 .into());
10637 }
10638 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10639 return Err(format!(
10640 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10641 selected.len(),
10642 route_weights.len(),
10643 )
10644 .into());
10645 }
10646 if !route_weights.iter().all(|weight| weight.is_finite()) {
10647 return Err("NVFP4 device route weights contain a non-finite value".into());
10648 }
10649 let world = self.ranks.len();
10650 if world != NVFP4_CANONICAL_ROW_SHARDS {
10651 return Err(format!(
10652 "NVFP4 device routes require world == canonical shard grid \
10653 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10654 )
10655 .into());
10656 }
10657 let local_out = experts.expert_width / world;
10658 let n_sel = experts_per_token;
10659
10660 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10661 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10662 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10663 let started = timing.then(std::time::Instant::now);
10664
10665 let mut workspace_guard = experts
10666 .device_workspace
10667 .lock()
10668 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10669 if workspace_guard.is_none() {
10670 drop(workspace_guard);
10671 let zero = vec![0.0f32; experts.input_width];
10674 let zero_sel = vec![0usize; n_sel];
10675 let zero_w = vec![0.0f32; n_sel];
10676 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10677 experts,
10678 &zero,
10679 &zero_sel,
10680 &zero_w,
10681 n_sel,
10682 activation_limit,
10683 )?;
10684 workspace_guard = experts
10685 .device_workspace
10686 .lock()
10687 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10688 }
10689 let workspace = workspace_guard
10690 .as_mut()
10691 .expect("NVFP4 device routes workspace initialized above");
10692 if workspace.n_sel != n_sel {
10693 return Err(format!(
10694 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10695 workspace.n_sel
10696 )
10697 .into());
10698 }
10699 for &expert in selected {
10700 if expert >= experts.expert_count {
10701 return Err(format!(
10702 "NVFP4 device selected expert {expert} outside 0..{}",
10703 experts.expert_count
10704 )
10705 .into());
10706 }
10707 }
10708 let sel_i32 = selected
10709 .iter()
10710 .map(|&expert| expert as i32)
10711 .collect::<Vec<_>>();
10712
10713 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10717 if *device != e.ctx().ordinal() {
10718 return Err("NVFP4 device-io routes engine changed".into());
10719 }
10720 } else {
10721 let _main = e.gpu.enter_main()?;
10722 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10723 }
10724 {
10725 let _main = e.gpu.enter_main()?;
10726 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10727 ev_entry.record(&e.stream())?;
10728 }
10729 for (rank_index, engine) in self.ranks.iter().enumerate() {
10730 let _main = engine.gpu.enter_main()?;
10731 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10732 engine.stream().wait(ev_entry)?;
10733 {
10734 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10735 engine
10736 .stream()
10737 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10738 }
10739 {
10740 let Nvfp4DeviceRoutesWorkspace {
10741 input, in_q, in_d, ..
10742 } = &mut *workspace;
10743 engine.quantize_q8_1_into(
10744 &input[rank_index],
10745 1,
10746 experts.input_width,
10747 &mut in_q[rank_index],
10748 &mut in_d[rank_index],
10749 )?;
10750 }
10751 }
10752 self.nvfp4_routes_batched_sweeps(
10753 experts,
10754 workspace,
10755 selected,
10756 route_weights,
10757 &sel_i32,
10758 local_out,
10759 n_sel,
10760 activation_limit,
10761 false,
10762 )?;
10763
10764 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10770 let _main = engine.gpu.enter_main()?;
10771 workspace.ev_rank[rank_index].record(&engine.stream())?;
10772 }
10773 if moe_direct_on() && self.ranks.len() == 2 {
10774 {
10781 let root = &self.ranks[0];
10782 let _main = root.gpu.enter_main()?;
10783 workspace
10784 .ev_done
10785 .as_ref()
10786 .expect("device routes done event")
10787 .record(&root.stream())?;
10788 }
10789 let _main = e.gpu.enter_main()?;
10790 e.stream().wait(
10791 workspace
10792 .ev_done
10793 .as_ref()
10794 .expect("device routes done event"),
10795 )?;
10796 for ev in workspace.ev_rank.iter().skip(1) {
10797 e.stream().wait(ev)?;
10798 }
10799 let mut output = e.uninit(experts.input_width)?;
10800 e.add(
10801 &workspace.accumulator[0],
10802 &workspace.accumulator[1],
10803 &mut output,
10804 experts.input_width,
10805 )?;
10806 let output = output;
10807 if let Some(started) = started {
10808 use std::sync::atomic::Ordering;
10809 let ns = TIMING_NS
10810 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10811 + started.elapsed().as_nanos() as u64;
10812 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10813 if calls % 430 == 0 {
10814 eprintln!(
10815 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10816 ns as f64 / 1.0e6,
10817 ns as f64 / calls as f64 / 1.0e3,
10818 );
10819 }
10820 }
10821 return Ok(output);
10822 }
10823 {
10824 let root = &self.ranks[0];
10825 let _main = root.gpu.enter_main()?;
10826 for ev in workspace.ev_rank.iter().skip(1) {
10827 root.stream().wait(ev)?;
10828 }
10829 root.stream()
10830 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10831 {
10832 let Nvfp4DeviceRoutesWorkspace {
10833 accumulator,
10834 remote,
10835 combined,
10836 ..
10837 } = &mut *workspace;
10838 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10839 }
10840 workspace
10841 .ev_done
10842 .as_ref()
10843 .expect("device routes done event")
10844 .record(&root.stream())?;
10845 }
10846 let output = {
10847 let _main = e.gpu.enter_main()?;
10848 e.stream().wait(
10849 workspace
10850 .ev_done
10851 .as_ref()
10852 .expect("device routes done event"),
10853 )?;
10854 let mut output = e.uninit(experts.input_width)?;
10857 e.stream().memcpy_dtod(
10858 &workspace.combined.slice(0..experts.input_width),
10859 &mut output.slice_mut(0..experts.input_width),
10860 )?;
10861 output
10862 };
10863 if let Some(started) = started {
10864 use std::sync::atomic::Ordering;
10865 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10866 + started.elapsed().as_nanos() as u64;
10867 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10868 if calls % 430 == 0 {
10869 eprintln!(
10870 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10871 ns as f64 / 1.0e6,
10872 ns as f64 / calls as f64 / 1.0e3,
10873 );
10874 }
10875 }
10876 Ok(output)
10877 }
10878
10879 #[allow(clippy::too_many_arguments)]
10885 pub fn nvfp4_routes_prestage(
10890 &self,
10891 experts: &ResidentNvfp4TensorParallel,
10892 e: &Engine,
10893 input_dev: &crate::CudaSlice<f32>,
10894 ) -> Result<bool, Box<dyn std::error::Error>> {
10895 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
10896 }
10897
10898 pub fn nvfp4_routes_prestage_with(
10904 &self,
10905 experts: &ResidentNvfp4TensorParallel,
10906 e: &Engine,
10907 input_dev: &crate::CudaSlice<f32>,
10908 rank1_router: impl FnOnce(
10909 &Engine,
10910 &crate::CudaSlice<f32>,
10911 &mut crate::CudaSlice<i32>,
10912 &mut crate::CudaSlice<f32>,
10913 ) -> Result<bool, Box<dyn std::error::Error>>,
10914 ) -> Result<bool, Box<dyn std::error::Error>> {
10915 if !routes_prestage_on() || step_tp_graph_enabled()? {
10916 return Ok(false);
10917 }
10918 if input_dev.len() != experts.input_width {
10919 return Err("NVFP4 prestage input width mismatch".into());
10920 }
10921 let mut workspace_guard = experts
10922 .device_workspace
10923 .lock()
10924 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10925 let Some(workspace) = workspace_guard.as_mut() else {
10926 return Ok(false);
10927 };
10928 if workspace.ev_input.is_none() {
10929 let _main = e.gpu.enter_main()?;
10930 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10931 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
10932 return Err("NVFP4 prestage engine changed".into());
10933 }
10934 {
10935 let _main = e.gpu.enter_main()?;
10936 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10937 ev.record(&e.stream())?;
10938 }
10939 for (rank_index, engine) in self.ranks.iter().enumerate() {
10940 let _main = engine.gpu.enter_main()?;
10941 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10942 engine.stream().wait(ev)?;
10943 {
10944 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10945 engine
10946 .stream()
10947 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10948 }
10949 {
10950 let Nvfp4DeviceRoutesWorkspace {
10951 input, in_q, in_d, ..
10952 } = &mut *workspace;
10953 engine.quantize_q8_1_into(
10954 &input[rank_index],
10955 1,
10956 experts.input_width,
10957 &mut in_q[rank_index],
10958 &mut in_d[rank_index],
10959 )?;
10960 }
10961 }
10962 if self.ranks.len() == 2 {
10963 let rank1 = &self.ranks[1];
10964 let _r1 = rank1.gpu.enter_main()?;
10965 let Nvfp4DeviceRoutesWorkspace {
10966 input,
10967 sel,
10968 route_w,
10969 ..
10970 } = &mut *workspace;
10971 let (in1, rest_sel) = (&input[1], &mut sel[1]);
10972 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
10973 workspace.rank1_routed = true;
10974 }
10975 }
10976 workspace.prestaged = true;
10977 Ok(true)
10978 }
10979
10980 #[allow(clippy::too_many_arguments)]
10991 pub fn run_tensor_parallel_routes_nvfp4_device_routed_tn(
10992 &self,
10993 experts: &ResidentNvfp4TensorParallel,
10994 e: &Engine,
10995 z_t: &crate::CudaSlice<f32>,
10996 sel_d: &crate::CudaSlice<i32>,
10997 w_d: &crate::CudaSlice<f32>,
10998 t: usize,
10999 n_sel_col: usize,
11000 activation_limit: Option<f32>,
11001 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11002 let world = self.ranks.len();
11003 if world != NVFP4_CANONICAL_ROW_SHARDS {
11004 return Err("NVFP4 t-row routes require the canonical 2-shard grid".into());
11005 }
11006 let width = experts.input_width;
11007 let n_sel = t * n_sel_col;
11008 if t == 0 || t > 32 || z_t.len() < t * width || sel_d.len() < n_sel || w_d.len() < n_sel {
11009 return Err("NVFP4 t-row routes geometry".into());
11010 }
11011 if !nvfp4_bank_v2_on() {
11012 return Err("NVFP4 t-row routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
11013 }
11014 let local_out = experts.expert_width / world;
11015 let mut guard = experts
11016 .t2_workspace
11017 .lock()
11018 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
11019 if guard.as_ref().is_none_or(|ws| ws.n_sel != n_sel) {
11020 let mut input2 = Vec::new();
11021 let mut in_q2 = Vec::new();
11022 let mut in_d2 = Vec::new();
11023 let mut sel2 = Vec::new();
11024 let mut route_w2 = Vec::new();
11025 let mut gate_out2 = Vec::new();
11026 let mut up_out2 = Vec::new();
11027 let mut act_q2 = Vec::new();
11028 let mut act_d2 = Vec::new();
11029 let mut partial2 = Vec::new();
11030 let mut acc_a = Vec::new();
11031 let mut acc_b = Vec::new();
11032 let mut acc2 = Vec::new();
11033 let mut ev_rank = Vec::new();
11034 for engine in &self.ranks {
11035 let _m = engine.gpu.enter_main()?;
11036 input2.push(engine.uninit(t * width)?);
11037 in_q2.push(engine.alloc_i8_uninit(t * width)?);
11038 in_d2.push(engine.uninit(t * (width / 32))?);
11039 sel2.push(engine.htod_i32(&vec![0i32; n_sel])?);
11040 route_w2.push(engine.uninit(n_sel)?);
11041 gate_out2.push(engine.uninit(n_sel * local_out)?);
11042 up_out2.push(engine.uninit(n_sel * local_out)?);
11043 act_q2.push(engine.alloc_i8_uninit(n_sel * local_out)?);
11044 act_d2.push(engine.uninit(n_sel * (local_out / 32))?);
11045 partial2.push(engine.uninit(n_sel * width)?);
11046 acc_a.push(engine.uninit(width)?);
11047 acc_b.push(engine.uninit(width)?);
11048 acc2.push(engine.uninit(t * width)?);
11049 ev_rank.push(engine.ctx().new_event(None)?);
11050 }
11051 let root = &self.ranks[0];
11052 let (peer_a, peer_b, omix_a, omix_b, peer2, omix2, ev_root) = {
11053 let _m = root.gpu.enter_main()?;
11054 (
11055 root.uninit(width)?,
11056 root.uninit(width)?,
11057 root.uninit(width)?,
11058 root.uninit(width)?,
11059 root.uninit(t * width)?,
11060 root.uninit(t * width)?,
11061 root.ctx().new_event(None)?,
11062 )
11063 };
11064 let ev_entry = {
11065 let _m = e.gpu.enter_main()?;
11066 e.ctx().new_event(None)?
11067 };
11068 *guard = Some(Nvfp4T2Workspace {
11069 input2,
11070 in_q2,
11071 in_d2,
11072 sel2,
11073 route_w2,
11074 gate_out2,
11075 up_out2,
11076 act_q2,
11077 act_d2,
11078 partial2,
11079 acc_a,
11080 acc_b,
11081 acc2,
11082 peer2,
11083 omix2,
11084 peer_a,
11085 peer_b,
11086 omix_a,
11087 omix_b,
11088 ev_entry,
11089 ev_rank,
11090 ev_root,
11091 n_sel,
11092 e_device: e.ctx().ordinal(),
11093 });
11094 }
11095 let ws = guard.as_mut().expect("armed above");
11096 if ws.e_device != e.ctx().ordinal() {
11097 return Err("NVFP4 t2 routes engine changed".into());
11098 }
11099 {
11100 let _main = e.gpu.enter_main()?;
11101 ws.ev_entry.record(&e.stream())?;
11102 }
11103 let down8 = sel_down8_on() && (local_out >> 5) <= 32 && n_sel_col <= 8;
11107 if !down8 && t != 2 {
11108 return Err(
11109 "NVFP4 t-row routes at t != 2 require MEMRA_SEL_DOWN8=1 (fused rows kernel)".into(),
11110 );
11111 }
11112 for rank in 0..world {
11113 let engine = &self.ranks[rank];
11114 let _main = engine.gpu.enter_main()?;
11115 engine.stream().wait(&ws.ev_entry)?;
11116 {
11117 let mut dst = ws.input2[rank].slice_mut(0..t * width);
11118 engine
11119 .stream()
11120 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
11121 }
11122 {
11123 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
11124 engine
11125 .stream()
11126 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11127 }
11128 {
11129 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
11130 engine
11131 .stream()
11132 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11133 }
11134 {
11135 let Nvfp4T2Workspace {
11136 input2,
11137 in_q2,
11138 in_d2,
11139 ..
11140 } = &mut *ws;
11141 engine.quantize_q8_1_into(
11142 &input2[rank],
11143 t,
11144 width,
11145 &mut in_q2[rank],
11146 &mut in_d2[rank],
11147 )?;
11148 }
11149 let gate_bank = &experts.gate[rank];
11150 let up_bank = &experts.up[rank];
11151 if gate_bank.in_features != up_bank.in_features
11152 || gate_bank.local_out != up_bank.local_out
11153 || gate_bank.row_bytes != up_bank.row_bytes
11154 || gate_bank.expert_bytes != up_bank.expert_bytes
11155 {
11156 return Err("NVFP4 t-row routes need matched gate/up bank geometry".into());
11157 }
11158 {
11159 let Nvfp4T2Workspace {
11160 sel2,
11161 in_q2,
11162 in_d2,
11163 gate_out2,
11164 up_out2,
11165 ..
11166 } = &mut *ws;
11167 engine.qmatvec_nvfp4_sel_gu_tcol_into(
11168 &gate_bank.bank,
11169 &up_bank.bank,
11170 &sel2[rank],
11171 &in_q2[rank],
11172 &in_d2[rank],
11173 &mut gate_out2[rank],
11174 &mut up_out2[rank],
11175 n_sel,
11176 n_sel_col,
11177 gate_bank.in_features,
11178 gate_bank.local_out,
11179 gate_bank.row_bytes,
11180 gate_bank.expert_bytes,
11181 width,
11182 width / 32,
11183 )?;
11184 }
11185 {
11186 let Nvfp4T2Workspace {
11187 gate_out2,
11188 up_out2,
11189 sel2,
11190 act_q2,
11191 act_d2,
11192 ..
11193 } = &mut *ws;
11194 engine.silu_mul_scaled_q8_1_sel_into(
11195 &gate_out2[rank],
11196 &up_out2[rank],
11197 &experts.macros_gate_dev[rank],
11198 &experts.macros_up_dev[rank],
11199 &sel2[rank],
11200 activation_limit,
11201 &mut act_q2[rank],
11202 &mut act_d2[rank],
11203 local_out,
11204 n_sel,
11205 )?;
11206 }
11207 let shard = &experts.down[rank];
11208 if shard.device_rank != rank || shard.local_in != local_out {
11209 return Err("NVFP4 t-row routes: down shard placement drifted".into());
11210 }
11211 if down8 {
11215 let Nvfp4T2Workspace {
11216 sel2,
11217 act_q2,
11218 act_d2,
11219 route_w2,
11220 acc2,
11221 ..
11222 } = &mut *ws;
11223 engine.qmatvec_nvfp4_sel_down8_rows_into(
11224 &shard.bank,
11225 &sel2[rank],
11226 &act_q2[rank],
11227 &act_d2[rank],
11228 &route_w2[rank],
11229 &experts.macros_down_dev[rank],
11230 &mut acc2[rank],
11231 t,
11232 n_sel_col,
11233 shard.local_in,
11234 shard.out_features,
11235 shard.row_bytes,
11236 shard.expert_bytes,
11237 local_out,
11238 local_out / 32,
11239 )?;
11240 } else {
11241 {
11242 let Nvfp4T2Workspace {
11243 sel2,
11244 act_q2,
11245 act_d2,
11246 partial2,
11247 ..
11248 } = &mut *ws;
11249 engine.qmatvec_nvfp4_sel_into(
11250 &shard.bank,
11251 &sel2[rank],
11252 &act_q2[rank],
11253 &act_d2[rank],
11254 &mut partial2[rank],
11255 n_sel,
11256 shard.local_in,
11257 shard.out_features,
11258 shard.row_bytes,
11259 shard.expert_bytes,
11260 local_out,
11261 local_out / 32,
11262 )?;
11263 }
11264 let Nvfp4T2Workspace {
11265 partial2,
11266 route_w2,
11267 sel2,
11268 acc_a,
11269 acc_b,
11270 ..
11271 } = &mut *ws;
11272 engine.axpy_rows_seq_md_off_into(
11273 &partial2[rank],
11274 &route_w2[rank],
11275 &experts.macros_down_dev[rank],
11276 &sel2[rank],
11277 &mut acc_a[rank],
11278 width,
11279 n_sel_col,
11280 0,
11281 )?;
11282 engine.axpy_rows_seq_md_off_into(
11283 &partial2[rank],
11284 &route_w2[rank],
11285 &experts.macros_down_dev[rank],
11286 &sel2[rank],
11287 &mut acc_b[rank],
11288 width,
11289 n_sel_col,
11290 n_sel_col,
11291 )?;
11292 }
11293 if rank != 0 {
11294 ws.ev_rank[rank].record(&engine.stream())?;
11295 }
11296 }
11297 let root = &self.ranks[0];
11298 {
11299 let _main = root.gpu.enter_main()?;
11300 for ev in ws.ev_rank.iter().skip(1) {
11301 root.stream().wait(ev)?;
11302 }
11303 if down8 {
11304 let Nvfp4T2Workspace {
11307 acc2, peer2, omix2, ..
11308 } = &mut *ws;
11309 {
11310 let mut dst = peer2.slice_mut(0..t * width);
11311 root.stream()
11312 .memcpy_dtod(&acc2[1].slice(0..t * width), &mut dst)?;
11313 }
11314 root.add(&acc2[0], peer2, omix2, t * width)?;
11315 } else {
11316 let Nvfp4T2Workspace {
11317 acc_a,
11318 acc_b,
11319 peer_a,
11320 peer_b,
11321 omix_a,
11322 omix_b,
11323 ..
11324 } = &mut *ws;
11325 {
11326 let mut dst = peer_a.slice_mut(0..width);
11327 root.stream()
11328 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
11329 }
11330 {
11331 let mut dst = peer_b.slice_mut(0..width);
11332 root.stream()
11333 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
11334 }
11335 root.add(&acc_a[0], peer_a, omix_a, width)?;
11336 root.add(&acc_b[0], peer_b, omix_b, width)?;
11337 }
11338 ws.ev_root.record(&root.stream())?;
11339 }
11340 let _main = e.gpu.enter_main()?;
11341 e.stream().wait(&ws.ev_root)?;
11342 let mut out = e.uninit(t * width)?;
11343 if down8 {
11344 e.stream().memcpy_dtod(
11345 &ws.omix2.slice(0..t * width),
11346 &mut out.slice_mut(0..t * width),
11347 )?;
11348 } else {
11349 e.stream()
11350 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
11351 e.stream().memcpy_dtod(
11352 &ws.omix_b.slice(0..width),
11353 &mut out.slice_mut(width..2 * width),
11354 )?;
11355 }
11356 Ok(out)
11357 }
11358
11359 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
11360 &self,
11361 experts: &ResidentNvfp4TensorParallel,
11362 e: &Engine,
11363 input_dev: &crate::CudaSlice<f32>,
11364 sel_d: &crate::CudaSlice<i32>,
11365 w_d: &crate::CudaSlice<f32>,
11366 experts_per_token: usize,
11367 activation_limit: Option<f32>,
11368 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11369 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11370 experts,
11371 e,
11372 input_dev,
11373 sel_d,
11374 w_d,
11375 experts_per_token,
11376 activation_limit,
11377 || Ok(()),
11378 )
11379 }
11380
11381 #[allow(clippy::too_many_arguments)]
11387 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11388 &self,
11389 experts: &ResidentNvfp4TensorParallel,
11390 e: &Engine,
11391 input_dev: &crate::CudaSlice<f32>,
11392 sel_d: &crate::CudaSlice<i32>,
11393 w_d: &crate::CudaSlice<f32>,
11394 experts_per_token: usize,
11395 activation_limit: Option<f32>,
11396 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11397 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11398 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11399 experts,
11400 e,
11401 input_dev,
11402 sel_d,
11403 w_d,
11404 experts_per_token,
11405 activation_limit,
11406 pre_join,
11407 None,
11408 )
11409 }
11410
11411 #[allow(clippy::too_many_arguments)]
11416 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11417 &self,
11418 experts: &ResidentNvfp4TensorParallel,
11419 e: &Engine,
11420 input_dev: &crate::CudaSlice<f32>,
11421 sel_d: &crate::CudaSlice<i32>,
11422 w_d: &crate::CudaSlice<f32>,
11423 experts_per_token: usize,
11424 activation_limit: Option<f32>,
11425 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11426 post_add: Option<(u64, u64)>,
11427 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11428 if input_dev.len() != experts.input_width {
11429 return Err(format!(
11430 "NVFP4 device-routed input {} != width {}",
11431 input_dev.len(),
11432 experts.input_width
11433 )
11434 .into());
11435 }
11436 let n_sel = experts_per_token;
11437 if sel_d.len() < n_sel || w_d.len() < n_sel {
11438 return Err(format!(
11439 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
11440 sel_d.len(),
11441 w_d.len()
11442 )
11443 .into());
11444 }
11445 let world = self.ranks.len();
11446 if world != NVFP4_CANONICAL_ROW_SHARDS {
11447 return Err(format!(
11448 "NVFP4 device routes require world == canonical shard grid \
11449 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
11450 )
11451 .into());
11452 }
11453 let local_out = if experts.ep2 {
11454 experts.expert_width
11455 } else {
11456 experts.expert_width / world
11457 };
11458
11459 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11460 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11461 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11462 let started = timing.then(std::time::Instant::now);
11463
11464 let mut workspace_guard = experts
11465 .device_workspace
11466 .lock()
11467 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11468 if workspace_guard.is_none() {
11469 drop(workspace_guard);
11470 let zero = vec![0.0f32; experts.input_width];
11471 let zero_sel = vec![0usize; n_sel];
11472 let zero_w = vec![0.0f32; n_sel];
11473 let _ = self.run_tensor_parallel_routes_nvfp4_device(
11474 experts,
11475 &zero,
11476 &zero_sel,
11477 &zero_w,
11478 n_sel,
11479 activation_limit,
11480 )?;
11481 workspace_guard = experts
11482 .device_workspace
11483 .lock()
11484 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11485 }
11486 let workspace = workspace_guard
11487 .as_mut()
11488 .expect("NVFP4 device routes workspace initialized above");
11489 if workspace.n_sel != n_sel {
11490 return Err(format!(
11491 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
11492 workspace.n_sel
11493 )
11494 .into());
11495 }
11496
11497 if step_tp_graph_enabled()? {
11502 if experts.ep2 {
11503 return Err(
11504 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
11505 co-gated; unset one"
11506 .into(),
11507 );
11508 }
11509 if workspace.dev_route_e.is_none() {
11510 let _main = e.gpu.enter_main()?;
11511 workspace.dev_route_e = Some((
11512 e.htod_i32(&vec![0i32; n_sel])?,
11513 e.htod(&vec![0.0f32; n_sel])?,
11514 ));
11515 }
11516 if workspace.in_stage_e.is_none() {
11517 let _main = e.gpu.enter_main()?;
11518 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11519 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11520 }
11521 if workspace.routes_graph.is_none() {
11522 let graph = self.nvfp4_routes_build_graph(
11523 experts,
11524 workspace,
11525 local_out,
11526 n_sel,
11527 activation_limit,
11528 )?;
11529 workspace.routes_graph = Some(graph);
11530 eprintln!(
11531 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
11532 children=3 updates=none performance_claim=false"
11533 );
11534 }
11535 let output = {
11536 let _main = e.gpu.enter_main()?;
11537 {
11538 let (sel_e, w_e) = workspace
11539 .dev_route_e
11540 .as_mut()
11541 .expect("device route staging set above");
11542 {
11543 let mut dst = sel_e.slice_mut(0..n_sel);
11544 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11545 }
11546 {
11547 let mut dst = w_e.slice_mut(0..n_sel);
11548 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11549 }
11550 }
11551 {
11552 let in_stage = workspace
11553 .in_stage_e
11554 .as_mut()
11555 .expect("graph staging set above");
11556 let mut dst = in_stage.slice_mut(0..experts.input_width);
11557 e.stream()
11558 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
11559 }
11560 unsafe {
11561 let r = cudarc::driver::sys::cuGraphLaunch(
11562 workspace
11563 .routes_graph
11564 .as_ref()
11565 .expect("routes graph built above")
11566 .exec,
11567 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11568 );
11569 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11570 return Err(format!("routes graph launch: {r:?}").into());
11571 }
11572 }
11573 let mut output = e.uninit(experts.input_width)?;
11574 {
11575 let out_stage = workspace
11576 .out_stage_e
11577 .as_ref()
11578 .expect("graph staging set above");
11579 e.stream().memcpy_dtod(
11580 &out_stage.slice(0..experts.input_width),
11581 &mut output.slice_mut(0..experts.input_width),
11582 )?;
11583 }
11584 output
11585 };
11586 if let Some(started) = started {
11587 use std::sync::atomic::Ordering;
11588 let ns = TIMING_NS
11589 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11590 + started.elapsed().as_nanos() as u64;
11591 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11592 if calls % 430 == 0 {
11593 eprintln!(
11594 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11595 ns as f64 / 1.0e6,
11596 ns as f64 / calls as f64 / 1.0e3,
11597 );
11598 }
11599 }
11600 return Ok(output);
11601 }
11602
11603 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11607 if *device != e.ctx().ordinal() {
11608 return Err("NVFP4 device-routed routes engine changed".into());
11609 }
11610 } else {
11611 let _main = e.gpu.enter_main()?;
11612 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11613 }
11614 if workspace.dev_route_e.is_none() {
11615 let _main = e.gpu.enter_main()?;
11616 workspace.dev_route_e = Some((
11617 e.htod_i32(&vec![0i32; n_sel])?,
11618 e.htod(&vec![0.0f32; n_sel])?,
11619 ));
11620 }
11621 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
11627 let e_device = e.ctx().ordinal();
11628 let rank1_routed_peek = workspace.rank1_routed;
11630 let stage_needed = !mirror
11631 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
11632 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
11633 });
11634 {
11635 let _main = e.gpu.enter_main()?;
11636 if stage_needed {
11637 let (sel_e, w_e) = workspace
11638 .dev_route_e
11639 .as_mut()
11640 .expect("device route staging set above");
11641 {
11642 let mut dst = sel_e.slice_mut(0..n_sel);
11643 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11644 }
11645 {
11646 let mut dst = w_e.slice_mut(0..n_sel);
11647 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11648 }
11649 }
11650 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11651 ev_entry.record(&e.stream())?;
11652 }
11653 let prestaged = std::mem::take(&mut workspace.prestaged);
11656 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
11657 for (rank_index, engine) in self.ranks.iter().enumerate() {
11658 let _main = engine.gpu.enter_main()?;
11659 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11660 engine.stream().wait(ev_entry)?;
11661 if !prestaged {
11662 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11663 engine
11664 .stream()
11665 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11666 }
11667 if !(rank1_routed && rank_index == 1) {
11668 let same_dev = engine.ctx().ordinal() == e_device;
11672 if mirror {
11673 let Nvfp4DeviceRoutesWorkspace {
11676 sel,
11677 route_w,
11678 dev_route_e,
11679 ..
11680 } = &mut *workspace;
11681 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
11682 if same_dev {
11683 (sel_d, w_d)
11684 } else {
11685 let (sel_e, w_e) = dev_route_e
11686 .as_ref()
11687 .expect("device route staging set above");
11688 (sel_e, w_e)
11689 };
11690 engine.moe_sel_w_mirror(
11691 src_sel,
11692 src_w,
11693 &mut sel[rank_index],
11694 &mut route_w[rank_index],
11695 n_sel,
11696 )?;
11697 } else {
11698 let (sel_e, w_e) = workspace
11699 .dev_route_e
11700 .as_ref()
11701 .expect("device route staging set above");
11702 {
11703 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
11704 engine
11705 .stream()
11706 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
11707 }
11708 {
11709 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
11710 engine
11711 .stream()
11712 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11713 }
11714 }
11715 }
11716 if !prestaged {
11717 let Nvfp4DeviceRoutesWorkspace {
11718 input, in_q, in_d, ..
11719 } = &mut *workspace;
11720 engine.quantize_q8_1_into(
11721 &input[rank_index],
11722 1,
11723 experts.input_width,
11724 &mut in_q[rank_index],
11725 &mut in_d[rank_index],
11726 )?;
11727 }
11728 }
11729 self.nvfp4_routes_batched_sweeps(
11730 experts,
11731 workspace,
11732 &[],
11733 &[],
11734 &[],
11735 local_out,
11736 n_sel,
11737 activation_limit,
11738 true,
11739 )?;
11740
11741 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11744 let _main = engine.gpu.enter_main()?;
11745 workspace.ev_rank[rank_index].record(&engine.stream())?;
11746 }
11747 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
11750 let mut ticket = 0u32;
11751 if memops {
11752 use cudarc::driver::sys;
11753 if workspace.fence_flags_raw == 0 {
11754 let root = &self.ranks[0];
11755 let _main = root.gpu.enter_main()?;
11756 let mut ptr: sys::CUdeviceptr = 0;
11757 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
11758 if r != sys::CUresult::CUDA_SUCCESS {
11759 return Err(format!("fence flag alloc: {r:?}").into());
11760 }
11761 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
11762 if r != sys::CUresult::CUDA_SUCCESS {
11763 return Err(format!("fence flag memset: {r:?}").into());
11764 }
11765 workspace.fence_flags_raw = ptr as u64;
11766 }
11767 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
11768 ticket = workspace.fence_ticket;
11769 let base = workspace.fence_flags_raw;
11770 if fence_rank1_on() {
11776 let peer = &self.ranks[1];
11777 let _pmain = peer.gpu.enter_main()?;
11778 peer.ring_flag_raw(base, ticket)?;
11779 }
11780 {
11781 let root = &self.ranks[0];
11782 let _main = root.gpu.enter_main()?;
11783 let r = unsafe {
11784 sys::cuStreamWriteValue32_v2(
11785 root.stream().cu_stream() as sys::CUstream,
11786 (base + 4) as sys::CUdeviceptr,
11787 ticket,
11788 0,
11789 )
11790 };
11791 if r != sys::CUresult::CUDA_SUCCESS {
11792 return Err(format!("fence write root: {r:?}").into());
11793 }
11794 }
11795 }
11796 pre_join()?;
11799
11800 if moe_direct_on() && self.ranks.len() == 2 {
11801 let _main = e.gpu.enter_main()?;
11808 if memops {
11809 use cudarc::driver::sys;
11810 let base = workspace.fence_flags_raw;
11811 let r = unsafe {
11812 sys::cuStreamWaitValue32_v2(
11813 e.stream().cu_stream() as sys::CUstream,
11814 (base + 4) as sys::CUdeviceptr,
11815 ticket,
11816 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11817 )
11818 };
11819 if r != sys::CUresult::CUDA_SUCCESS {
11820 return Err(format!("fence wait: {r:?}").into());
11821 }
11822 if fence_rank1_on() {
11823 let r = unsafe {
11825 sys::cuStreamWaitValue32_v2(
11826 e.stream().cu_stream() as sys::CUstream,
11827 base as sys::CUdeviceptr,
11828 ticket,
11829 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11830 )
11831 };
11832 if r != sys::CUresult::CUDA_SUCCESS {
11833 return Err(format!("fence wait rank1: {r:?}").into());
11834 }
11835 } else {
11836 for ev in workspace.ev_rank.iter().skip(1) {
11837 e.stream().wait(ev)?;
11838 }
11839 }
11840 } else {
11841 {
11842 let root = &self.ranks[0];
11843 let _rmain = root.gpu.enter_main()?;
11844 workspace
11845 .ev_done
11846 .as_ref()
11847 .expect("device routes done event")
11848 .record(&root.stream())?;
11849 }
11850 e.stream().wait(
11851 workspace
11852 .ev_done
11853 .as_ref()
11854 .expect("device routes done event"),
11855 )?;
11856 for ev in workspace.ev_rank.iter().skip(1) {
11857 e.stream().wait(ev)?;
11858 }
11859 }
11860 let mut output = e.uninit(experts.input_width)?;
11861 if let Some((sh_raw, scale_raw)) = post_add {
11862 e.add3_raw(
11865 &workspace.accumulator[0],
11866 &workspace.accumulator[1],
11867 sh_raw,
11868 scale_raw,
11869 &mut output,
11870 experts.input_width,
11871 )?;
11872 } else {
11873 e.add(
11874 &workspace.accumulator[0],
11875 &workspace.accumulator[1],
11876 &mut output,
11877 experts.input_width,
11878 )?;
11879 }
11880 let output = output;
11881 if let Some(started) = started {
11882 use std::sync::atomic::Ordering;
11883 let ns = TIMING_NS
11884 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11885 + started.elapsed().as_nanos() as u64;
11886 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11887 if calls % 430 == 0 {
11888 eprintln!(
11889 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11890 ns as f64 / 1.0e6,
11891 ns as f64 / calls as f64 / 1.0e3,
11892 );
11893 }
11894 }
11895 return Ok(output);
11896 }
11897 {
11898 let root = &self.ranks[0];
11899 let _main = root.gpu.enter_main()?;
11900 for ev in workspace.ev_rank.iter().skip(1) {
11901 root.stream().wait(ev)?;
11902 }
11903 root.stream()
11904 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11905 {
11906 let Nvfp4DeviceRoutesWorkspace {
11907 accumulator,
11908 remote,
11909 combined,
11910 ..
11911 } = &mut *workspace;
11912 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11913 }
11914 workspace
11915 .ev_done
11916 .as_ref()
11917 .expect("device routes done event")
11918 .record(&root.stream())?;
11919 }
11920 let output = {
11921 let _main = e.gpu.enter_main()?;
11922 e.stream().wait(
11923 workspace
11924 .ev_done
11925 .as_ref()
11926 .expect("device routes done event"),
11927 )?;
11928 let mut output = e.uninit(experts.input_width)?;
11931 e.stream().memcpy_dtod(
11932 &workspace.combined.slice(0..experts.input_width),
11933 &mut output.slice_mut(0..experts.input_width),
11934 )?;
11935 output
11936 };
11937 if let Some(started) = started {
11938 use std::sync::atomic::Ordering;
11939 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11940 + started.elapsed().as_nanos() as u64;
11941 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11942 if calls % 430 == 0 {
11943 eprintln!(
11944 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11945 ns as f64 / 1.0e6,
11946 ns as f64 / calls as f64 / 1.0e3,
11947 );
11948 }
11949 }
11950 Ok(output)
11951 }
11952
11953 pub(crate) fn decode_v2_finish_root_fused(
11957 &self,
11958 ws: &mut StepTpDecodeV2Ws,
11959 ) -> Result<(), Box<dyn std::error::Error>> {
11960 let root = &self.ranks[0];
11961 let _main = root.gpu.enter_main()?;
11962 if ws.raw_peer_partial != 0 {
11963 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
11965 } else {
11966 root.stream()
11967 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
11968 }
11969 {
11970 let StepTpDecodeV2Ws {
11971 o_partials,
11972 peer_partial,
11973 reduce_a,
11974 o_out,
11975 ..
11976 } = &mut *ws;
11977 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
11978 }
11979 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
11980 if shadows {
11981 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
11984 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
11985 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
11986 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
11987 }
11988 if shadows && ws.raw_peer_partial != 0 {
11989 raw_copy_bytes(
11990 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
11991 ws.raw_k1,
11992 ws.local_kv_dim * 4,
11993 root,
11994 )?;
11995 raw_copy_bytes(
11996 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
11997 ws.raw_v1,
11998 ws.local_kv_dim * 4,
11999 root,
12000 )?;
12001 } else if shadows {
12002 let start = ws.local_kv_dim;
12003 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
12004 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
12005 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
12006 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
12007 }
12008 if ws.raw_mixed_stage_e != 0 {
12009 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
12012 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
12013 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
12014 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
12015 }
12016 Ok(())
12017 }
12018
12019 pub(crate) fn decode_v2_arm_token_mirrors(
12022 &self,
12023 ws: &mut StepTpDecodeV2Ws,
12024 mixed_stage_e: u64,
12025 shadow_stage_e: (u64, u64),
12026 ) -> Result<(), Box<dyn std::error::Error>> {
12027 use cudarc::driver::DevicePtr;
12028 let root = &self.ranks[0];
12029 let _main = root.gpu.enter_main()?;
12030 let stream = root.stream();
12031 let (a, _g) = ws.reduce_a.device_ptr(&stream);
12032 ws.raw_reduce_a = a as u64;
12033 ws.raw_mixed_stage_e = mixed_stage_e;
12034 ws.raw_shadow_stage_e = shadow_stage_e;
12035 Ok(())
12036 }
12037
12038 fn nvfp4_routes_build_graph(
12044 &self,
12045 experts: &ResidentNvfp4TensorParallel,
12046 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12047 local_out: usize,
12048 n_sel: usize,
12049 activation_limit: Option<f32>,
12050 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12051 use cudarc::driver::DevicePtr;
12052 use cudarc::driver::sys;
12053 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12054 if r == sys::CUresult::CUDA_SUCCESS {
12055 Ok(())
12056 } else {
12057 Err(format!("{what}: {r:?}").into())
12058 }
12059 }
12060 let world = self.ranks.len();
12061 if world != 2 {
12062 return Err("routes graph door is built for the TP2 pair".into());
12063 }
12064 let width = experts.input_width;
12065
12066 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
12068 let stream = engine.stream();
12069 let (ptr, _g) = buf.device_ptr(&stream);
12070 ptr as u64
12071 };
12072 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
12073 let stream = engine.stream();
12074 let (ptr, _g) = buf.device_ptr(&stream);
12075 ptr as u64
12076 };
12077 let (sel_e, w_e) = workspace
12078 .dev_route_e
12079 .as_ref()
12080 .expect("device route staging set before graph build");
12081 let root_engine = &self.ranks[0];
12082 let p_in_stage = ptr_f32(
12083 workspace.in_stage_e.as_ref().expect("graph staging"),
12084 root_engine,
12085 );
12086 let p_out_stage = ptr_f32(
12087 workspace.out_stage_e.as_ref().expect("graph staging"),
12088 root_engine,
12089 );
12090 let p_sel_e = ptr_i32(sel_e, root_engine);
12091 let p_w_e = ptr_f32(w_e, root_engine);
12092 let p_input: Vec<u64> = (0..world)
12093 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
12094 .collect();
12095 let p_sel: Vec<u64> = (0..world)
12096 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
12097 .collect();
12098 let p_route_w: Vec<u64> = (0..world)
12099 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
12100 .collect();
12101 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
12102 let p_remote = ptr_f32(&workspace.remote, root_engine);
12103 let p_combined = ptr_f32(&workspace.combined, root_engine);
12104
12105 let raw_copy = |dst: u64,
12106 src: u64,
12107 bytes: usize,
12108 engine: &Engine|
12109 -> Result<(), Box<dyn std::error::Error>> {
12110 unsafe {
12111 cu_try(
12112 sys::cuMemcpyAsync(
12113 dst as sys::CUdeviceptr,
12114 src as sys::CUdeviceptr,
12115 bytes,
12116 engine.stream().cu_stream() as sys::CUstream,
12117 ),
12118 "routes graph cuMemcpyAsync",
12119 )
12120 }
12121 };
12122
12123 let mut children = Vec::with_capacity(3);
12124 for rank in 0..world {
12125 let engine = &self.ranks[rank];
12126 let _main = engine.gpu.enter_main()?;
12127 let (child, _retained) = engine.capture_graph_retained(|_| {
12128 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
12129 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
12130 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
12131 {
12132 let Nvfp4DeviceRoutesWorkspace {
12133 input, in_q, in_d, ..
12134 } = &mut *workspace;
12135 engine.quantize_q8_1_into(
12136 &input[rank],
12137 1,
12138 width,
12139 &mut in_q[rank],
12140 &mut in_d[rank],
12141 )?;
12142 }
12143 self.nvfp4_routes_batched_sweeps_rank(
12144 experts,
12145 workspace,
12146 &[],
12147 &[],
12148 &[],
12149 local_out,
12150 n_sel,
12151 activation_limit,
12152 true,
12153 rank,
12154 )?;
12155 Ok(())
12156 })?;
12157 children.push(child);
12158 }
12159 {
12160 let root = &self.ranks[0];
12161 let _main = root.gpu.enter_main()?;
12162 let (child, _retained) = root.capture_graph_retained(|_| {
12163 raw_copy(p_remote, p_acc1, width * 4, root)?;
12164 {
12165 let Nvfp4DeviceRoutesWorkspace {
12166 accumulator,
12167 remote,
12168 combined,
12169 ..
12170 } = &mut *workspace;
12171 root.add(&accumulator[0], remote, combined, width)?;
12172 }
12173 raw_copy(p_out_stage, p_combined, width * 4, root)?;
12174 Ok(())
12175 })?;
12176 children.push(child);
12177 }
12178
12179 let mut parent: sys::CUgraph = std::ptr::null_mut();
12180 unsafe {
12181 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
12182 }
12183 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
12184 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
12185 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
12186 unsafe {
12187 cu_try(
12188 sys::cuGraphAddChildGraphNode(
12189 &mut n0,
12190 parent,
12191 std::ptr::null(),
12192 0,
12193 children[0].cu_graph(),
12194 ),
12195 "routes child r0",
12196 )?;
12197 cu_try(
12198 sys::cuGraphAddChildGraphNode(
12199 &mut n1,
12200 parent,
12201 std::ptr::null(),
12202 0,
12203 children[1].cu_graph(),
12204 ),
12205 "routes child r1",
12206 )?;
12207 let deps = [n0, n1];
12208 cu_try(
12209 sys::cuGraphAddChildGraphNode(
12210 &mut n2,
12211 parent,
12212 deps.as_ptr(),
12213 2,
12214 children[2].cu_graph(),
12215 ),
12216 "routes child root",
12217 )?;
12218 }
12219 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12220 unsafe {
12221 cu_try(
12222 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12223 "routes instantiate",
12224 )?;
12225 }
12226 Ok(RoutesGraph {
12227 exec,
12228 parent,
12229 _children: children,
12230 })
12231 }
12232
12233 #[allow(clippy::too_many_arguments)]
12237 pub(crate) fn routes_rank_section(
12238 &self,
12239 experts: &ResidentNvfp4TensorParallel,
12240 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12241 raw_input_src: u64,
12242 local_out: usize,
12243 n_sel: usize,
12244 activation_limit: Option<f32>,
12245 rank_index: usize,
12246 ) -> Result<(), Box<dyn std::error::Error>> {
12247 let engine = &self.ranks[rank_index];
12248 {
12249 let _main = engine.gpu.enter_main()?;
12250 let (sel_e_ptr, w_e_ptr) = workspace
12252 .raw_dev_route_e
12253 .ok_or("routes rank section requires armed staging pointers")?;
12254 raw_copy_bytes(
12255 workspace.raw_input[rank_index],
12256 raw_input_src,
12257 experts.input_width * 4,
12258 engine,
12259 )?;
12260 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
12261 raw_copy_bytes(
12262 workspace.raw_route_w[rank_index],
12263 w_e_ptr,
12264 n_sel * 4,
12265 engine,
12266 )?;
12267 {
12268 let Nvfp4DeviceRoutesWorkspace {
12269 input, in_q, in_d, ..
12270 } = &mut *workspace;
12271 engine.quantize_q8_1_into(
12272 &input[rank_index],
12273 1,
12274 experts.input_width,
12275 &mut in_q[rank_index],
12276 &mut in_d[rank_index],
12277 )?;
12278 }
12279 }
12280 self.nvfp4_routes_batched_sweeps_rank(
12281 experts,
12282 workspace,
12283 &[],
12284 &[],
12285 &[],
12286 local_out,
12287 n_sel,
12288 activation_limit,
12289 true,
12290 rank_index,
12291 )
12292 }
12293
12294 pub(crate) fn routes_root_section(
12297 &self,
12298 experts: &ResidentNvfp4TensorParallel,
12299 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12300 ) -> Result<(), Box<dyn std::error::Error>> {
12301 let root = &self.ranks[0];
12302 let _main = root.gpu.enter_main()?;
12303 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
12304 .raw_combine
12305 .ok_or("routes root section requires armed combine pointers")?;
12306 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
12307 {
12308 let Nvfp4DeviceRoutesWorkspace {
12309 accumulator,
12310 remote,
12311 combined,
12312 ..
12313 } = &mut *workspace;
12314 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12315 }
12316 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
12317 Ok(())
12318 }
12319
12320 pub(crate) fn routes_arm_raw(
12323 &self,
12324 experts: &ResidentNvfp4TensorParallel,
12325 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12326 ) -> Result<(), Box<dyn std::error::Error>> {
12327 use cudarc::driver::DevicePtr;
12328 if workspace.raw_dev_route_e.is_some() {
12329 return Ok(());
12330 }
12331 let _ = experts;
12332 let (sel_e, w_e) = workspace
12333 .dev_route_e
12334 .as_ref()
12335 .ok_or("routes staging not armed")?;
12336 let root = &self.ranks[0];
12337 {
12338 let _main = root.gpu.enter_main()?;
12339 let stream = root.stream();
12340 let (a, _g) = sel_e.device_ptr(&stream);
12341 let (b, _g) = w_e.device_ptr(&stream);
12342 workspace.raw_dev_route_e = Some((a as u64, b as u64));
12343 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
12344 let (d, _g) = workspace.remote.device_ptr(&stream);
12345 let (f, _g) = workspace.combined.device_ptr(&stream);
12346 let out_stage = workspace
12347 .out_stage_e
12348 .as_ref()
12349 .ok_or("routes out stage not armed")?;
12350 let (g_, _g) = out_stage.device_ptr(&stream);
12351 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
12352 }
12353 for rank in 0..self.ranks.len() {
12354 let engine = &self.ranks[rank];
12355 let _main = engine.gpu.enter_main()?;
12356 let stream = engine.stream();
12357 let (a, _g) = workspace.input[rank].device_ptr(&stream);
12358 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
12359 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
12360 workspace.raw_input.push(a as u64);
12361 workspace.raw_sel.push(b as u64);
12362 workspace.raw_route_w.push(c as u64);
12363 }
12364 Ok(())
12365 }
12366
12367 pub fn run_tensor_parallel_routes_nvfp4(
12371 &self,
12372 experts: &ResidentNvfp4TensorParallel,
12373 input: &[f32],
12374 tokens: usize,
12375 selected: &[usize],
12376 route_weights: &[f32],
12377 experts_per_token: usize,
12378 activation_limit: Option<f32>,
12379 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12380 validate_activations(input, tokens, experts.input_width)?;
12381 let pairs = tokens
12382 .checked_mul(experts_per_token)
12383 .ok_or("NVFP4 TP route count overflow")?;
12384 if selected.len() != pairs || route_weights.len() != pairs {
12385 return Err(format!(
12386 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
12387 {experts_per_token} ({pairs})",
12388 selected.len(),
12389 route_weights.len(),
12390 )
12391 .into());
12392 }
12393 if !route_weights.iter().all(|weight| weight.is_finite()) {
12394 return Err("NVFP4 TP route weights contain a non-finite value".into());
12395 }
12396
12397 let mut output = vec![0.0f32; tokens * experts.input_width];
12398 for token in 0..tokens {
12399 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
12400 for slot in 0..experts_per_token {
12401 let pair = token * experts_per_token + slot;
12402 let expert = selected[pair];
12403 if expert >= experts.expert_count {
12404 return Err(format!(
12405 "NVFP4 TP selected expert {expert} outside 0..{}",
12406 experts.expert_count
12407 )
12408 .into());
12409 }
12410 let gate = if experts.ep2 {
12416 self.run_full_bank_expert_nvfp4(
12417 &experts.gate,
12418 &experts.macros_gate,
12419 expert,
12420 input_row,
12421 )?
12422 } else {
12423 self.run_column_bank_expert_nvfp4(
12424 &experts.gate,
12425 &experts.macros_gate,
12426 expert,
12427 input_row,
12428 )?
12429 };
12430 let up = if experts.ep2 {
12431 self.run_full_bank_expert_nvfp4(
12432 &experts.up,
12433 &experts.macros_up,
12434 expert,
12435 input_row,
12436 )?
12437 } else {
12438 self.run_column_bank_expert_nvfp4(
12439 &experts.up,
12440 &experts.macros_up,
12441 expert,
12442 input_row,
12443 )?
12444 };
12445 let activated: Vec<f32> = gate
12446 .iter()
12447 .zip(&up)
12448 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
12449 .collect();
12450 debug_assert_eq!(activated.len(), experts.expert_width);
12451 let down = if experts.ep2 {
12452 self.run_full_down_expert_nvfp4(
12453 &experts.down,
12454 &experts.macros_down,
12455 expert,
12456 &activated,
12457 )?
12458 } else {
12459 self.run_row_bank_expert_nvfp4(
12460 &experts.down,
12461 &experts.macros_down,
12462 expert,
12463 &activated,
12464 )?
12465 };
12466 let weight = route_weights[pair];
12467 for (sum, value) in output
12468 [token * experts.input_width..(token + 1) * experts.input_width]
12469 .iter_mut()
12470 .zip(down)
12471 {
12472 *sum += weight * value;
12473 }
12474 }
12475 }
12476 Ok(output)
12477 }
12478}
12479
12480#[cfg(test)]
12481mod tests {
12482 use super::*;
12483
12484 #[test]
12485 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
12486 let limit = Some(7.0);
12487 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
12488 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
12489 assert!(
12490 step_expert_activation_host(-20.0, 9.0, limit).abs()
12491 < step_expert_activation_host(-20.0, 9.0, None).abs()
12492 );
12493 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
12494 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
12495 assert!(validate_step_expert_activation_limit(limit).is_ok());
12496 }
12497
12498 #[test]
12499 fn moe_residual_host_preserves_official_add_order() {
12500 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
12501 assert_eq!(output, [0.0]);
12502 assert_eq!(
12503 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
12504 "MoE residual lengths residual=1 routed=2 shared=1"
12505 );
12506 }
12507
12508 #[test]
12509 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
12510 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
12511 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
12512 assert_eq!(owners.len(), 4);
12513 for (rank, owner) in owners.iter().enumerate() {
12514 assert_eq!(owner.rank, rank);
12515 assert_eq!(owner.selected, vec![0, 36]);
12516 assert_eq!(owner.token_rows, vec![0, 0]);
12517 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
12518 }
12519 }
12520
12521 #[test]
12522 fn expert_owner_routes_validate_geometry_and_selected_experts() {
12523 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
12524 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
12525 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
12526 assert!(error.contains("outside 0..288"));
12527 }
12528
12529 #[test]
12530 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
12531 let selected = [
12532 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
12533 ];
12534 assert_eq!(
12535 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
12536 16
12537 );
12538 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
12539 assert_eq!(
12540 owners
12541 .iter()
12542 .map(|owner| owner.selected.len())
12543 .collect::<Vec<_>>(),
12544 vec![2, 4, 6, 4]
12545 );
12546 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
12547 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
12548 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
12549 }
12550
12551 #[test]
12552 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
12553 let owner0 = [0usize, 3];
12554 let owner1 = [1usize, 2];
12555 let owners = [owner0.as_slice(), owner1.as_slice()];
12556 assert_eq!(
12557 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
12558 .unwrap(),
12559 WeightedRouteCombineShape {
12560 pairs: 4,
12561 max_pairs: 12,
12562 }
12563 );
12564 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
12565 assert!(
12566 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
12567 .is_err()
12568 );
12569 assert!(
12570 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
12571 .is_err()
12572 );
12573 assert!(
12574 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
12575 .is_err()
12576 );
12577 }
12578
12579 #[test]
12580 fn native_p2p_door_is_strict_and_default_off() {
12581 assert!(!parse_step_tp_native_p2p(None).unwrap());
12582 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
12583 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
12584 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
12585 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
12586 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
12587 }
12588
12589 #[test]
12590 fn bulk_p2p_door_is_strict_and_default_off() {
12591 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
12592 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
12593 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
12594 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
12595 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
12596 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
12597 }
12598
12599 #[test]
12600 fn ep_device_arithmetic_door_is_strict_and_default_off() {
12601 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
12602 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
12603 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
12604 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
12605 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
12606 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
12607 }
12608
12609 #[test]
12610 fn f32_mirror_door_is_strict_and_default_off() {
12611 assert!(!parse_step_tp_f32_mirror(None).unwrap());
12612 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
12613 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
12614 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
12615 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
12616 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
12617 }
12618
12619 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
12620 let codes = (0..out_features * in_features)
12621 .map(|index| (index % 251) as u8)
12622 .collect();
12623 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
12624 .map(|index| index as f32 + 1.0)
12625 .collect();
12626 (codes, scales)
12627 }
12628
12629 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
12630 (0..out_features * in_features)
12631 .flat_map(|value| (value as u16).to_le_bytes())
12632 .collect()
12633 }
12634
12635 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
12636 bytes
12637 .chunks_exact(2)
12638 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
12639 .collect()
12640 }
12641
12642 #[test]
12643 fn bf16_matrix_rejects_wrong_byte_count() {
12644 let bytes = vec![0u8; 4 * 4 * 2 - 1];
12645 let matrix = Bf16Matrix {
12646 bytes: &bytes,
12647 out_features: 4,
12648 in_features: 4,
12649 };
12650 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
12651 }
12652
12653 #[test]
12654 fn replicated_device_rows_require_exact_rank_local_shapes() {
12655 assert_eq!(
12656 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
12657 12_288
12658 );
12659 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
12660 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
12661 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
12662 assert!(
12663 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
12664 );
12665 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
12666 }
12667
12668 #[test]
12669 fn replicated_device_row_refresh_requires_exact_root_source() {
12670 assert_eq!(
12671 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
12672 12_288
12673 );
12674 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
12675 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
12676 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
12677 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
12678 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
12679 }
12680
12681 #[test]
12682 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
12683 for tp in [1, 2, 4, 8] {
12684 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
12685 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
12686 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
12687 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
12688 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
12689 }
12690 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
12691 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
12692 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
12693 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
12694 }
12695
12696 #[test]
12697 fn cache_rows_split_by_token_then_rank() {
12698 let rows = (0u8..24).collect::<Vec<_>>();
12699 assert_eq!(
12700 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
12701 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
12702 );
12703 assert_eq!(
12704 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
12705 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
12706 );
12707 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
12708 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
12709 }
12710
12711 #[test]
12712 fn bf16_column_shard_preserves_contiguous_output_rows() {
12713 let bytes = bf16_matrix_bytes(4, 4);
12714 let matrix = Bf16Matrix {
12715 bytes: &bytes,
12716 out_features: 4,
12717 in_features: 4,
12718 };
12719 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
12720 assert_eq!(shard.out_features, 2);
12721 assert_eq!(shard.in_features, 4);
12722 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
12723 }
12724
12725 #[test]
12726 fn bf16_row_shard_preserves_each_input_column_window() {
12727 let bytes = bf16_matrix_bytes(3, 4);
12728 let matrix = Bf16Matrix {
12729 bytes: &bytes,
12730 out_features: 3,
12731 in_features: 4,
12732 };
12733 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
12734 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
12735 }
12736
12737 #[test]
12738 fn bf16_row_block_preserves_global_column_order() {
12739 let bytes = bf16_matrix_bytes(3, 8);
12740 let matrix = Bf16Matrix {
12741 bytes: &bytes,
12742 out_features: 3,
12743 in_features: 8,
12744 };
12745 let block = bf16_row_block(matrix, 2, 3).unwrap();
12746 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
12747 }
12748
12749 #[test]
12750 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
12751 let (codes, scales) = matrix(1280, 4096);
12752 let matrix = E4m3BlockMatrix {
12753 codes: &codes,
12754 scales: &scales,
12755 out_features: 1280,
12756 in_features: 4096,
12757 };
12758 let shard = column_shard(matrix, 2, 1).unwrap();
12759 assert_eq!(shard.out_features, 640);
12760 assert_eq!(shard.codes, &codes[640 * 4096..]);
12761 assert_eq!(shard.scales, &scales[5 * 32..]);
12762 }
12763
12764 #[test]
12765 fn row_shard_preserves_each_weight_and_scale_column_window() {
12766 let (codes, scales) = matrix(4096, 1280);
12767 let matrix = E4m3BlockMatrix {
12768 codes: &codes,
12769 scales: &scales,
12770 out_features: 4096,
12771 in_features: 1280,
12772 };
12773 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
12774 assert_eq!(shard_codes.len(), 4096 * 640);
12775 assert_eq!(&shard_codes[..640], &codes[640..1280]);
12776 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
12777 assert_eq!(shard_scales.len(), 32 * 5);
12778 assert_eq!(&shard_scales[..5], &scales[5..10]);
12779 assert_eq!(&shard_scales[5..10], &scales[15..20]);
12780 }
12781
12782 #[test]
12783 fn activation_shards_keep_token_rows_separate() {
12784 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
12785 assert_eq!(
12786 activation_shard(&activations, 2, 8, 2, 1),
12787 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
12788 );
12789 }
12790
12791 #[test]
12792 fn expert_bank_selects_expert_major_code_and_scale_planes() {
12793 let expert_count = 2;
12794 let out_features = 128;
12795 let in_features = 128;
12796 let code_stride = out_features * in_features;
12797 let codes: Vec<u8> = (0..expert_count * code_stride)
12798 .map(|index| (index % 251) as u8)
12799 .collect();
12800 let scales = vec![1.0f32, 2.0];
12801 let bank = E4m3ExpertBank {
12802 codes: &codes,
12803 scales: &scales,
12804 expert_count,
12805 out_features,
12806 in_features,
12807 };
12808 bank.validate().unwrap();
12809 let expert = bank.expert(1).unwrap();
12810 assert_eq!(expert.codes, &codes[code_stride..]);
12811 assert_eq!(expert.scales, &[2.0]);
12812 }
12813
12814 #[test]
12815 fn expert_bank_rejects_non_positive_scale() {
12816 let codes = vec![0u8; 128 * 128];
12817 let scales = vec![0.0f32];
12818 let bank = E4m3ExpertBank {
12819 codes: &codes,
12820 scales: &scales,
12821 expert_count: 1,
12822 out_features: 128,
12823 in_features: 128,
12824 };
12825 assert!(bank.validate().unwrap_err().contains("non-positive"));
12826 }
12827
12828 #[test]
12829 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
12830 let expert_count = 2;
12831 let out_features = 256;
12832 let in_features = 128;
12833 let code_stride = out_features * in_features;
12834 let scale_stride = 2;
12835 let codes = (0..expert_count * code_stride)
12836 .map(|index| (index % 251) as u8)
12837 .collect::<Vec<_>>();
12838 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12839 let bank = E4m3ExpertBank {
12840 codes: &codes,
12841 scales: &scales,
12842 expert_count,
12843 out_features,
12844 in_features,
12845 };
12846
12847 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
12848 assert_eq!(rank.out_features, 128);
12849 assert_eq!(rank.in_features, 128);
12850 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12851 assert_eq!(rank.scales, vec![11.0, 21.0]);
12852 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
12853 assert_eq!(
12854 &rank.codes[128 * 128..],
12855 &codes[code_stride + 128 * 128..2 * code_stride]
12856 );
12857 assert_eq!(scale_stride, scales.len() / expert_count);
12858 }
12859
12860 #[test]
12861 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
12862 let expert_count = 2;
12863 let out_features = 128;
12864 let in_features = 256;
12865 let code_stride = out_features * in_features;
12866 let codes = (0..expert_count * code_stride)
12867 .map(|index| (index % 251) as u8)
12868 .collect::<Vec<_>>();
12869 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12870 let bank = E4m3ExpertBank {
12871 codes: &codes,
12872 scales: &scales,
12873 expert_count,
12874 out_features,
12875 in_features,
12876 };
12877
12878 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12879 assert_eq!(rank.out_features, 128);
12880 assert_eq!(rank.in_features, 128);
12881 assert_eq!(rank.k_blocks, Some(1));
12882 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12883 assert_eq!(rank.scales, vec![11.0, 21.0]);
12884 assert_eq!(&rank.codes[..128], &codes[128..256]);
12885 assert_eq!(
12886 &rank.codes[128 * 128..128 * 128 + 128],
12887 &codes[code_stride + 128..code_stride + 256]
12888 );
12889 }
12890
12891 #[test]
12892 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
12893 let expert_count = 2;
12894 let out_features = 256;
12895 let in_features = 512;
12896 let code_stride = out_features * in_features;
12897 let mut codes = vec![0u8; expert_count * code_stride];
12898 for expert in 0..expert_count {
12899 for row in 0..out_features {
12900 for block in 0..4 {
12901 let value = (expert * 80 + block * 16 + row % 16) as u8;
12902 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
12903 codes[start..start + FP8_BLOCK].fill(value);
12904 }
12905 }
12906 }
12907 let scales = vec![
12908 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,
12909 112.0, 113.0, 114.0,
12910 ];
12911 let bank = E4m3ExpertBank {
12912 codes: &codes,
12913 scales: &scales,
12914 expert_count,
12915 out_features,
12916 in_features,
12917 };
12918
12919 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12920 assert_eq!(rank.out_features, out_features);
12921 assert_eq!(rank.in_features, 256);
12922 assert_eq!(rank.k_blocks, Some(2));
12923 assert_eq!(rank.code_stride, out_features * 256);
12924 assert_eq!(rank.scale_stride, 4);
12925 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
12926 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
12927
12928 let block_stride = out_features * FP8_BLOCK;
12929 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
12930 assert!(
12931 rank.codes[block_stride..block_stride + FP8_BLOCK]
12932 .iter()
12933 .all(|&code| code == 48)
12934 );
12935 assert!(
12936 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
12937 .iter()
12938 .all(|&code| code == 112)
12939 );
12940 assert!(
12941 rank.codes
12942 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
12943 .iter()
12944 .all(|&code| code == 128)
12945 );
12946 }
12947
12948 #[test]
12949 fn step_ep_layer_specs_are_literal_and_fail_closed() {
12950 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
12951 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
12952 assert_eq!(
12953 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
12954 vec![StepEpLayerSpec {
12955 layer: 24,
12956 devices: vec![1, 2],
12957 }]
12958 );
12959 assert_eq!(
12960 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
12961 vec![
12962 StepEpLayerSpec {
12963 layer: 24,
12964 devices: vec![1, 2],
12965 },
12966 StepEpLayerSpec {
12967 layer: 25,
12968 devices: vec![1, 2],
12969 },
12970 StepEpLayerSpec {
12971 layer: 31,
12972 devices: vec![0, 2],
12973 },
12974 ]
12975 );
12976 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
12977 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
12978 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
12979 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
12980 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
12981 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12982 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
12983 }
12984
12985 #[test]
12986 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
12987 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
12988 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
12989 assert_eq!(
12990 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
12991 vec![
12992 StepTpLayerSpec {
12993 layer: 24,
12994 devices: vec![1, 2],
12995 },
12996 StepTpLayerSpec {
12997 layer: 25,
12998 devices: vec![1, 2],
12999 },
13000 ]
13001 );
13002 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
13003 assert!(error.contains("MEMRA_STEP_TP"));
13004 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
13005 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13006
13007 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
13008 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
13009 assert_eq!(all.first().unwrap().layer, 0);
13010 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
13011 let devices = (0..8).collect::<Vec<_>>();
13012 assert!(all.iter().all(|spec| spec.devices == devices));
13013 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
13014 }
13015}
13016
13017struct TokenGraphChild {
13029 graph: cudarc::driver::CudaGraph,
13030 node: cudarc::driver::sys::CUgraphNode,
13031 ctx: cudarc::driver::sys::CUcontext,
13032}
13033
13034struct TokenGraphFaSite {
13038 ctx: cudarc::driver::sys::CUcontext,
13039 memset_o: cudarc::driver::sys::CUgraphNode,
13040 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
13041 fa: cudarc::driver::sys::CUgraphNode,
13042 combine: cudarc::driver::sys::CUgraphNode,
13043 window: usize,
13044 n_head: usize,
13045 n_head_kv: usize,
13046 head_dim: usize,
13047}
13048
13049pub struct TokenGraphBuilder {
13050 parent: cudarc::driver::sys::CUgraph,
13051 children: Vec<TokenGraphChild>,
13052 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
13055 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
13058 group: Option<(
13061 u32,
13062 Vec<cudarc::driver::sys::CUgraphNode>,
13063 Vec<cudarc::driver::sys::CUgraphNode>,
13064 )>,
13065}
13066
13067unsafe impl Send for TokenGraphBuilder {}
13069
13070impl TokenGraphBuilder {
13071 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
13072 use cudarc::driver::sys;
13073 let mut parent: sys::CUgraph = std::ptr::null_mut();
13074 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
13075 if r != sys::CUresult::CUDA_SUCCESS {
13076 return Err(format!("token graph create: {r:?}").into());
13077 }
13078 Ok(Self {
13079 parent,
13080 children: Vec::new(),
13081 frontier: Vec::new(),
13082 pending_detached: Vec::new(),
13083 group: None,
13084 })
13085 }
13086
13087 fn push_child(
13088 &mut self,
13089 graph: cudarc::driver::CudaGraph,
13090 parallel_group: Option<u32>,
13091 detached: bool,
13092 absorb: bool,
13093 ctx: cudarc::driver::sys::CUcontext,
13094 ) -> Result<(), Box<dyn std::error::Error>> {
13095 use cudarc::driver::sys;
13096 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
13100 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
13101 (state, Some(group)) => {
13102 if let Some((_, _, members)) = state.take() {
13104 self.frontier = members;
13105 }
13106 let base = self.frontier.clone();
13107 *state = Some((group, base.clone(), Vec::new()));
13108 base
13109 }
13110 (state, None) if detached => match state.as_ref() {
13111 Some((_, base, _)) => base.clone(),
13112 None => self.frontier.clone(),
13113 },
13114 (state, None) => {
13115 if let Some((_, _, members)) = state.take() {
13116 self.frontier = members;
13117 }
13118 let mut deps = self.frontier.clone();
13119 if absorb {
13120 deps.append(&mut self.pending_detached);
13121 }
13122 deps
13123 }
13124 };
13125 let mut node: sys::CUgraphNode = std::ptr::null_mut();
13126 let r = unsafe {
13127 sys::cuGraphAddChildGraphNode(
13128 &mut node,
13129 self.parent,
13130 if deps.is_empty() {
13131 std::ptr::null()
13132 } else {
13133 deps.as_ptr()
13134 },
13135 deps.len(),
13136 graph.cu_graph(),
13137 )
13138 };
13139 if r != sys::CUresult::CUDA_SUCCESS {
13140 return Err(format!("token graph child: {r:?}").into());
13141 }
13142 match (&mut self.group, parallel_group, detached) {
13143 (_, None, true) => self.pending_detached.push(node),
13144 (Some((_, _, members)), Some(_), _) => members.push(node),
13145 _ => self.frontier = vec![node],
13146 }
13147 self.children.push(TokenGraphChild { graph, node, ctx });
13148 Ok(())
13149 }
13150
13151 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
13152 use cudarc::driver::sys;
13153 if let Some((_, _, members)) = self.group.take() {
13154 self.frontier = members;
13155 }
13156 let mut fa_sites = Vec::new();
13159 for child in &self.children {
13160 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
13161 fa_sites.push(site);
13162 }
13163 }
13164 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
13165 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
13166 if r != sys::CUresult::CUDA_SUCCESS {
13167 return Err(format!("token graph instantiate: {r:?}").into());
13168 }
13169 Ok(TokenGraph {
13170 exec,
13171 parent: self.parent,
13172 _children: self.children,
13173 fa_sites,
13174 })
13175 }
13176}
13177
13178fn discover_fa_site(
13181 child_node: cudarc::driver::sys::CUgraphNode,
13182 ctx: cudarc::driver::sys::CUcontext,
13183) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
13184 use cudarc::driver::sys;
13185 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13186 if r == sys::CUresult::CUDA_SUCCESS {
13187 Ok(())
13188 } else {
13189 Err(format!("{what}: {r:?}").into())
13190 }
13191 }
13192 let mut graph: sys::CUgraph = std::ptr::null_mut();
13193 unsafe {
13194 cu_try(
13195 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
13196 "fa-site child GetGraph",
13197 )?;
13198 }
13199 let mut count: usize = 0;
13200 unsafe {
13201 cu_try(
13202 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
13203 "fa-site GetNodes(count)",
13204 )?;
13205 }
13206 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
13207 unsafe {
13208 cu_try(
13209 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
13210 "fa-site GetNodes",
13211 )?;
13212 }
13213 nodes.truncate(count);
13214 let node_type =
13215 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
13216 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
13217 unsafe {
13218 cu_try(
13219 sys::cuGraphNodeGetType(node, &mut ty),
13220 "fa-site NodeGetType",
13221 )?;
13222 }
13223 Ok(ty)
13224 };
13225 let memsets: Vec<sys::CUgraphNode> = {
13226 let mut v = Vec::new();
13227 for &node in &nodes {
13228 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
13229 v.push(node);
13230 }
13231 }
13232 v
13233 };
13234 if memsets.len() != 3 {
13235 return Ok(None);
13236 }
13237 let dependents =
13239 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
13240 let mut n: usize = 0;
13241 unsafe {
13242 cu_try(
13243 sys::cuGraphNodeGetDependentNodes_v2(
13244 node,
13245 std::ptr::null_mut(),
13246 std::ptr::null_mut(),
13247 &mut n,
13248 ),
13249 "fa-site GetDependentNodes(count)",
13250 )?;
13251 }
13252 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
13253 unsafe {
13254 cu_try(
13255 sys::cuGraphNodeGetDependentNodes_v2(
13256 node,
13257 v.as_mut_ptr(),
13258 std::ptr::null_mut(),
13259 &mut n,
13260 ),
13261 "fa-site GetDependentNodes",
13262 )?;
13263 }
13264 v.truncate(n);
13265 Ok(v)
13266 };
13267 let mut fa: Option<sys::CUgraphNode> = None;
13270 let mut last_memset: Option<sys::CUgraphNode> = None;
13271 for &ms in &memsets {
13272 for dep in dependents(ms)? {
13273 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13274 fa = Some(dep);
13275 last_memset = Some(ms);
13276 }
13277 }
13278 }
13279 let (Some(fa), Some(_last)) = (fa, last_memset) else {
13280 return Ok(None);
13281 };
13282 let mut combine: Option<sys::CUgraphNode> = None;
13283 for dep in dependents(fa)? {
13284 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13285 combine = Some(dep);
13286 }
13287 }
13288 let Some(combine) = combine else {
13289 return Ok(None);
13290 };
13291 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13294 unsafe {
13295 cu_try(
13296 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
13297 "fa-site KernelNodeGetParams",
13298 )?;
13299 }
13300 let arg_i32 =
13301 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
13302 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
13303 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
13305 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13306 unsafe {
13307 cu_try(
13308 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13309 "fa-site MemsetNodeGetParams",
13310 )?;
13311 }
13312 Ok(mp.width)
13313 };
13314 let mut widest = memsets[0];
13315 for &ms in &memsets[1..] {
13316 if width_of(ms)? > width_of(widest)? {
13317 widest = ms;
13318 }
13319 }
13320 let memset_m: Vec<sys::CUgraphNode> =
13321 memsets.iter().copied().filter(|&m| m != widest).collect();
13322 Ok(Some(TokenGraphFaSite {
13323 ctx,
13324 memset_o: widest,
13325 memset_m: [memset_m[0], memset_m[1]],
13326 fa,
13327 combine,
13328 window: win as usize,
13329 n_head: nh as usize,
13330 n_head_kv: nhkv as usize,
13331 head_dim: hd as usize,
13332 }))
13333}
13334
13335pub struct TokenGraph {
13336 exec: cudarc::driver::sys::CUgraphExec,
13337 parent: cudarc::driver::sys::CUgraph,
13338 _children: Vec<TokenGraphChild>,
13339 fa_sites: Vec<TokenGraphFaSite>,
13340}
13341
13342unsafe impl Send for TokenGraph {}
13343
13344impl TokenGraph {
13345 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
13350 use cudarc::driver::sys;
13351 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13352 if r == sys::CUresult::CUDA_SUCCESS {
13353 Ok(())
13354 } else {
13355 Err(format!("{what}: {r:?}").into())
13356 }
13357 }
13358 for site in &self.fa_sites {
13359 let layer_bucket = if site.window > 0 {
13360 bucket.min(site.window)
13361 } else {
13362 bucket
13363 };
13364 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
13365 let nsp = layer_bucket.div_ceil(sp).max(1);
13366 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13368 unsafe {
13369 cu_try(
13370 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
13371 "retarget fa GetParams",
13372 )?;
13373 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
13374 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
13375 params.gridDimY = nsp as u32;
13376 cu_try(
13377 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
13378 "retarget fa SetParams",
13379 )?;
13380 }
13381 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13383 unsafe {
13384 cu_try(
13385 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
13386 "retarget combine GetParams",
13387 )?;
13388 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
13389 cu_try(
13390 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
13391 "retarget combine SetParams",
13392 )?;
13393 }
13394 let set_width =
13396 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
13397 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13398 unsafe {
13399 cu_try(
13400 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13401 "retarget memset GetParams",
13402 )?;
13403 }
13404 mp.width = width;
13405 unsafe {
13406 cu_try(
13407 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
13408 "retarget memset SetParams",
13409 )?;
13410 }
13411 Ok(())
13412 };
13413 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
13414 set_width(site.memset_m[0], site.n_head * nsp)?;
13415 set_width(site.memset_m[1], site.n_head * nsp)?;
13416 }
13417 Ok(())
13418 }
13419
13420 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
13421 use cudarc::driver::sys;
13422 let _main = e.gpu.enter_main()?;
13423 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
13424 if r != sys::CUresult::CUDA_SUCCESS {
13425 return Err(format!("token graph launch: {r:?}").into());
13426 }
13427 Ok(())
13428 }
13429}
13430
13431impl Drop for TokenGraph {
13432 fn drop(&mut self) {
13433 unsafe {
13434 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
13435 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
13436 }
13437 }
13438}
13439
13440std::thread_local! {
13441 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
13442 const { std::cell::RefCell::new(None) };
13443}
13444
13445pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
13447 let builder = TokenGraphBuilder::new()?;
13448 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
13449 Ok(())
13450}
13451
13452pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
13454 let builder = TOKEN_GRAPH_BUILDER
13455 .with(|cell| cell.borrow_mut().take())
13456 .ok_or("token graph build was not begun")?;
13457 builder.finish()
13458}
13459
13460pub fn token_graph_building() -> bool {
13462 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
13463}
13464
13465pub fn graph_section<F>(
13470 engine: &Engine,
13471 parallel_group: Option<u32>,
13472 f: F,
13473) -> Result<(), Box<dyn std::error::Error>>
13474where
13475 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13476{
13477 graph_section_opts(engine, parallel_group, false, false, f)
13478}
13479
13480pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13482where
13483 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13484{
13485 graph_section_opts(engine, None, false, true, f)
13486}
13487
13488pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13491where
13492 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13493{
13494 graph_section_opts(engine, None, true, false, f)
13495}
13496
13497pub fn graph_section_opts<F>(
13498 engine: &Engine,
13499 parallel_group: Option<u32>,
13500 detached: bool,
13501 absorb: bool,
13502 f: F,
13503) -> Result<(), Box<dyn std::error::Error>>
13504where
13505 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13506{
13507 let building = token_graph_building();
13508 if !building {
13509 let mut f = f;
13510 return f();
13511 }
13512 let (child, ctx) = {
13513 let _main = engine.gpu.enter_main()?;
13514 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
13515 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
13516 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13517 return Err(format!("graph section ctx query: {r:?}").into());
13518 }
13519 let mut f = f;
13520 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
13523 (child, ctx)
13524 };
13525 TOKEN_GRAPH_BUILDER.with(|cell| {
13526 cell.borrow_mut()
13527 .as_mut()
13528 .expect("builder checked above")
13529 .push_child(child, parallel_group, detached, absorb, ctx)
13530 })
13531}