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}
935
936pub struct ResidentColumnParallel {
937 ranks: Vec<ResidentE4m3Rank>,
938 out_features: usize,
939 in_features: usize,
940}
941
942pub struct ResidentRowParallel {
943 ranks: Vec<ResidentE4m3Rank>,
944 out_features: usize,
945 in_features: usize,
946}
947
948pub struct ResidentBf16ColumnParallel {
949 ranks: Vec<ResidentBf16Rank>,
950 out_features: usize,
951 in_features: usize,
952 canonical_chunk_rows: Option<usize>,
953}
954
955pub struct ResidentBf16RowParallel {
956 ranks: Vec<ResidentBf16Rank>,
957 out_features: usize,
958 in_features: usize,
959}
960
961pub struct ResidentStepBf16RowParallel {
962 ranks: Vec<Vec<ResidentBf16Rank>>,
963 out_features: usize,
964 in_features: usize,
965 canonical_chunk_cols: usize,
966}
967
968pub struct ResidentSigmoidTopKRouter {
970 weight: CudaSlice<f32>,
971 correction_bias: CudaSlice<f32>,
972 active: CudaSlice<u8>,
973 root_device: usize,
974 input_width: usize,
975 expert_count: usize,
976 experts_per_token: usize,
977 active_count: usize,
978 scaling_factor: f32,
979 route_norm: bool,
980}
981
982pub struct SigmoidTopKHostOutput {
983 pub logits: Vec<f32>,
984 pub selected: Vec<u32>,
985 pub weights: Vec<f32>,
986}
987
988pub struct ResidentReplicatedBf16SwiGlu {
990 gate: Vec<ResidentBf16Rank>,
991 up: Vec<ResidentBf16Rank>,
992 down: Vec<ResidentBf16Rank>,
993 input_width: usize,
994 intermediate_width: usize,
995}
996
997pub struct ResidentReplicatedDeviceRows {
1002 ranks: Vec<CudaSlice<f32>>,
1003 tokens: usize,
1004 width: usize,
1005}
1006
1007impl ResidentReplicatedDeviceRows {
1008 pub fn tokens(&self) -> usize {
1009 self.tokens
1010 }
1011
1012 pub fn width(&self) -> usize {
1013 self.width
1014 }
1015
1016 pub fn ranks(&self) -> usize {
1017 self.ranks.len()
1018 }
1019}
1020
1021pub fn moe_residual_host(
1023 residual: &[f32],
1024 routed: &[f32],
1025 shared: &[f32],
1026) -> Result<Vec<f32>, String> {
1027 if residual.len() != routed.len() || residual.len() != shared.len() {
1028 return Err(format!(
1029 "MoE residual lengths residual={} routed={} shared={}",
1030 residual.len(),
1031 routed.len(),
1032 shared.len()
1033 ));
1034 }
1035 let ffn = routed
1036 .iter()
1037 .zip(shared)
1038 .map(|(&routed, &shared)| routed + shared)
1039 .collect::<Vec<_>>();
1040 Ok(residual
1041 .iter()
1042 .zip(ffn)
1043 .map(|(&residual, ffn)| residual + ffn)
1044 .collect())
1045}
1046
1047pub use memra_kv::{
1048 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1049};
1050
1051pub struct ResidentTpExpert {
1057 gate: ResidentColumnParallel,
1058 up: ResidentColumnParallel,
1059 down: ResidentRowParallel,
1060 input_width: usize,
1061 expert_width: usize,
1062}
1063
1064struct ResidentE4m3ExpertBankRank {
1065 codes: CudaSlice<u8>,
1066 scales: CudaSlice<f32>,
1067 expert_range: Range<usize>,
1068 out_features: usize,
1069 in_features: usize,
1070 code_stride: usize,
1071 scale_stride: usize,
1072 k_blocks: Option<usize>,
1075}
1076
1077struct PackedE4m3ExpertBankRank {
1078 codes: Vec<u8>,
1079 scales: Vec<f32>,
1080 expert_range: Range<usize>,
1081 out_features: usize,
1082 in_features: usize,
1083 code_stride: usize,
1084 scale_stride: usize,
1085 k_blocks: Option<usize>,
1086}
1087
1088struct ResidentEpRank {
1089 gate: ResidentE4m3ExpertBankRank,
1090 up: ResidentE4m3ExpertBankRank,
1091 down: ResidentE4m3ExpertBankRank,
1092}
1093
1094pub struct ResidentExpertParallel {
1101 ranks: Vec<ResidentEpRank>,
1102 expert_count: usize,
1103 input_width: usize,
1104 expert_width: usize,
1105}
1106
1107pub struct StepGroupedFp8ProjectionOutput {
1112 pub gate: Vec<f32>,
1113 pub up: Vec<f32>,
1114 pub down: Vec<f32>,
1115}
1116
1117pub struct PreparedStepGroupedFp8Gate {
1122 device: usize,
1123 gate: ResidentE4m3ExpertBankRank,
1124 up: ResidentE4m3ExpertBankRank,
1125 down: ResidentE4m3ExpertBankRank,
1126 input: CudaSlice<f32>,
1127 route_csr: DeviceExpertCsr,
1128 down_csr: DeviceExpertCsr,
1129 gate_workspace: Fp8GroupedWorkspace,
1130 up_workspace: Fp8GroupedWorkspace,
1131 down_workspace: Fp8GroupedWorkspace,
1132 activation: CudaSlice<f32>,
1133 activation_limit: Option<f32>,
1134 tokens: usize,
1135 pairs: usize,
1136}
1137
1138impl PreparedStepGroupedFp8Gate {
1139 pub fn tokens(&self) -> usize {
1140 self.tokens
1141 }
1142
1143 pub fn pairs(&self) -> usize {
1144 self.pairs
1145 }
1146}
1147
1148struct PreparedStepGroupedExpertOwner {
1149 rank: usize,
1150 global_pairs: Vec<usize>,
1151 route_csr: DeviceExpertCsr,
1152 down_csr: DeviceExpertCsr,
1153 gate_workspace: Fp8GroupedWorkspace,
1154 up_workspace: Fp8GroupedWorkspace,
1155 down_workspace: Fp8GroupedWorkspace,
1156 activation: CudaSlice<f32>,
1157}
1158
1159struct StepGroupedExpertOwnerSchedule {
1160 global_pairs: Vec<usize>,
1161 route_csr: ExpertCsr,
1162 down_csr: ExpertCsr,
1163}
1164
1165pub struct PreparedStepGroupedExpertParallelGate {
1171 rank_inputs: Vec<CudaSlice<f32>>,
1172 owners: Vec<PreparedStepGroupedExpertOwner>,
1173 activation_limit: Option<f32>,
1174 tokens: usize,
1175 pairs: usize,
1176 max_tokens: usize,
1177 max_pairs: usize,
1178 input_width: usize,
1179 expert_width: usize,
1180 generation: u64,
1181 executed_generation: Option<u64>,
1182 ready: bool,
1183}
1184
1185impl PreparedStepGroupedExpertParallelGate {
1186 pub fn tokens(&self) -> usize {
1187 self.tokens
1188 }
1189
1190 pub fn pairs(&self) -> usize {
1191 self.pairs
1192 }
1193
1194 pub fn max_tokens(&self) -> usize {
1195 self.max_tokens
1196 }
1197
1198 pub fn input_width(&self) -> usize {
1199 self.input_width
1200 }
1201
1202 pub fn expert_width(&self) -> usize {
1203 self.expert_width
1204 }
1205
1206 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1207 validate_step_expert_activation_limit(limit)?;
1208 self.activation_limit = limit;
1209 self.executed_generation = None;
1210 Ok(())
1211 }
1212
1213 pub fn active_owners(&self) -> usize {
1214 self.owners
1215 .iter()
1216 .filter(|owner| !owner.global_pairs.is_empty())
1217 .count()
1218 }
1219
1220 pub fn owner_pair_counts(&self) -> Vec<usize> {
1221 self.owners
1222 .iter()
1223 .map(|owner| owner.global_pairs.len())
1224 .collect()
1225 }
1226
1227 pub fn generation(&self) -> u64 {
1228 self.generation
1229 }
1230}
1231
1232struct PreparedPeerWeightedRouteOwner {
1233 token_rows: CudaSlice<i32>,
1234 slots: CudaSlice<i32>,
1235 weights: CudaSlice<f32>,
1236 active_pairs: usize,
1237}
1238
1239pub struct PreparedPeerWeightedRouteCombine {
1245 root_device: usize,
1246 owners: Vec<PreparedPeerWeightedRouteOwner>,
1247 peer_staging: CudaSlice<f32>,
1248 slots: CudaSlice<f32>,
1249 weights: CudaSlice<f32>,
1250 output: CudaSlice<f32>,
1251 peer_devices: Vec<usize>,
1252 peer_outputs: Vec<CudaSlice<f32>>,
1253 width: usize,
1254 experts_per_token: usize,
1255 max_tokens: usize,
1256 max_pairs: usize,
1257 tokens: usize,
1258 pairs: usize,
1259 projection_generation: u64,
1260 output_generation: Option<u64>,
1261 broadcast_generation: Option<u64>,
1262 ready: bool,
1263}
1264
1265impl PreparedPeerWeightedRouteCombine {
1266 pub fn tokens(&self) -> usize {
1267 self.tokens
1268 }
1269
1270 pub fn pairs(&self) -> usize {
1271 self.pairs
1272 }
1273
1274 pub fn owner_pair_counts(&self) -> Vec<usize> {
1275 self.owners.iter().map(|owner| owner.active_pairs).collect()
1276 }
1277
1278 pub fn distributed_ranks(&self) -> usize {
1279 1 + self.peer_outputs.len()
1280 }
1281}
1282
1283struct ResidentTpExpertBank {
1284 gate: Vec<ResidentE4m3ExpertBankRank>,
1285 up: Vec<ResidentE4m3ExpertBankRank>,
1286 down: Vec<ResidentE4m3ExpertBankRank>,
1287 expert_count: usize,
1288 input_width: usize,
1289 expert_width: usize,
1290}
1291
1292pub struct ResidentTensorParallel {
1298 bank: ResidentTpExpertBank,
1299}
1300
1301pub struct TpE4m3HostBounce {
1307 devices: Vec<usize>,
1308 ranks: Vec<Engine>,
1309 native_p2p: bool,
1310 ep_device_arithmetic: bool,
1311 bulk_p2p: bool,
1312 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1315}
1316
1317pub enum StepTpGateShards<'a> {
1326 F32(&'a [crate::CudaSlice<f32>]),
1327 Bf16(&'a [crate::CudaSlice<u8>]),
1328}
1329
1330pub struct StepTpDecodeV2Ws {
1331 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1335 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1336 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1337 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1338 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1339 pub(crate) tcol_cap: usize,
1340 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1347 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1348 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1349 pub(crate) fa2_cap: usize,
1350 rope_k_t: Vec<CudaSlice<f32>>,
1354 rope_ctr_t: Vec<CudaSlice<u32>>,
1355 rope_pos_t: Vec<CudaSlice<i32>>,
1356 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1359 tcol_gated: Vec<CudaSlice<f32>>,
1360 tcol_opart: Vec<CudaSlice<f32>>,
1361 tcol_opeer: Option<CudaSlice<f32>>,
1362 tcol_omix: Option<CudaSlice<f32>>,
1363 tcol_ocap: usize,
1364 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1367 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1368 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1369 pub(crate) q: Vec<CudaSlice<f32>>,
1370 pub(crate) k: Vec<CudaSlice<f32>>,
1371 pub(crate) pos: Vec<CudaSlice<i32>>,
1372 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1374 pub(crate) gate: Vec<CudaSlice<f32>>,
1375 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1376 pub(crate) gated: Vec<CudaSlice<f32>>,
1377 o_partials: Vec<Vec<CudaSlice<f32>>>,
1379 ev_rank: Vec<CudaEvent>,
1381 peer_partial: CudaSlice<f32>,
1383 reduce_a: CudaSlice<f32>,
1384 reduce_b: CudaSlice<f32>,
1385 zeros: CudaSlice<f32>,
1387 pub(crate) k_shadow: CudaSlice<f32>,
1388 pub(crate) v_shadow: CudaSlice<f32>,
1389 ev_refresh: CudaEvent,
1390 ev_oproj: CudaEvent,
1391 gate_e: CudaSlice<f32>,
1393 pub(crate) h_stage: Option<CudaSlice<f32>>,
1396 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1397 attn_in: Vec<CudaSlice<f32>>,
1401 raw_h_stage: u64,
1403 raw_pos_stage: u64,
1404 raw_attn_in: Vec<u64>,
1405 raw_pos: Vec<u64>,
1406 raw_o_partial1: u64,
1407 raw_peer_partial: u64,
1408 raw_k1: u64,
1409 raw_v1: u64,
1410 raw_k_shadow: u64,
1411 raw_v_shadow: u64,
1412 raw_mixed_stage_e: u64,
1416 raw_reduce_a: u64,
1417 raw_shadow_stage_e: (u64, u64),
1418 ev_entry: CudaEvent,
1419 e_device: usize,
1420 local_q_dim: usize,
1422 local_kv_dim: usize,
1423 heads: usize,
1424 pub(crate) o_out: usize,
1425 o_block_cols: usize,
1426 blocks_per_rank: usize,
1427}
1428
1429impl TpE4m3HostBounce {
1430 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1431 Self::new_inner(devices, false, false, false, false)
1432 }
1433
1434 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1435 Self::new_inner(devices, false, true, false, false)
1436 }
1437
1438 pub fn new_native_p2p_device_arithmetic(
1439 devices: &[usize],
1440 ) -> Result<Self, Box<dyn std::error::Error>> {
1441 Self::new_inner(devices, false, true, true, false)
1442 }
1443
1444 pub(crate) fn new_configured(
1445 devices: &[usize],
1446 native_p2p: bool,
1447 ep_device_arithmetic: bool,
1448 bulk_p2p: bool,
1449 ) -> Result<Self, Box<dyn std::error::Error>> {
1450 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1451 }
1452
1453 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1458 Self::new_inner(&[device], true, false, false, false)
1459 }
1460
1461 fn new_inner(
1462 devices: &[usize],
1463 allow_single_rank: bool,
1464 native_p2p: bool,
1465 ep_device_arithmetic: bool,
1466 bulk_p2p: bool,
1467 ) -> Result<Self, Box<dyn std::error::Error>> {
1468 if ep_device_arithmetic && !native_p2p {
1469 return Err("device-resident EP arithmetic requires native P2P".into());
1470 }
1471 if bulk_p2p && !native_p2p {
1472 return Err("bulk TP transport requires native P2P".into());
1473 }
1474 let minimum = if allow_single_rank { 1 } else { 2 };
1475 if !(minimum..=8).contains(&devices.len()) {
1476 return Err(format!(
1477 "TP reference requires {minimum}..=8 devices, got {}",
1478 devices.len()
1479 )
1480 .into());
1481 }
1482 let mut unique = devices.to_vec();
1483 unique.sort_unstable();
1484 unique.dedup();
1485 if unique.len() != devices.len() {
1486 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1487 }
1488 let ranks = devices
1489 .iter()
1490 .map(|&device| Engine::new(device))
1491 .collect::<Result<Vec<_>, _>>()?;
1492 if native_p2p {
1493 configure_native_p2p(&ranks, devices)?;
1494 }
1495 if allow_single_rank {
1496 eprintln!(
1497 "[tp] canonical oracle transport=local device={} performance_claim=false",
1498 devices[0]
1499 );
1500 } else if native_p2p {
1501 if ep_device_arithmetic {
1502 eprintln!(
1503 "[tp] correctness transport=native-p2p devices={devices:?} \
1504 native_p2p=true activation=device-host-exact \
1505 accumulation=device-host-exact output=root-readback \
1506 bulk_p2p={bulk_p2p} performance_claim=false"
1507 );
1508 } else {
1509 eprintln!(
1510 "[tp] correctness transport=native-p2p devices={devices:?} \
1511 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1512 performance_claim=false"
1513 );
1514 }
1515 } else {
1516 eprintln!(
1517 "[tp] correctness transport=host-bounce devices={devices:?} \
1518 native_p2p=false performance_claim=false"
1519 );
1520 }
1521 Ok(Self {
1522 devices: devices.to_vec(),
1523 ranks,
1524 native_p2p,
1525 ep_device_arithmetic,
1526 bulk_p2p,
1527 decode_v2: std::sync::Mutex::new(Vec::new()),
1528 })
1529 }
1530
1531 pub fn devices(&self) -> &[usize] {
1532 &self.devices
1533 }
1534
1535 pub fn native_p2p(&self) -> bool {
1536 self.native_p2p
1537 }
1538
1539 pub fn bulk_p2p(&self) -> bool {
1540 self.bulk_p2p
1541 }
1542
1543 pub fn expert_activation_label(&self) -> &'static str {
1544 if self.ep_device_arithmetic {
1545 "device-host-exact"
1546 } else {
1547 "host-canonical"
1548 }
1549 }
1550
1551 pub fn expert_accumulation_label(&self) -> &'static str {
1552 self.expert_activation_label()
1553 }
1554
1555 pub fn expert_output_label(&self) -> &'static str {
1556 if self.ep_device_arithmetic {
1557 "root-readback"
1558 } else {
1559 "host-accumulated"
1560 }
1561 }
1562
1563 pub fn transport_label(&self) -> &'static str {
1564 if self.devices.len() == 1 {
1565 "local"
1566 } else if self.native_p2p {
1567 "native-p2p"
1568 } else {
1569 "host-bounce"
1570 }
1571 }
1572
1573 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1574 self.ranks
1575 .iter()
1576 .map(|rank| rank.ctx().name().map_err(Into::into))
1577 .collect()
1578 }
1579
1580 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1586 self.ranks.get(rank)
1587 }
1588
1589 pub fn allocate_tp_kv_cache(
1590 &self,
1591 kv_dim_k: usize,
1592 kv_dim_v: usize,
1593 capacity: usize,
1594 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1595 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1596 }
1597
1598 pub fn allocate_tp_swa_kv_cache(
1599 &self,
1600 kv_dim_k: usize,
1601 kv_dim_v: usize,
1602 capacity: usize,
1603 window: usize,
1604 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1605 if window == 0 {
1606 return Err("TP SWA KV window must be nonzero".into());
1607 }
1608 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1609 }
1610
1611 fn allocate_tp_kv_cache_inner(
1612 &self,
1613 kv_dim_k: usize,
1614 kv_dim_v: usize,
1615 capacity: usize,
1616 window: Option<usize>,
1617 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1618 if capacity == 0 || capacity > i32::MAX as usize {
1619 return Err(
1620 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1621 );
1622 }
1623 let tp = self.ranks.len();
1624 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1625 let physical_rows = window
1626 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1627 .unwrap_or(capacity);
1628 let k_plane_bytes = physical_rows
1629 .checked_mul(shape.k_token_bytes)
1630 .and_then(|bytes| bytes.checked_add(8))
1631 .ok_or("TP KV K plane-byte overflow")?;
1632 let v_plane_bytes = physical_rows
1633 .checked_mul(shape.v_token_bytes)
1634 .and_then(|bytes| bytes.checked_add(8))
1635 .ok_or("TP KV V plane-byte overflow")?;
1636 let mut ranks = Vec::with_capacity(tp);
1637 for engine in &self.ranks {
1638 let _main = engine.gpu.enter_main()?;
1639 ranks.push(ResidentTpKvCacheRank::new(
1640 engine.alloc_u8(k_plane_bytes)?,
1641 engine.alloc_u8(v_plane_bytes)?,
1642 engine.htod_i32(&[0])?,
1643 ));
1644 }
1645 Ok(match window {
1646 Some(window) => ResidentTpKvCache::new_swa(
1647 ranks,
1648 shape.kv_dim_k,
1649 shape.kv_dim_v,
1650 shape.k_token_bytes,
1651 shape.v_token_bytes,
1652 capacity,
1653 window,
1654 ),
1655 None => ResidentTpKvCache::new(
1656 ranks,
1657 shape.kv_dim_k,
1658 shape.kv_dim_v,
1659 shape.k_token_bytes,
1660 shape.v_token_bytes,
1661 capacity,
1662 ),
1663 })
1664 }
1665
1666 pub fn grow_tp_kv_cache(
1667 &self,
1668 source: &ResidentTpKvCache,
1669 target_capacity: usize,
1670 rows: usize,
1671 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1672 self.validate_tp_kv_cache(source)?;
1673 let plan = source.prepare_grow(target_capacity, rows)?;
1674 let ranks = self.ranks.len();
1675 let global_k = source
1676 .kv_dim_k()
1677 .checked_mul(ranks)
1678 .ok_or("TP KV grow global K dimension overflow")?;
1679 let global_v = source
1680 .kv_dim_v()
1681 .checked_mul(ranks)
1682 .ok_or("TP KV grow global V dimension overflow")?;
1683 let mut target = match source.ring_window() {
1684 Some(window) => {
1685 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1686 }
1687 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1688 };
1689 self.validate_tp_kv_cache(&target)?;
1690
1691 for (rank, engine) in self.ranks.iter().enumerate() {
1692 let _main = engine.gpu.enter_main()?;
1693 let src = source
1694 .rank(rank)
1695 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1696 let dst = target
1697 .rank_mut(rank)
1698 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1699 if plan.k_bytes() > 0 {
1700 engine.copy_u8_range_into(
1701 dst.k_mut(),
1702 0,
1703 src.k(),
1704 plan.source_row() * source.k_tok_bytes(),
1705 plan.k_bytes(),
1706 )?;
1707 }
1708 if plan.v_bytes() > 0 {
1709 engine.copy_u8_range_into(
1710 dst.v_mut(),
1711 0,
1712 src.v(),
1713 plan.source_row() * source.v_tok_bytes(),
1714 plan.v_bytes(),
1715 )?;
1716 }
1717 }
1718 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1719
1720 for engine in &self.ranks {
1723 let _main = engine.gpu.enter_main()?;
1724 engine.stream().synchronize()?;
1725 }
1726 let physical_copy_rows = plan.copy_rows();
1727 target.publish_grow(plan)?;
1728 eprintln!(
1729 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1730 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1731 rank_streams_synchronized=true generation_preserved=true",
1732 rows,
1733 source.capacity(),
1734 target_capacity,
1735 ranks,
1736 physical_copy_rows,
1737 source.ring_window(),
1738 );
1739 Ok(target)
1740 }
1741
1742 pub fn hydrate_tp_kv_cache(
1743 &self,
1744 cache: &mut ResidentTpKvCache,
1745 rows: usize,
1746 k_rows: &[u8],
1747 v_rows: &[u8],
1748 ) -> Result<(), Box<dyn std::error::Error>> {
1749 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1750 }
1751
1752 pub fn hydrate_tp_kv_cache_from(
1753 &self,
1754 cache: &mut ResidentTpKvCache,
1755 logical_len: usize,
1756 resident_start: usize,
1757 k_rows: &[u8],
1758 v_rows: &[u8],
1759 ) -> Result<(), Box<dyn std::error::Error>> {
1760 self.validate_tp_kv_cache(cache)?;
1761 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1762 return Err(format!(
1763 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1764 cache.committed_len(),
1765 cache.staged_len()
1766 )
1767 .into());
1768 }
1769 if resident_start > logical_len || logical_len > cache.capacity() {
1770 return Err(format!(
1771 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1772 cache.capacity(),
1773 )
1774 .into());
1775 }
1776 let rows = logical_len - resident_start;
1777 if rows > cache.physical_capacity() {
1778 return Err(format!(
1779 "TP KV hydration rows {rows} exceed physical capacity {}",
1780 cache.physical_capacity()
1781 )
1782 .into());
1783 }
1784 for rank in 0..self.ranks.len() {
1785 let k_rank =
1786 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1787 let v_rank =
1788 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1789 let engine = &self.ranks[rank];
1790 let _main = engine.gpu.enter_main()?;
1791 let rank_cache = cache
1792 .rank_mut(rank)
1793 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1794 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1795 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1796 }
1797 cache.publish_hydration(logical_len, resident_start)?;
1798 Ok(())
1799 }
1800
1801 pub fn append_tp_kv_transaction(
1802 &self,
1803 cache: &mut ResidentTpKvCache,
1804 transaction: TpKvTransaction,
1805 k_shards: &[CudaSlice<f32>],
1806 v_shards: &[CudaSlice<f32>],
1807 rows: usize,
1808 ) -> Result<(), Box<dyn std::error::Error>> {
1809 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1810 }
1811
1812 #[allow(clippy::too_many_arguments)]
1817 pub fn append_tp_kv_transaction_inner(
1818 &self,
1819 cache: &mut ResidentTpKvCache,
1820 transaction: TpKvTransaction,
1821 k_shards: &[CudaSlice<f32>],
1822 v_shards: &[CudaSlice<f32>],
1823 rows: usize,
1824 external_rank_appends: bool,
1825 ) -> Result<(), Box<dyn std::error::Error>> {
1826 self.validate_tp_kv_cache(cache)?;
1827 let plan = cache.prepare_append(transaction, rows)?;
1828 let target = plan.target();
1829 let expected_k = rows
1830 .checked_mul(cache.kv_dim_k())
1831 .ok_or("TP KV K append size overflow")?;
1832 let expected_v = rows
1833 .checked_mul(cache.kv_dim_v())
1834 .ok_or("TP KV V append size overflow")?;
1835 if !external_rank_appends
1838 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1839 {
1840 return Err(format!(
1841 "TP KV append shard counts k={} v={} != ranks {}",
1842 k_shards.len(),
1843 v_shards.len(),
1844 self.ranks.len()
1845 )
1846 .into());
1847 }
1848 let kv_dim_k = cache.kv_dim_k();
1849 let kv_dim_v = cache.kv_dim_v();
1850 let k_tok_bytes = cache.k_tok_bytes();
1851 let v_tok_bytes = cache.v_tok_bytes();
1852 if let Some(KvRingAppend::Rebase {
1853 src_row,
1854 keep_rows,
1855 new_base,
1856 ..
1857 }) = plan.ring_append()
1858 {
1859 for rank in 0..self.ranks.len() {
1860 let engine = &self.ranks[rank];
1861 let _main = engine.gpu.enter_main()?;
1862 let rank_cache = cache
1863 .rank_mut(rank)
1864 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1865 if keep_rows > 0 {
1866 let k_len = keep_rows
1867 .checked_mul(k_tok_bytes)
1868 .ok_or("TP KV K rebase-byte overflow")?;
1869 let v_len = keep_rows
1870 .checked_mul(v_tok_bytes)
1871 .ok_or("TP KV V rebase-byte overflow")?;
1872 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1873 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
1874 engine.copy_u8_range_into(
1875 &mut k_tmp,
1876 0,
1877 rank_cache.k(),
1878 src_row * k_tok_bytes,
1879 k_len,
1880 )?;
1881 engine.copy_u8_range_into(
1882 &mut v_tmp,
1883 0,
1884 rank_cache.v(),
1885 src_row * v_tok_bytes,
1886 v_len,
1887 )?;
1888 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
1889 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
1890 }
1891 if rank_cache.base_d().is_some() {
1895 let value = new_base as i32;
1896 let rank_cache = cache
1897 .rank_mut(rank)
1898 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1899 if let Some(base_d) = rank_cache.base_d_mut() {
1900 engine.set_i32_one(base_d, value)?;
1901 }
1902 }
1903 }
1904 }
1905 cache.publish_append_rebase(plan)?;
1906 let write_row = plan.write_row();
1907 for rank in 0..self.ranks.len() {
1908 if external_rank_appends {
1909 break;
1910 }
1911 let engine = &self.ranks[rank];
1912 let _main = engine.gpu.enter_main()?;
1913 if k_shards[rank].len() != expected_k
1914 || v_shards[rank].len() != expected_v
1915 || k_shards[rank].ordinal() != engine.ctx().ordinal()
1916 || v_shards[rank].ordinal() != engine.ctx().ordinal()
1917 {
1918 return Err(format!(
1919 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
1920 != expected {expected_k}/{expected_v} on device {}",
1921 k_shards[rank].len(),
1922 k_shards[rank].ordinal(),
1923 v_shards[rank].len(),
1924 v_shards[rank].ordinal(),
1925 engine.ctx().ordinal(),
1926 )
1927 .into());
1928 }
1929 let rank_cache = cache
1930 .rank_mut(rank)
1931 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1932 let (rank_k, rank_v) = rank_cache.planes_mut();
1933 engine.append_kv_quantized_rows(
1934 &k_shards[rank],
1935 &v_shards[rank],
1936 rank_k,
1937 rank_v,
1938 write_row,
1939 rows,
1940 kv_dim_k,
1941 kv_dim_v,
1942 k_tok_bytes,
1943 v_tok_bytes,
1944 Engine::kv_fp8_on(),
1945 )?;
1946 }
1947 if !external_rank_appends {
1948 self.set_tp_kv_len_mirrors(cache, target)?;
1951 }
1952 cache.publish_append_plan(plan)?;
1953 Ok(())
1954 }
1955
1956 pub fn commit_tp_kv_transaction(
1957 &self,
1958 cache: &mut ResidentTpKvCache,
1959 transaction: TpKvTransaction,
1960 accepted_rows: usize,
1961 ) -> Result<(), Box<dyn std::error::Error>> {
1962 self.validate_tp_kv_cache(cache)?;
1963 let target = cache.commit_target(transaction, accepted_rows)?;
1964 self.set_tp_kv_len_mirrors(cache, target)?;
1965 cache.publish_finalize(transaction, target)?;
1966 Ok(())
1967 }
1968
1969 pub fn commit_tp_kv_transaction_external(
1975 &self,
1976 cache: &mut ResidentTpKvCache,
1977 transaction: TpKvTransaction,
1978 accepted_rows: usize,
1979 ) -> Result<(), Box<dyn std::error::Error>> {
1980 self.validate_tp_kv_cache(cache)?;
1981 let target = cache.commit_target(transaction, accepted_rows)?;
1982 cache.publish_finalize(transaction, target)?;
1983 Ok(())
1984 }
1985
1986 pub fn rollback_tp_kv_transaction(
1987 &self,
1988 cache: &mut ResidentTpKvCache,
1989 transaction: TpKvTransaction,
1990 ) -> Result<(), Box<dyn std::error::Error>> {
1991 self.validate_tp_kv_cache(cache)?;
1992 cache.validate_transaction(transaction)?;
1993 let target = transaction.base_len();
1994 self.set_tp_kv_len_mirrors(cache, target)?;
1995 cache.publish_finalize(transaction, target)?;
1996 Ok(())
1997 }
1998
1999 pub fn tp_kv_device_lengths(
2000 &self,
2001 cache: &ResidentTpKvCache,
2002 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2003 self.validate_tp_kv_cache(cache)?;
2004 let mut lengths = Vec::with_capacity(self.ranks.len());
2005 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2006 let _main = engine.gpu.enter_main()?;
2007 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2008 }
2009 Ok(lengths)
2010 }
2011
2012 fn set_tp_kv_len_mirrors(
2013 &self,
2014 cache: &mut ResidentTpKvCache,
2015 len: usize,
2016 ) -> Result<(), Box<dyn std::error::Error>> {
2017 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2018 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2019 let _main = engine.gpu.enter_main()?;
2020 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2021 }
2022 Ok(())
2023 }
2024
2025 fn validate_tp_kv_cache(
2026 &self,
2027 cache: &ResidentTpKvCache,
2028 ) -> Result<(), Box<dyn std::error::Error>> {
2029 if cache.ranks_len() != self.ranks.len() {
2030 return Err(format!(
2031 "TP KV cache ranks {} != runtime ranks {}",
2032 cache.ranks_len(),
2033 self.ranks.len()
2034 )
2035 .into());
2036 }
2037 let expected_k = cache
2038 .physical_capacity()
2039 .checked_mul(cache.k_tok_bytes())
2040 .and_then(|bytes| bytes.checked_add(8))
2041 .ok_or("TP KV K plane validation overflow")?;
2042 let expected_v = cache
2043 .physical_capacity()
2044 .checked_mul(cache.v_tok_bytes())
2045 .and_then(|bytes| bytes.checked_add(8))
2046 .ok_or("TP KV V plane validation overflow")?;
2047 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2048 let device = engine.ctx().ordinal();
2049 if rank_cache.k().len() != expected_k
2050 || rank_cache.v().len() != expected_v
2051 || rank_cache.len_d().len() != 1
2052 || rank_cache.k().ordinal() != device
2053 || rank_cache.v().ordinal() != device
2054 || rank_cache.len_d().ordinal() != device
2055 {
2056 return Err(format!(
2057 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2058 )
2059 .into());
2060 }
2061 }
2062 Ok(())
2063 }
2064
2065 pub fn full(
2066 &self,
2067 matrix: E4m3BlockMatrix<'_>,
2068 activations: &[f32],
2069 tokens: usize,
2070 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2071 matrix.validate()?;
2072 validate_activations(activations, tokens, matrix.in_features)?;
2073 run_rank(&self.ranks[0], matrix, activations, tokens)
2074 }
2075
2076 pub fn column_parallel(
2080 &self,
2081 matrix: E4m3BlockMatrix<'_>,
2082 activations: &[f32],
2083 tokens: usize,
2084 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2085 matrix.validate()?;
2086 validate_activations(activations, tokens, matrix.in_features)?;
2087 let tp = self.ranks.len();
2088 if matrix.out_features % tp != 0 {
2089 return Err(format!(
2090 "column-parallel out_features {} is not divisible by TP={tp}",
2091 matrix.out_features
2092 )
2093 .into());
2094 }
2095 let local_out = matrix.out_features / tp;
2096 if local_out % FP8_BLOCK != 0 {
2097 return Err(format!(
2098 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2099 E4M3 scale block"
2100 )
2101 .into());
2102 }
2103
2104 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2105 let mut rank_outputs = Vec::with_capacity(tp);
2106 for (rank_index, rank) in self.ranks.iter().enumerate() {
2107 let shard = column_shard(matrix, tp, rank_index)?;
2108 let output = run_rank(rank, shard, activations, tokens)?;
2109 let row_start = rank_index * local_out;
2110 for token in 0..tokens {
2111 gathered[token * matrix.out_features + row_start
2112 ..token * matrix.out_features + row_start + local_out]
2113 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2114 }
2115 rank_outputs.push(output);
2116 }
2117 Ok(ColumnParallelResult {
2118 gathered,
2119 rank_outputs,
2120 })
2121 }
2122
2123 pub fn upload_column_parallel(
2124 &self,
2125 matrix: E4m3BlockMatrix<'_>,
2126 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2127 matrix.validate()?;
2128 let tp = self.ranks.len();
2129 validate_column_shape(matrix, tp)?;
2130 let mut ranks = Vec::with_capacity(tp);
2131 for (rank_index, engine) in self.ranks.iter().enumerate() {
2132 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2133 }
2134 Ok(ResidentColumnParallel {
2135 ranks,
2136 out_features: matrix.out_features,
2137 in_features: matrix.in_features,
2138 })
2139 }
2140
2141 pub fn column_parallel_resident(
2142 &self,
2143 matrix: &ResidentColumnParallel,
2144 activations: &[f32],
2145 tokens: usize,
2146 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2147 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2148 validate_activations(activations, tokens, matrix.in_features)?;
2149 let local_out = matrix.out_features / self.ranks.len();
2150 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2151 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2152 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2153 let output = run_resident_rank(engine, shard, activations, tokens)?;
2154 let row_start = rank_index * local_out;
2155 for token in 0..tokens {
2156 gathered[token * matrix.out_features + row_start
2157 ..token * matrix.out_features + row_start + local_out]
2158 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2159 }
2160 rank_outputs.push(output);
2161 }
2162 Ok(ColumnParallelResult {
2163 gathered,
2164 rank_outputs,
2165 })
2166 }
2167
2168 pub fn row_parallel(
2172 &self,
2173 matrix: E4m3BlockMatrix<'_>,
2174 activations: &[f32],
2175 tokens: usize,
2176 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2177 matrix.validate()?;
2178 validate_activations(activations, tokens, matrix.in_features)?;
2179 let tp = self.ranks.len();
2180 if matrix.in_features % tp != 0 {
2181 return Err(format!(
2182 "row-parallel in_features {} is not divisible by TP={tp}",
2183 matrix.in_features
2184 )
2185 .into());
2186 }
2187 let local_in = matrix.in_features / tp;
2188 if local_in % FP8_BLOCK != 0 {
2189 return Err(format!(
2190 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2191 E4M3 scale block"
2192 )
2193 .into());
2194 }
2195
2196 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2197 let mut rank_partials = Vec::with_capacity(tp);
2198 for (rank_index, rank) in self.ranks.iter().enumerate() {
2199 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2200 let local_activations =
2201 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2202 let shard = E4m3BlockMatrix {
2203 codes: &codes,
2204 scales: &scales,
2205 out_features: matrix.out_features,
2206 in_features: local_in,
2207 };
2208 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2209 for (sum, value) in reduced.iter_mut().zip(&partial) {
2210 *sum += *value;
2211 }
2212 rank_partials.push(partial);
2213 }
2214 Ok(RowParallelResult {
2215 reduced,
2216 rank_partials,
2217 })
2218 }
2219
2220 pub fn upload_row_parallel(
2221 &self,
2222 matrix: E4m3BlockMatrix<'_>,
2223 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2224 matrix.validate()?;
2225 let tp = self.ranks.len();
2226 validate_row_shape(matrix, tp)?;
2227 let local_in = matrix.in_features / tp;
2228 let mut ranks = Vec::with_capacity(tp);
2229 for (rank_index, engine) in self.ranks.iter().enumerate() {
2230 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2231 ranks.push(upload_rank(
2232 engine,
2233 E4m3BlockMatrix {
2234 codes: &codes,
2235 scales: &scales,
2236 out_features: matrix.out_features,
2237 in_features: local_in,
2238 },
2239 )?);
2240 }
2241 Ok(ResidentRowParallel {
2242 ranks,
2243 out_features: matrix.out_features,
2244 in_features: matrix.in_features,
2245 })
2246 }
2247
2248 pub fn row_parallel_resident(
2249 &self,
2250 matrix: &ResidentRowParallel,
2251 activations: &[f32],
2252 tokens: usize,
2253 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2254 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2255 validate_activations(activations, tokens, matrix.in_features)?;
2256 let tp = self.ranks.len();
2257 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2258 let mut rank_partials = Vec::with_capacity(tp);
2259 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2260 let local_activations =
2261 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2262 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2263 for (sum, value) in reduced.iter_mut().zip(&partial) {
2264 *sum += *value;
2265 }
2266 rank_partials.push(partial);
2267 }
2268 Ok(RowParallelResult {
2269 reduced,
2270 rank_partials,
2271 })
2272 }
2273
2274 pub fn upload_bf16_column_parallel(
2275 &self,
2276 matrix: Bf16Matrix<'_>,
2277 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2278 self.upload_bf16_column_parallel_inner(matrix, None, false)
2279 }
2280
2281 pub fn upload_step_bf16_column_parallel(
2283 &self,
2284 matrix: Bf16Matrix<'_>,
2285 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2286 self.upload_step_bf16_column_parallel_inner(matrix, false)
2287 }
2288
2289 pub fn upload_step_bf16_column_parallel_f32_mirror(
2294 &self,
2295 matrix: Bf16Matrix<'_>,
2296 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2297 self.upload_step_bf16_column_parallel_inner(matrix, true)
2298 }
2299
2300 fn upload_step_bf16_column_parallel_inner(
2301 &self,
2302 matrix: Bf16Matrix<'_>,
2303 f32_mirror: bool,
2304 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2305 let canonical_chunk_rows =
2306 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2307 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2308 }
2309
2310 fn upload_bf16_column_parallel_inner(
2311 &self,
2312 matrix: Bf16Matrix<'_>,
2313 canonical_chunk_rows: Option<usize>,
2314 f32_mirror: bool,
2315 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2316 matrix.validate()?;
2317 let tp = self.ranks.len();
2318 if matrix.out_features % tp != 0 {
2319 return Err(format!(
2320 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2321 matrix.out_features
2322 )
2323 .into());
2324 }
2325 let mut ranks = Vec::with_capacity(tp);
2326 for (rank, engine) in self.ranks.iter().enumerate() {
2327 ranks.push(upload_bf16_rank(
2328 engine,
2329 bf16_column_shard(matrix, tp, rank)?,
2330 f32_mirror,
2331 )?);
2332 }
2333 Ok(ResidentBf16ColumnParallel {
2334 ranks,
2335 out_features: matrix.out_features,
2336 in_features: matrix.in_features,
2337 canonical_chunk_rows,
2338 })
2339 }
2340
2341 pub fn bf16_column_parallel_resident(
2342 &self,
2343 matrix: &ResidentBf16ColumnParallel,
2344 activations: &[f32],
2345 tokens: usize,
2346 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2347 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2348 validate_activations(activations, tokens, matrix.in_features)?;
2349 let local_out = matrix.out_features / self.ranks.len();
2350 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2351 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2352 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2353 let output = run_resident_bf16_rank(
2354 engine,
2355 shard,
2356 activations,
2357 tokens,
2358 matrix.canonical_chunk_rows,
2359 )?;
2360 for token in 0..tokens {
2361 let src = &output[token * local_out..(token + 1) * local_out];
2362 let dst_start = token * matrix.out_features + rank * local_out;
2363 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2364 }
2365 rank_outputs.push(output);
2366 }
2367 Ok(ColumnParallelResult {
2368 gathered,
2369 rank_outputs,
2370 })
2371 }
2372
2373 pub fn bf16_column_parallel_resident_native(
2380 &self,
2381 matrix: &ResidentBf16ColumnParallel,
2382 activations: &[f32],
2383 tokens: usize,
2384 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2385 let rank_outputs =
2386 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2387 let local_out = matrix.out_features / self.ranks.len();
2388 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2389 }
2390
2391 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2396 self.ranks
2397 .first()
2398 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2399 }
2400
2401 pub fn bf16_column_parallel_resident_native_device(
2414 &self,
2415 matrix: &ResidentBf16ColumnParallel,
2416 root_activation: &CudaSlice<f32>,
2417 tokens: usize,
2418 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2419 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2420 matrix,
2421 root_activation,
2422 tokens,
2423 )?;
2424 let local_out = matrix.out_features / self.ranks.len();
2425 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2426 let root = &self.ranks[0];
2427 let _main = root.gpu.enter_main()?;
2428 root.stream().synchronize()?;
2429 Ok(gathered)
2430 }
2431
2432 pub fn bf16_column_parallel_resident_device_shards_from_root(
2437 &self,
2438 matrix: &ResidentBf16ColumnParallel,
2439 root_activation: &CudaSlice<f32>,
2440 tokens: usize,
2441 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2442 if self.ranks.len() > 1 && !self.native_p2p {
2443 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2444 }
2445 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2446 let values = tokens
2447 .checked_mul(matrix.in_features)
2448 .ok_or("device BF16 column activation size overflow")?;
2449 let root = &self.ranks[0];
2450 if tokens == 0
2451 || root_activation.len() < values
2452 || root_activation.ordinal() != root.ctx().ordinal()
2453 {
2454 return Err("device BF16 column root activation geometry mismatch".into());
2455 }
2456
2457 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2458 let root_input = {
2459 let _main = root.gpu.enter_main()?;
2460 let mut root_input = root.uninit(values)?;
2461 root.stream()
2462 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2463 root_input
2464 };
2465 {
2469 let _main = root.gpu.enter_main()?;
2470 root.stream().synchronize()?;
2471 }
2472 rank_inputs.push(root_input);
2473 for engine in &self.ranks[1..] {
2474 let peer_input = {
2475 let _main = engine.gpu.enter_main()?;
2476 let mut peer_input = engine.uninit(values)?;
2477 engine
2478 .stream()
2479 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2480 peer_input
2481 };
2482 rank_inputs.push(peer_input);
2483 }
2484
2485 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2486 for rank in 0..self.ranks.len() {
2487 rank_outputs.push(run_resident_bf16_rank_device(
2488 &self.ranks[rank],
2489 &matrix.ranks[rank],
2490 &rank_inputs[rank],
2491 tokens,
2492 matrix.canonical_chunk_rows,
2493 self.bulk_p2p,
2494 )?);
2495 }
2496 Ok(rank_outputs)
2497 }
2498
2499 pub fn bf16_column_parallel_resident_device_shards(
2506 &self,
2507 matrix: &ResidentBf16ColumnParallel,
2508 activations: &[f32],
2509 tokens: usize,
2510 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2511 if self.ranks.len() > 1 && !self.native_p2p {
2512 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2513 }
2514 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2515 validate_activations(activations, tokens, matrix.in_features)?;
2516
2517 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2518 let root_input = {
2519 let root = &self.ranks[0];
2520 let _main = root.gpu.enter_main()?;
2521 root.htod(activations)?
2522 };
2523 {
2529 let root = &self.ranks[0];
2530 let _main = root.gpu.enter_main()?;
2531 root.stream().synchronize()?;
2532 }
2533 rank_inputs.push(root_input);
2534 for engine in &self.ranks[1..] {
2535 let peer_input = {
2536 let _main = engine.gpu.enter_main()?;
2537 let mut peer_input = engine.uninit(activations.len())?;
2538 engine
2539 .stream()
2540 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2541 peer_input
2542 };
2543 rank_inputs.push(peer_input);
2544 }
2545
2546 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2547 for rank in 0..self.ranks.len() {
2548 rank_outputs.push(run_resident_bf16_rank_device(
2549 &self.ranks[rank],
2550 &matrix.ranks[rank],
2551 &rank_inputs[rank],
2552 tokens,
2553 matrix.canonical_chunk_rows,
2554 self.bulk_p2p,
2555 )?);
2556 }
2557 Ok(rank_outputs)
2558 }
2559
2560 pub fn allocate_replicated_device_rows(
2564 &self,
2565 tokens: usize,
2566 width: usize,
2567 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2568 if self.ranks.len() > 1 && !self.native_p2p {
2569 return Err("replicated device rows require native P2P ranks".into());
2570 }
2571 let values = tokens
2572 .checked_mul(width)
2573 .ok_or("replicated device row size overflow")?;
2574 let rank_lengths = vec![values; self.ranks.len()];
2575 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2576 let mut ranks = Vec::with_capacity(self.ranks.len());
2577 for engine in &self.ranks {
2578 let _main = engine.gpu.enter_main()?;
2579 ranks.push(engine.uninit(values)?);
2580 }
2581 Ok(ResidentReplicatedDeviceRows {
2582 ranks,
2583 tokens,
2584 width,
2585 })
2586 }
2587
2588 pub fn refresh_replicated_device_rows_from_root(
2590 &self,
2591 rows: &mut ResidentReplicatedDeviceRows,
2592 source: &CudaSlice<f32>,
2593 ) -> Result<(), Box<dyn std::error::Error>> {
2594 if self.ranks.len() > 1 && !self.native_p2p {
2595 return Err("replicated device rows require native P2P ranks".into());
2596 }
2597 validate_replicated_device_rows(&self.ranks, rows)?;
2598 let root = self
2599 .ranks
2600 .first()
2601 .ok_or("replicated rows have no root rank")?;
2602 let values = replicated_device_row_source_values(
2603 rows.tokens,
2604 rows.width,
2605 source.len(),
2606 source.ordinal(),
2607 root.ctx().ordinal(),
2608 )?;
2609 let (root_rows, peer_rows) = rows
2610 .ranks
2611 .split_first_mut()
2612 .ok_or("replicated rows have no root allocation")?;
2613 {
2614 let _main = root.gpu.enter_main()?;
2615 let mut destination = root_rows.slice_mut(0..values);
2616 root.stream()
2617 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2618 root.stream().synchronize()?;
2619 }
2620 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2621 let _main = engine.gpu.enter_main()?;
2622 let mut destination = peer_rows.slice_mut(0..values);
2623 engine
2624 .stream()
2625 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2626 }
2627 Ok(())
2628 }
2629
2630 pub fn upload_replicated_device_rows(
2632 &self,
2633 rows: &[f32],
2634 tokens: usize,
2635 width: usize,
2636 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2637 if self.ranks.len() > 1 && !self.native_p2p {
2638 return Err("replicated device rows require native P2P ranks".into());
2639 }
2640 validate_activations(rows, tokens, width)?;
2641 let root = self
2642 .ranks
2643 .first()
2644 .ok_or("replicated rows have no root rank")?;
2645 let root_rows = {
2646 let _main = root.gpu.enter_main()?;
2647 root.htod(rows)?
2648 };
2649 {
2650 let _main = root.gpu.enter_main()?;
2651 root.stream().synchronize()?;
2652 }
2653 let mut ranks = Vec::with_capacity(self.ranks.len());
2654 ranks.push(root_rows);
2655 for engine in self.ranks.iter().skip(1) {
2656 let _main = engine.gpu.enter_main()?;
2657 let mut peer_rows = engine.uninit(rows.len())?;
2658 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2659 ranks.push(peer_rows);
2660 }
2661 Ok(ResidentReplicatedDeviceRows {
2662 ranks,
2663 tokens,
2664 width,
2665 })
2666 }
2667
2668 pub fn bf16_column_parallel_resident_replicated_device_shards(
2670 &self,
2671 matrix: &ResidentBf16ColumnParallel,
2672 activations: &ResidentReplicatedDeviceRows,
2673 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2674 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2675 validate_replicated_device_rows(&self.ranks, activations)?;
2676 if activations.width != matrix.in_features {
2677 return Err(format!(
2678 "replicated BF16 column input width {} != matrix width {}",
2679 activations.width, matrix.in_features
2680 )
2681 .into());
2682 }
2683 let mut outputs = Vec::with_capacity(self.ranks.len());
2684 for rank in 0..self.ranks.len() {
2685 outputs.push(run_resident_bf16_rank_device(
2686 &self.ranks[rank],
2687 &matrix.ranks[rank],
2688 &activations.ranks[rank],
2689 activations.tokens,
2690 matrix.canonical_chunk_rows,
2691 self.bulk_p2p,
2692 )?);
2693 }
2694 Ok(outputs)
2695 }
2696
2697 #[allow(clippy::too_many_arguments)]
2699 pub fn upload_sigmoid_topk_router(
2700 &self,
2701 weight: Bf16Matrix<'_>,
2702 correction_bias: &[f32],
2703 active: Option<&[bool]>,
2704 experts_per_token: usize,
2705 scaling_factor: f32,
2706 route_norm: bool,
2707 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2708 weight.validate()?;
2709 if correction_bias.len() != weight.out_features
2710 || experts_per_token == 0
2711 || experts_per_token > weight.out_features
2712 || !correction_bias.iter().all(|value| value.is_finite())
2713 || !scaling_factor.is_finite()
2714 || scaling_factor <= 0.0
2715 {
2716 return Err(format!(
2717 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2718 weight.out_features,
2719 weight.in_features,
2720 correction_bias.len(),
2721 experts_per_token,
2722 )
2723 .into());
2724 }
2725 let active_row = active
2726 .map(|mask| {
2727 if mask.len() != weight.out_features {
2728 return Err(format!(
2729 "sigmoid router active mask {} != experts {}",
2730 mask.len(),
2731 weight.out_features
2732 ));
2733 }
2734 Ok(mask
2735 .iter()
2736 .map(|&enabled| u8::from(enabled))
2737 .collect::<Vec<_>>())
2738 })
2739 .transpose()?
2740 .unwrap_or_else(|| vec![1; weight.out_features]);
2741 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2742 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2743
2744 let root = self
2745 .ranks
2746 .first()
2747 .ok_or("sigmoid router runtime has no root rank")?;
2748 let _main = root.gpu.enter_main()?;
2749 let bf16 = root.htod_bytes(weight.bytes)?;
2750 let weight_f32 = root.bf16_to_f32(
2751 &bf16.slice(0..bf16.len()),
2752 weight.out_features * weight.in_features,
2753 )?;
2754 Ok(ResidentSigmoidTopKRouter {
2755 weight: weight_f32,
2756 correction_bias: root.htod(correction_bias)?,
2757 active: root.htod_bytes(&active_row)?,
2758 root_device: root.ctx().ordinal(),
2759 input_width: weight.in_features,
2760 expert_count: weight.out_features,
2761 experts_per_token,
2762 active_count,
2763 scaling_factor,
2764 route_norm,
2765 })
2766 }
2767
2768 pub fn sigmoid_topk_replicated_device_rows_host(
2773 &self,
2774 router: &ResidentSigmoidTopKRouter,
2775 input: &ResidentReplicatedDeviceRows,
2776 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2777 validate_replicated_device_rows(&self.ranks, input)?;
2778 if input.width != router.input_width {
2779 return Err(format!(
2780 "sigmoid router input width {} != resident width {}",
2781 input.width, router.input_width
2782 )
2783 .into());
2784 }
2785 let root = self
2786 .ranks
2787 .first()
2788 .ok_or("sigmoid router runtime has no root rank")?;
2789 let _main = root.gpu.enter_main()?;
2790 if root.ctx().ordinal() != router.root_device
2791 || router.weight.ordinal() != router.root_device
2792 || router.correction_bias.ordinal() != router.root_device
2793 || router.active.ordinal() != router.root_device
2794 {
2795 return Err("sigmoid router root residency changed".into());
2796 }
2797 let logits = root.router_gemv(
2798 &router.weight,
2799 &input.ranks[0],
2800 router.input_width,
2801 router.expert_count,
2802 input.tokens,
2803 )?;
2804 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2805 &logits,
2806 input.tokens,
2807 router.expert_count,
2808 router.experts_per_token,
2809 router.active_count,
2810 &router.correction_bias,
2811 &router.active,
2812 router.scaling_factor,
2813 router.route_norm,
2814 )?;
2815 Ok(SigmoidTopKHostOutput {
2816 logits: root.dtoh(&logits)?,
2817 selected,
2818 weights,
2819 })
2820 }
2821
2822 pub fn upload_replicated_bf16_swiglu(
2824 &self,
2825 gate: Bf16Matrix<'_>,
2826 up: Bf16Matrix<'_>,
2827 down: Bf16Matrix<'_>,
2828 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2829 gate.validate()?;
2830 up.validate()?;
2831 down.validate()?;
2832 if gate.in_features != up.in_features
2833 || gate.out_features != up.out_features
2834 || down.in_features != gate.out_features
2835 || down.out_features != gate.in_features
2836 {
2837 return Err(format!(
2838 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2839 gate.out_features,
2840 gate.in_features,
2841 up.out_features,
2842 up.in_features,
2843 down.out_features,
2844 down.in_features,
2845 )
2846 .into());
2847 }
2848 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2849 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2850 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2851 for engine in &self.ranks {
2852 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2853 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2854 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2855 }
2856 Ok(ResidentReplicatedBf16SwiGlu {
2857 gate: gate_ranks,
2858 up: up_ranks,
2859 down: down_ranks,
2860 input_width: gate.in_features,
2861 intermediate_width: gate.out_features,
2862 })
2863 }
2864
2865 pub fn replicated_bf16_swiglu_resident_device(
2867 &self,
2868 mlp: &ResidentReplicatedBf16SwiGlu,
2869 input: &ResidentReplicatedDeviceRows,
2870 activation_limit: Option<f32>,
2871 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2872 validate_step_expert_activation_limit(activation_limit)?;
2873 validate_replicated_device_rows(&self.ranks, input)?;
2874 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2875 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2876 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2877 if input.width != mlp.input_width
2878 || mlp.gate.len() != self.ranks.len()
2879 || mlp.up.len() != self.ranks.len()
2880 || mlp.down.len() != self.ranks.len()
2881 {
2882 return Err("replicated BF16 SwiGLU residency or input width changed".into());
2883 }
2884
2885 let mut outputs = Vec::with_capacity(self.ranks.len());
2886 for rank in 0..self.ranks.len() {
2887 let engine = &self.ranks[rank];
2888 let gate = run_resident_bf16_rank_device(
2889 engine,
2890 &mlp.gate[rank],
2891 &input.ranks[rank],
2892 input.tokens,
2893 None,
2894 self.bulk_p2p,
2895 )?;
2896 let up = run_resident_bf16_rank_device(
2897 engine,
2898 &mlp.up[rank],
2899 &input.ranks[rank],
2900 input.tokens,
2901 None,
2902 self.bulk_p2p,
2903 )?;
2904 let _main = engine.gpu.enter_main()?;
2905 let values = input
2906 .tokens
2907 .checked_mul(mlp.intermediate_width)
2908 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
2909 let mut activation = engine.uninit(values)?;
2910 if let Some(limit) = activation_limit {
2911 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
2912 } else {
2913 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
2914 }
2915 outputs.push(run_resident_bf16_rank_device(
2916 engine,
2917 &mlp.down[rank],
2918 &activation,
2919 input.tokens,
2920 None,
2921 self.bulk_p2p,
2922 )?);
2923 }
2924 Ok(ResidentReplicatedDeviceRows {
2925 ranks: outputs,
2926 tokens: input.tokens,
2927 width: mlp.input_width,
2928 })
2929 }
2930
2931 pub fn rms_norm_replicated_device_rows(
2933 &self,
2934 input: &ResidentReplicatedDeviceRows,
2935 weight: &[f32],
2936 eps: f32,
2937 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2938 validate_replicated_device_rows(&self.ranks, input)?;
2939 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
2940 return Err(format!(
2941 "replicated RMS norm weight/eps {}/{} != width {}",
2942 weight.len(),
2943 eps,
2944 input.width
2945 )
2946 .into());
2947 }
2948 let mut ranks = Vec::with_capacity(self.ranks.len());
2949 for (rank, engine) in self.ranks.iter().enumerate() {
2950 let _main = engine.gpu.enter_main()?;
2951 let weight = engine.htod(weight)?;
2952 let mut output = engine.uninit(input.tokens * input.width)?;
2953 engine.rms_norm(
2954 &input.ranks[rank],
2955 &weight,
2956 &mut output,
2957 input.width,
2958 input.tokens,
2959 eps,
2960 )?;
2961 ranks.push(output);
2962 }
2963 Ok(ResidentReplicatedDeviceRows {
2964 ranks,
2965 tokens: input.tokens,
2966 width: input.width,
2967 })
2968 }
2969
2970 pub fn add_rms_norm_replicated_device_rows(
2972 &self,
2973 input: &ResidentReplicatedDeviceRows,
2974 update: &ResidentReplicatedDeviceRows,
2975 weight: &[f32],
2976 eps: f32,
2977 ) -> Result<
2978 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
2979 Box<dyn std::error::Error>,
2980 > {
2981 validate_replicated_device_rows(&self.ranks, input)?;
2982 validate_replicated_device_rows(&self.ranks, update)?;
2983 if input.tokens != update.tokens
2984 || input.width != update.width
2985 || weight.len() != input.width
2986 || !eps.is_finite()
2987 || eps <= 0.0
2988 {
2989 return Err(format!(
2990 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
2991 input.tokens,
2992 input.width,
2993 update.tokens,
2994 update.width,
2995 weight.len(),
2996 )
2997 .into());
2998 }
2999 let values = input.tokens * input.width;
3000 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3001 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3002 for (rank, engine) in self.ranks.iter().enumerate() {
3003 let _main = engine.gpu.enter_main()?;
3004 let weight = engine.htod(weight)?;
3005 let mut residual = engine.uninit(values)?;
3006 let mut normalized = engine.uninit(values)?;
3007 engine.add_rms_norm(
3008 &input.ranks[rank],
3009 &update.ranks[rank],
3010 &weight,
3011 &mut residual,
3012 &mut normalized,
3013 input.width,
3014 input.tokens,
3015 eps,
3016 )?;
3017 residual_ranks.push(residual);
3018 normalized_ranks.push(normalized);
3019 }
3020 Ok((
3021 ResidentReplicatedDeviceRows {
3022 ranks: residual_ranks,
3023 tokens: input.tokens,
3024 width: input.width,
3025 },
3026 ResidentReplicatedDeviceRows {
3027 ranks: normalized_ranks,
3028 tokens: input.tokens,
3029 width: input.width,
3030 },
3031 ))
3032 }
3033
3034 pub fn collect_replicated_device_rows(
3035 &self,
3036 rows: &ResidentReplicatedDeviceRows,
3037 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3038 validate_replicated_device_rows(&self.ranks, rows)?;
3039 let mut outputs = Vec::with_capacity(self.ranks.len());
3040 for (rank, engine) in self.ranks.iter().enumerate() {
3041 let _main = engine.gpu.enter_main()?;
3042 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3043 }
3044 Ok(outputs)
3045 }
3046
3047 pub fn upload_bf16_row_parallel(
3048 &self,
3049 matrix: Bf16Matrix<'_>,
3050 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3051 matrix.validate()?;
3052 let tp = self.ranks.len();
3053 if matrix.in_features % tp != 0 {
3054 return Err(format!(
3055 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3056 matrix.in_features
3057 )
3058 .into());
3059 }
3060 let mut ranks = Vec::with_capacity(tp);
3061 for (rank, engine) in self.ranks.iter().enumerate() {
3062 let shard = bf16_row_shard(matrix, tp, rank)?;
3063 ranks.push(upload_bf16_rank(
3064 engine,
3065 Bf16Matrix {
3066 bytes: &shard,
3067 out_features: matrix.out_features,
3068 in_features: matrix.in_features / tp,
3069 },
3070 false,
3071 )?);
3072 }
3073 Ok(ResidentBf16RowParallel {
3074 ranks,
3075 out_features: matrix.out_features,
3076 in_features: matrix.in_features,
3077 })
3078 }
3079
3080 pub fn bf16_row_parallel_resident(
3081 &self,
3082 matrix: &ResidentBf16RowParallel,
3083 activations: &[f32],
3084 tokens: usize,
3085 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3086 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3087 validate_activations(activations, tokens, matrix.in_features)?;
3088 let tp = self.ranks.len();
3089 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3090 let mut rank_partials = Vec::with_capacity(tp);
3091 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3092 let local_activations =
3093 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3094 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3095 for (sum, value) in reduced.iter_mut().zip(&partial) {
3096 *sum += value;
3097 }
3098 rank_partials.push(partial);
3099 }
3100 Ok(RowParallelResult {
3101 reduced,
3102 rank_partials,
3103 })
3104 }
3105
3106 pub fn upload_step_bf16_row_parallel(
3108 &self,
3109 matrix: Bf16Matrix<'_>,
3110 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3111 self.upload_step_bf16_row_parallel_inner(matrix, false)
3112 }
3113
3114 pub fn upload_step_bf16_row_parallel_f32_mirror(
3115 &self,
3116 matrix: Bf16Matrix<'_>,
3117 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3118 self.upload_step_bf16_row_parallel_inner(matrix, true)
3119 }
3120
3121 fn upload_step_bf16_row_parallel_inner(
3122 &self,
3123 matrix: Bf16Matrix<'_>,
3124 f32_mirror: bool,
3125 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3126 matrix.validate()?;
3127 let tp = self.ranks.len();
3128 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3129 let local_in = matrix.in_features / tp;
3130 let blocks_per_rank = local_in / canonical_chunk_cols;
3131 let mut ranks = Vec::with_capacity(tp);
3132 for (rank, engine) in self.ranks.iter().enumerate() {
3133 let mut blocks = Vec::with_capacity(blocks_per_rank);
3134 for block in 0..blocks_per_rank {
3135 let global_block = rank * blocks_per_rank + block;
3136 let col_start = global_block * canonical_chunk_cols;
3137 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3138 blocks.push(upload_bf16_rank(
3139 engine,
3140 Bf16Matrix {
3141 bytes: &bytes,
3142 out_features: matrix.out_features,
3143 in_features: canonical_chunk_cols,
3144 },
3145 f32_mirror,
3146 )?);
3147 }
3148 ranks.push(blocks);
3149 }
3150 Ok(ResidentStepBf16RowParallel {
3151 ranks,
3152 out_features: matrix.out_features,
3153 in_features: matrix.in_features,
3154 canonical_chunk_cols,
3155 })
3156 }
3157
3158 pub fn step_bf16_row_parallel_resident(
3163 &self,
3164 matrix: &ResidentStepBf16RowParallel,
3165 activations: &[f32],
3166 tokens: usize,
3167 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3168 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3169 validate_activations(activations, tokens, matrix.in_features)?;
3170 let root = &self.ranks[0];
3171 let output_len = tokens
3172 .checked_mul(matrix.out_features)
3173 .ok_or("Step BF16 row output size overflow")?;
3174 let mut reduced = {
3175 let _main = root.gpu.enter_main()?;
3176 root.htod(&vec![0.0f32; output_len])?
3177 };
3178 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3179 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3180 for (block, resident) in blocks.iter().enumerate() {
3181 let global_block = rank * blocks_per_rank + block;
3182 let input = activation_shard(
3183 activations,
3184 tokens,
3185 matrix.in_features,
3186 PRODUCT_MAX_CARDS,
3187 global_block,
3188 );
3189 let partial =
3190 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3191 let next = {
3192 let _main = root.gpu.enter_main()?;
3193 let partial = root.htod(&partial)?;
3194 let mut next = root.uninit(output_len)?;
3195 root.add(&reduced, &partial, &mut next, output_len)?;
3196 next
3197 };
3198 reduced = next;
3199 }
3200 }
3201 let _main = root.gpu.enter_main()?;
3202 root.dtoh(&reduced)
3203 }
3204
3205 pub fn step_bf16_row_parallel_resident_native(
3211 &self,
3212 matrix: &ResidentStepBf16RowParallel,
3213 activations: &[f32],
3214 tokens: usize,
3215 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3216 if self.ranks.len() > 1 && !self.native_p2p {
3217 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3218 }
3219 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3220 validate_activations(activations, tokens, matrix.in_features)?;
3221 let root = &self.ranks[0];
3222 let root_input = {
3223 let _main = root.gpu.enter_main()?;
3224 root.htod(activations)?
3225 };
3226 {
3229 let _main = root.gpu.enter_main()?;
3230 root.stream().synchronize()?;
3231 }
3232 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3233 let _main = root.gpu.enter_main()?;
3234 root.dtoh(&reduced)
3235 }
3236
3237 pub fn step_bf16_row_parallel_resident_native_device(
3245 &self,
3246 matrix: &ResidentStepBf16RowParallel,
3247 root_activation: &CudaSlice<f32>,
3248 tokens: usize,
3249 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3250 if self.ranks.len() > 1 && !self.native_p2p {
3251 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3252 }
3253 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3254 let values = tokens
3255 .checked_mul(matrix.in_features)
3256 .ok_or("device Step BF16 row activation size overflow")?;
3257 let root = &self.ranks[0];
3258 if tokens == 0
3259 || root_activation.len() < values
3260 || root_activation.ordinal() != root.ctx().ordinal()
3261 {
3262 return Err("device Step BF16 row root activation geometry mismatch".into());
3263 }
3264 let root_input = {
3265 let _main = root.gpu.enter_main()?;
3266 let mut root_input = root.uninit(values)?;
3267 root.stream()
3268 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3269 root.stream().synchronize()?; root_input
3271 };
3272 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3273 let _main = root.gpu.enter_main()?;
3274 root.stream().synchronize()?;
3275 Ok(reduced)
3276 }
3277
3278 fn step_bf16_row_native_reduce_from_root(
3283 &self,
3284 matrix: &ResidentStepBf16RowParallel,
3285 root_input: &CudaSlice<f32>,
3286 tokens: usize,
3287 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3288 let root = &self.ranks[0];
3289 let output_len = tokens
3290 .checked_mul(matrix.out_features)
3291 .ok_or("native Step BF16 row output size overflow")?;
3292 let mut reduced = {
3293 let _main = root.gpu.enter_main()?;
3294 root.htod(&vec![0.0f32; output_len])?
3295 };
3296 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3297 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3298 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3299 let mut remote_partial_keepalive = Vec::new();
3300 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3301 for (block, resident) in blocks.iter().enumerate() {
3302 let global_block = rank * blocks_per_rank + block;
3303 let col_start = global_block * matrix.canonical_chunk_cols;
3304 let block_len = tokens
3305 .checked_mul(matrix.canonical_chunk_cols)
3306 .ok_or("native Step BF16 row block size overflow")?;
3307 let block_input = if self.bulk_p2p {
3308 let root_packed = {
3309 let _main = root.gpu.enter_main()?;
3310 let mut root_packed = root.uninit(block_len)?;
3311 root.copy_rows_strided(
3312 &root_input,
3313 &mut root_packed,
3314 matrix.canonical_chunk_cols,
3315 tokens,
3316 matrix.in_features,
3317 col_start,
3318 )?;
3319 root_packed
3320 };
3321 if rank == 0 {
3322 root_packed
3323 } else {
3324 {
3327 let _main = root.gpu.enter_main()?;
3328 root.stream().synchronize()?;
3329 }
3330 let engine = &self.ranks[rank];
3331 let _main = engine.gpu.enter_main()?;
3332 let mut block_input = engine.uninit(block_len)?;
3333 engine
3334 .stream()
3335 .memcpy_dtod(&root_packed, &mut block_input)?;
3336 root_packed_keepalive.push(root_packed);
3337 block_input
3338 }
3339 } else {
3340 let engine = &self.ranks[rank];
3341 let _main = engine.gpu.enter_main()?;
3342 let mut block_input = engine.uninit(block_len)?;
3343 for token in 0..tokens {
3344 let source_start = token * matrix.in_features + col_start;
3345 let source = root_input
3346 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3347 let destination_start = token * matrix.canonical_chunk_cols;
3348 let mut destination = block_input.slice_mut(
3349 destination_start..destination_start + matrix.canonical_chunk_cols,
3350 );
3351 engine.stream().memcpy_dtod(&source, &mut destination)?;
3352 }
3353 block_input
3354 };
3355 let partial = run_resident_bf16_rank_device(
3356 &self.ranks[rank],
3357 resident,
3358 &block_input,
3359 tokens,
3360 None,
3361 self.bulk_p2p,
3362 )?;
3363 block_input_keepalive.push(block_input);
3364 let root_partial = if rank == 0 {
3365 partial
3366 } else {
3367 {
3370 let engine = &self.ranks[rank];
3371 let _main = engine.gpu.enter_main()?;
3372 engine.stream().synchronize()?;
3373 }
3374 let _main = root.gpu.enter_main()?;
3375 let mut peer_partial = root.uninit(output_len)?;
3376 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3377 remote_partial_keepalive.push(partial);
3378 peer_partial
3379 };
3380 let next = {
3381 let _main = root.gpu.enter_main()?;
3382 let mut next = root.uninit(output_len)?;
3383 root.add(&reduced, &root_partial, &mut next, output_len)?;
3384 next
3385 };
3386 reduced = next;
3387 }
3388 }
3389 {
3390 let _main = root.gpu.enter_main()?;
3391 root.stream().synchronize()?;
3392 }
3393 drop(remote_partial_keepalive);
3394 drop(root_packed_keepalive);
3395 drop(block_input_keepalive);
3396 Ok(reduced)
3397 }
3398
3399 pub fn step_bf16_row_parallel_resident_root_device(
3402 &self,
3403 matrix: &ResidentStepBf16RowParallel,
3404 rank_activations: &[CudaSlice<f32>],
3405 tokens: usize,
3406 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3407 if self.ranks.len() > 1 && !self.native_p2p {
3408 return Err(
3409 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3410 );
3411 }
3412 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3413 let local_width = matrix.in_features / self.ranks.len();
3414 let shard_len = tokens
3415 .checked_mul(local_width)
3416 .ok_or("device Step BF16 row shard size overflow")?;
3417 if tokens == 0
3418 || rank_activations.len() != self.ranks.len()
3419 || rank_activations
3420 .iter()
3421 .zip(&self.ranks)
3422 .any(|(rows, engine)| {
3423 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3424 })
3425 {
3426 return Err("device Step BF16 row activation shard geometry changed".into());
3427 }
3428
3429 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3430 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3431 let mut partials = Vec::with_capacity(self.ranks.len());
3432 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3433 if blocks.len() != blocks_per_rank {
3434 return Err(format!(
3435 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3436 blocks.len()
3437 )
3438 .into());
3439 }
3440 let engine = &self.ranks[rank];
3441 let _main = engine.gpu.enter_main()?;
3442 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3443 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3444 for (block, resident) in blocks.iter().enumerate() {
3445 let block_len = tokens
3446 .checked_mul(matrix.canonical_chunk_cols)
3447 .ok_or("device Step BF16 row block size overflow")?;
3448 let mut block_input = engine.uninit(block_len)?;
3449 let local_col_start = block * matrix.canonical_chunk_cols;
3450 if self.bulk_p2p {
3451 engine.copy_rows_strided(
3452 &rank_activations[rank],
3453 &mut block_input,
3454 matrix.canonical_chunk_cols,
3455 tokens,
3456 local_width,
3457 local_col_start,
3458 )?;
3459 } else {
3460 for token in 0..tokens {
3461 let source_start = token * local_width + local_col_start;
3462 let source = rank_activations[rank]
3463 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3464 let destination_start = token * matrix.canonical_chunk_cols;
3465 let mut destination = block_input.slice_mut(
3466 destination_start..destination_start + matrix.canonical_chunk_cols,
3467 );
3468 engine.stream().memcpy_dtod(&source, &mut destination)?;
3469 }
3470 }
3471 let partial = run_resident_bf16_rank_device(
3472 engine,
3473 resident,
3474 &block_input,
3475 tokens,
3476 None,
3477 self.bulk_p2p,
3478 )?;
3479 rank_inputs.push(block_input);
3480 rank_partials.push(partial);
3481 }
3482 block_inputs.push(rank_inputs);
3483 partials.push(rank_partials);
3484 }
3485 for engine in self.ranks.iter().skip(1) {
3486 let _main = engine.gpu.enter_main()?;
3487 engine.stream().synchronize()?;
3488 }
3489
3490 let output_len = tokens
3491 .checked_mul(matrix.out_features)
3492 .ok_or("device Step BF16 row output size overflow")?;
3493 let root = &self.ranks[0];
3494 let _main = root.gpu.enter_main()?;
3495 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3496 let mut remote_partials = Vec::new();
3497 for (rank, rank_partials) in partials.into_iter().enumerate() {
3498 for partial in rank_partials {
3499 let root_partial = if rank == 0 {
3500 partial
3501 } else {
3502 let mut peer_partial = root.uninit(output_len)?;
3503 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3504 remote_partials.push(partial);
3505 peer_partial
3506 };
3507 let mut next = root.uninit(output_len)?;
3508 root.add(&reduced, &root_partial, &mut next, output_len)?;
3509 reduced = next;
3510 }
3511 }
3512 root.stream().synchronize()?;
3513 drop(remote_partials);
3514 drop(block_inputs);
3515 Ok(reduced)
3516 }
3517
3518 pub fn step_bf16_row_parallel_resident_replicated_device(
3520 &self,
3521 matrix: &ResidentStepBf16RowParallel,
3522 rank_activations: &[CudaSlice<f32>],
3523 tokens: usize,
3524 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3525 let reduced =
3526 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3527 let output_len = tokens
3528 .checked_mul(matrix.out_features)
3529 .ok_or("device Step BF16 row output size overflow")?;
3530 let mut ranks = Vec::with_capacity(self.ranks.len());
3531 ranks.push(reduced);
3532 for engine in self.ranks.iter().skip(1) {
3533 let _main = engine.gpu.enter_main()?;
3534 let mut peer_output = engine.uninit(output_len)?;
3535 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3536 ranks.push(peer_output);
3537 }
3538 Ok(ResidentReplicatedDeviceRows {
3539 ranks,
3540 tokens,
3541 width: matrix.out_features,
3542 })
3543 }
3544
3545 pub fn upload_expert(
3546 &self,
3547 gate: E4m3BlockMatrix<'_>,
3548 up: E4m3BlockMatrix<'_>,
3549 down: E4m3BlockMatrix<'_>,
3550 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3551 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3552 return Err("TP expert gate/up dimensions differ".into());
3553 }
3554 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3555 return Err(format!(
3556 "TP expert down {}x{} does not invert gate/up {}x{}",
3557 down.out_features, down.in_features, gate.out_features, gate.in_features
3558 )
3559 .into());
3560 }
3561 Ok(ResidentTpExpert {
3562 gate: self.upload_column_parallel(gate)?,
3563 up: self.upload_column_parallel(up)?,
3564 down: self.upload_row_parallel(down)?,
3565 input_width: gate.in_features,
3566 expert_width: gate.out_features,
3567 })
3568 }
3569
3570 pub fn run_expert(
3571 &self,
3572 expert: &ResidentTpExpert,
3573 input: &[f32],
3574 tokens: usize,
3575 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3576 validate_activations(input, tokens, expert.input_width)?;
3577 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3578 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3579 let activated: Vec<f32> = gate
3580 .gathered
3581 .iter()
3582 .zip(&up.gathered)
3583 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3584 .collect();
3585 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3586 Ok(self
3587 .row_parallel_resident(&expert.down, &activated, tokens)?
3588 .reduced)
3589 }
3590
3591 pub fn upload_expert_parallel(
3592 &self,
3593 gate: E4m3ExpertBank<'_>,
3594 up: E4m3ExpertBank<'_>,
3595 down: E4m3ExpertBank<'_>,
3596 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3597 gate.validate()?;
3598 up.validate()?;
3599 down.validate()?;
3600 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3601 return Err("EP gate/up/down expert counts differ".into());
3602 }
3603 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3604 return Err("EP gate/up dimensions differ".into());
3605 }
3606 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3607 return Err(format!(
3608 "EP down {}x{} does not invert gate/up {}x{}",
3609 down.out_features, down.in_features, gate.out_features, gate.in_features
3610 )
3611 .into());
3612 }
3613 if gate.expert_count % self.ranks.len() != 0 {
3614 return Err(format!(
3615 "EP expert count {} is not divisible by {} ranks",
3616 gate.expert_count,
3617 self.ranks.len()
3618 )
3619 .into());
3620 }
3621
3622 let per_rank = gate.expert_count / self.ranks.len();
3623 let mut ranks = Vec::with_capacity(self.ranks.len());
3624 for (rank, engine) in self.ranks.iter().enumerate() {
3625 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3626 ranks.push(ResidentEpRank {
3627 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3628 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3629 down: upload_expert_bank_rank(engine, down, expert_range)?,
3630 });
3631 }
3632 Ok(ResidentExpertParallel {
3633 ranks,
3634 expert_count: gate.expert_count,
3635 input_width: gate.in_features,
3636 expert_width: gate.out_features,
3637 })
3638 }
3639
3640 #[allow(clippy::too_many_arguments)]
3646 pub fn prepare_step_grouped_fp8_gate(
3647 &self,
3648 gate: E4m3ExpertBank<'_>,
3649 up: E4m3ExpertBank<'_>,
3650 down: E4m3ExpertBank<'_>,
3651 input: &[f32],
3652 tokens: usize,
3653 selected: &[usize],
3654 activation_limit: Option<f32>,
3655 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3656 gate.validate()?;
3657 up.validate()?;
3658 down.validate()?;
3659 validate_step_expert_activation_limit(activation_limit)?;
3660 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3661 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3662 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3663 {
3664 return Err(format!(
3665 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3666 got gate/up/down={}/{}/{}",
3667 gate.expert_count, up.expert_count, down.expert_count,
3668 )
3669 .into());
3670 }
3671 if gate.in_features != up.in_features
3672 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3673 || up.out_features != STEP_GROUPED_FP8_WIDTH
3674 || down.in_features != STEP_GROUPED_FP8_WIDTH
3675 || down.out_features != gate.in_features
3676 {
3677 return Err(format!(
3678 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3679 gate.out_features,
3680 gate.in_features,
3681 up.out_features,
3682 up.in_features,
3683 down.out_features,
3684 down.in_features,
3685 )
3686 .into());
3687 }
3688 validate_activations(input, tokens, gate.in_features)?;
3689 let pairs = tokens
3690 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3691 .ok_or("official Step grouped FP8 route count overflow")?;
3692 if selected.len() != pairs {
3693 return Err(format!(
3694 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3695 ({pairs})",
3696 selected.len()
3697 )
3698 .into());
3699 }
3700 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3701 let mut unique = routes.to_vec();
3702 unique.sort_unstable();
3703 unique.dedup();
3704 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3705 return Err(format!(
3706 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3707 {routes:?}"
3708 )
3709 .into());
3710 }
3711 }
3712
3713 let engine = self
3714 .ranks
3715 .first()
3716 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3717 let _main = engine.gpu.enter_main()?;
3718 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3719 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3720 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3721 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3722 let input = engine.htod(input)?;
3723 let route_csr = ExpertCsr::from_token_routes(
3724 STEP_GROUPED_FP8_EXPERTS,
3725 tokens,
3726 STEP_GROUPED_FP8_TOP_K,
3727 selected,
3728 )?
3729 .upload(engine)?;
3730 let pair_rows = (0..pairs).collect::<Vec<_>>();
3731 let down_csr =
3732 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3733 .upload(engine)?;
3734 let gate_workspace =
3735 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3736 let up_workspace =
3737 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3738 let down_workspace =
3739 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3740 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3741 Ok(PreparedStepGroupedFp8Gate {
3742 device: engine.ctx().ordinal(),
3743 gate,
3744 up,
3745 down,
3746 input,
3747 route_csr,
3748 down_csr,
3749 gate_workspace,
3750 up_workspace,
3751 down_workspace,
3752 activation,
3753 activation_limit,
3754 tokens,
3755 pairs,
3756 })
3757 }
3758
3759 pub fn run_step_grouped_fp8_gate(
3761 &self,
3762 plan: &mut PreparedStepGroupedFp8Gate,
3763 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3764 let engine = self
3765 .ranks
3766 .first()
3767 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3768 if engine.ctx().ordinal() != plan.device {
3769 return Err(format!(
3770 "official Step grouped FP8 plan device {} != rank-zero device {}",
3771 plan.device,
3772 engine.ctx().ordinal()
3773 )
3774 .into());
3775 }
3776 let _main = engine.gpu.enter_main()?;
3777
3778 plan.gate_workspace.quantize(engine, &plan.input)?;
3779 plan.gate_workspace.project(
3780 engine,
3781 &plan.gate.codes,
3782 &plan.gate.scales,
3783 &plan.route_csr,
3784 plan.gate.code_stride,
3785 plan.gate.scale_stride,
3786 1.0,
3787 )?;
3788 plan.up_workspace.quantize(engine, &plan.input)?;
3789 plan.up_workspace.project(
3790 engine,
3791 &plan.up.codes,
3792 &plan.up.scales,
3793 &plan.route_csr,
3794 plan.up.code_stride,
3795 plan.up.scale_stride,
3796 1.0,
3797 )?;
3798 if let Some(limit) = plan.activation_limit {
3799 engine.silu_clamped_mul_host_expf(
3800 plan.gate_workspace.output(),
3801 plan.up_workspace.output(),
3802 limit,
3803 &mut plan.activation,
3804 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3805 )?;
3806 } else {
3807 engine.silu_mul_host_expf(
3808 plan.gate_workspace.output(),
3809 plan.up_workspace.output(),
3810 &mut plan.activation,
3811 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3812 )?;
3813 }
3814 plan.down_workspace.quantize(engine, &plan.activation)?;
3815 plan.down_workspace.project(
3816 engine,
3817 &plan.down.codes,
3818 &plan.down.scales,
3819 &plan.down_csr,
3820 plan.down.code_stride,
3821 plan.down.scale_stride,
3822 1.0,
3823 )?;
3824
3825 Ok(StepGroupedFp8ProjectionOutput {
3826 gate: engine.dtoh(plan.gate_workspace.output())?,
3827 up: engine.dtoh(plan.up_workspace.output())?,
3828 down: engine.dtoh(plan.down_workspace.output())?,
3829 })
3830 }
3831
3832 pub fn prepare_step_grouped_expert_parallel_gate(
3833 &self,
3834 experts: &ResidentExpertParallel,
3835 input: &[f32],
3836 tokens: usize,
3837 selected: &[usize],
3838 activation_limit: Option<f32>,
3839 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3840 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3841 experts,
3842 input,
3843 tokens,
3844 selected,
3845 activation_limit,
3846 tokens,
3847 )
3848 }
3849
3850 #[allow(clippy::too_many_arguments)]
3851 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3852 &self,
3853 experts: &ResidentExpertParallel,
3854 input: &[f32],
3855 tokens: usize,
3856 selected: &[usize],
3857 activation_limit: Option<f32>,
3858 max_tokens: usize,
3859 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3860 if !self.native_p2p || !self.ep_device_arithmetic {
3861 return Err(
3862 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3863 );
3864 }
3865 validate_step_expert_activation_limit(activation_limit)?;
3866 validate_ep_residency(&self.ranks, experts)?;
3867 validate_activations(input, tokens, experts.input_width)?;
3868 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3869 return Err(format!(
3870 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3871 )
3872 .into());
3873 }
3874 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3875 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3876 {
3877 return Err(format!(
3878 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3879 STEP_GROUPED_FP8_EXPERTS,
3880 STEP_GROUPED_FP8_WIDTH,
3881 experts.expert_count,
3882 experts.expert_width,
3883 )
3884 .into());
3885 }
3886 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3887 let max_pairs = max_tokens
3888 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3889 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
3890 let input_capacity = max_tokens
3891 .checked_mul(experts.input_width)
3892 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
3893
3894 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3895 for engine in &self.ranks {
3896 let _main = engine.gpu.enter_main()?;
3897 rank_inputs.push(engine.uninit(input_capacity)?);
3898 }
3899
3900 let mut owners = Vec::with_capacity(self.ranks.len());
3901 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
3902 if rank.gate.expert_range != rank.up.expert_range
3903 || rank.gate.expert_range != rank.down.expert_range
3904 {
3905 return Err(format!(
3906 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
3907 owner_rank
3908 )
3909 .into());
3910 }
3911 let local_experts = rank.gate.expert_range.len();
3912 let engine = &self.ranks[owner_rank];
3913 let _main = engine.gpu.enter_main()?;
3914 let route_csr =
3915 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
3916 let down_csr =
3917 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
3918 let gate_workspace = Fp8GroupedWorkspace::new(
3919 engine,
3920 experts.input_width,
3921 experts.expert_width,
3922 max_tokens,
3923 max_pairs,
3924 )?;
3925 let up_workspace = Fp8GroupedWorkspace::new(
3926 engine,
3927 experts.input_width,
3928 experts.expert_width,
3929 max_tokens,
3930 max_pairs,
3931 )?;
3932 let down_workspace = Fp8GroupedWorkspace::new(
3933 engine,
3934 experts.expert_width,
3935 experts.input_width,
3936 max_pairs,
3937 max_pairs,
3938 )?;
3939 let activation = engine.uninit(
3940 max_pairs
3941 .checked_mul(experts.expert_width)
3942 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
3943 )?;
3944 owners.push(PreparedStepGroupedExpertOwner {
3945 rank: owner_rank,
3946 global_pairs: Vec::new(),
3947 route_csr,
3948 down_csr,
3949 gate_workspace,
3950 up_workspace,
3951 down_workspace,
3952 activation,
3953 });
3954 }
3955
3956 let mut plan = PreparedStepGroupedExpertParallelGate {
3957 rank_inputs,
3958 owners,
3959 activation_limit,
3960 tokens: 0,
3961 pairs: 0,
3962 max_tokens,
3963 max_pairs,
3964 input_width: experts.input_width,
3965 expert_width: experts.expert_width,
3966 generation: 0,
3967 executed_generation: None,
3968 ready: false,
3969 };
3970 self.refresh_step_grouped_expert_parallel_gate(
3971 experts, &mut plan, input, tokens, selected,
3972 )?;
3973 Ok(plan)
3974 }
3975
3976 fn prepare_step_grouped_expert_parallel_refresh(
3977 &self,
3978 experts: &ResidentExpertParallel,
3979 plan: &PreparedStepGroupedExpertParallelGate,
3980 tokens: usize,
3981 selected: &[usize],
3982 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
3983 {
3984 validate_ep_residency(&self.ranks, experts)?;
3985 if plan.rank_inputs.len() != self.ranks.len()
3986 || plan.owners.len() != self.ranks.len()
3987 || plan.input_width != experts.input_width
3988 || plan.expert_width != experts.expert_width
3989 || tokens > plan.max_tokens
3990 {
3991 return Err(format!(
3992 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
3993 input={}/{} expert={}/{} tokens={}/{}",
3994 plan.rank_inputs.len(),
3995 self.ranks.len(),
3996 plan.owners.len(),
3997 self.ranks.len(),
3998 plan.input_width,
3999 experts.input_width,
4000 plan.expert_width,
4001 experts.expert_width,
4002 tokens,
4003 plan.max_tokens,
4004 )
4005 .into());
4006 }
4007 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4008 if pairs > plan.max_pairs {
4009 return Err(format!(
4010 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4011 plan.max_pairs
4012 )
4013 .into());
4014 }
4015 let next_generation = plan
4016 .generation
4017 .checked_add(1)
4018 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4019 let owner_routes = partition_expert_owner_routes(
4020 experts.expert_count,
4021 self.ranks.len(),
4022 tokens,
4023 STEP_GROUPED_FP8_TOP_K,
4024 selected,
4025 )?;
4026 let mut schedules = Vec::with_capacity(self.ranks.len());
4027 for routes in owner_routes {
4028 if routes.selected.is_empty() {
4029 schedules.push(None);
4030 continue;
4031 }
4032 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4033 let local_pairs = routes.selected.len();
4034 let route_csr = ExpertCsr::from_pair_rows(
4035 local_experts,
4036 tokens,
4037 &routes.selected,
4038 &routes.token_rows,
4039 )?;
4040 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4041 let down_csr = ExpertCsr::from_pair_rows(
4042 local_experts,
4043 local_pairs,
4044 &routes.selected,
4045 &down_rows,
4046 )?;
4047 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4048 global_pairs: routes.global_pairs,
4049 route_csr,
4050 down_csr,
4051 }));
4052 }
4053 Ok((pairs, next_generation, schedules))
4054 }
4055
4056 fn commit_step_grouped_expert_parallel_refresh(
4057 &self,
4058 plan: &mut PreparedStepGroupedExpertParallelGate,
4059 tokens: usize,
4060 pairs: usize,
4061 next_generation: u64,
4062 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4063 ) -> Result<(), Box<dyn std::error::Error>> {
4064 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4065 let engine = &self.ranks[owner.rank];
4066 let _main = engine.gpu.enter_main()?;
4067 if let Some(schedule) = schedule {
4068 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4069 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4070 owner.global_pairs = schedule.global_pairs;
4071 } else {
4072 owner.route_csr.clear();
4073 owner.down_csr.clear();
4074 owner.global_pairs.clear();
4075 }
4076 }
4077 plan.tokens = tokens;
4078 plan.pairs = pairs;
4079 plan.generation = next_generation;
4080 plan.ready = true;
4081 Ok(())
4082 }
4083
4084 pub fn refresh_step_grouped_expert_parallel_gate(
4085 &self,
4086 experts: &ResidentExpertParallel,
4087 plan: &mut PreparedStepGroupedExpertParallelGate,
4088 input: &[f32],
4089 tokens: usize,
4090 selected: &[usize],
4091 ) -> Result<(), Box<dyn std::error::Error>> {
4092 validate_activations(input, tokens, experts.input_width)?;
4093 let (pairs, next_generation, schedules) =
4094 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4095
4096 plan.ready = false;
4097 plan.executed_generation = None;
4098 {
4099 let root = &self.ranks[0];
4100 let _main = root.gpu.enter_main()?;
4101 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4102 root.stream().memcpy_htod(input, &mut destination)?;
4103 root.stream().synchronize()?;
4104 }
4105 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4106 let root_input = &root_inputs[0];
4107 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4108 let engine = &self.ranks[rank + 1];
4109 let _main = engine.gpu.enter_main()?;
4110 let mut destination = peer_input.slice_mut(0..input.len());
4111 engine
4112 .stream()
4113 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4114 }
4115 self.commit_step_grouped_expert_parallel_refresh(
4116 plan,
4117 tokens,
4118 pairs,
4119 next_generation,
4120 schedules,
4121 )
4122 }
4123
4124 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4129 &self,
4130 experts: &ResidentExpertParallel,
4131 plan: &mut PreparedStepGroupedExpertParallelGate,
4132 input: &CudaSlice<f32>,
4133 tokens: usize,
4134 selected: &[usize],
4135 ) -> Result<(), Box<dyn std::error::Error>> {
4136 let input_values = tokens
4137 .checked_mul(experts.input_width)
4138 .ok_or("Step owner-grouped FP8 input size overflow")?;
4139 let root = self
4140 .ranks
4141 .first()
4142 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4143 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4144 return Err(format!(
4145 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4146 device {}",
4147 input.len(),
4148 input.ordinal(),
4149 input_values,
4150 root.ctx().ordinal(),
4151 )
4152 .into());
4153 }
4154 let (pairs, next_generation, schedules) =
4155 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4156
4157 plan.ready = false;
4158 plan.executed_generation = None;
4159 {
4160 let _main = root.gpu.enter_main()?;
4161 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4162 root.stream()
4163 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4164 root.stream().synchronize()?;
4165 }
4166 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4167 let root_input = &root_inputs[0];
4168 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4169 let engine = &self.ranks[rank + 1];
4170 let _main = engine.gpu.enter_main()?;
4171 let mut destination = peer_input.slice_mut(0..input_values);
4172 engine
4173 .stream()
4174 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4175 }
4176 self.commit_step_grouped_expert_parallel_refresh(
4177 plan,
4178 tokens,
4179 pairs,
4180 next_generation,
4181 schedules,
4182 )
4183 }
4184
4185 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4190 &self,
4191 experts: &ResidentExpertParallel,
4192 plan: &mut PreparedStepGroupedExpertParallelGate,
4193 input: &ResidentReplicatedDeviceRows,
4194 ) -> Result<(), Box<dyn std::error::Error>> {
4195 validate_ep_residency(&self.ranks, experts)?;
4196 validate_replicated_device_rows(&self.ranks, input)?;
4197 if !plan.ready
4198 || input.tokens != plan.tokens
4199 || input.width != plan.input_width
4200 || input.tokens > plan.max_tokens
4201 || plan.rank_inputs.len() != self.ranks.len()
4202 || plan.owners.len() != self.ranks.len()
4203 || plan.input_width != experts.input_width
4204 || plan.expert_width != experts.expert_width
4205 {
4206 return Err("Step owner-grouped replicated input geometry changed".into());
4207 }
4208 let values = input
4209 .tokens
4210 .checked_mul(input.width)
4211 .ok_or("Step owner-grouped replicated input size overflow")?;
4212 let next_generation = plan
4213 .generation
4214 .checked_add(1)
4215 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4216 plan.ready = false;
4217 plan.executed_generation = None;
4218 for (rank, engine) in self.ranks.iter().enumerate() {
4219 let _main = engine.gpu.enter_main()?;
4220 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4221 engine
4222 .stream()
4223 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4224 }
4225 plan.generation = next_generation;
4226 plan.ready = true;
4227 Ok(())
4228 }
4229
4230 pub fn execute_step_grouped_expert_parallel_gate(
4231 &self,
4232 experts: &ResidentExpertParallel,
4233 plan: &mut PreparedStepGroupedExpertParallelGate,
4234 ) -> Result<(), Box<dyn std::error::Error>> {
4235 validate_ep_residency(&self.ranks, experts)?;
4236 if !plan.ready
4237 || plan.rank_inputs.len() != self.ranks.len()
4238 || plan.owners.len() != self.ranks.len()
4239 || plan.input_width != experts.input_width
4240 || plan.expert_width != experts.expert_width
4241 {
4242 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4243 }
4244 plan.executed_generation = None;
4245
4246 for owner in &mut plan.owners {
4247 if owner.global_pairs.is_empty() {
4248 continue;
4249 }
4250 let engine = &self.ranks[owner.rank];
4251 let bank = &experts.ranks[owner.rank];
4252 let _main = engine.gpu.enter_main()?;
4253 let local_pairs = owner.global_pairs.len();
4254 owner.gate_workspace.quantize_for_shape(
4255 engine,
4256 &plan.rank_inputs[owner.rank],
4257 plan.tokens,
4258 local_pairs,
4259 )?;
4260 owner.gate_workspace.project(
4261 engine,
4262 &bank.gate.codes,
4263 &bank.gate.scales,
4264 &owner.route_csr,
4265 bank.gate.code_stride,
4266 bank.gate.scale_stride,
4267 1.0,
4268 )?;
4269 owner.up_workspace.quantize_for_shape(
4270 engine,
4271 &plan.rank_inputs[owner.rank],
4272 plan.tokens,
4273 local_pairs,
4274 )?;
4275 owner.up_workspace.project(
4276 engine,
4277 &bank.up.codes,
4278 &bank.up.scales,
4279 &owner.route_csr,
4280 bank.up.code_stride,
4281 bank.up.scale_stride,
4282 1.0,
4283 )?;
4284 }
4285 for owner in &mut plan.owners {
4286 if owner.global_pairs.is_empty() {
4287 continue;
4288 }
4289 let engine = &self.ranks[owner.rank];
4290 let _main = engine.gpu.enter_main()?;
4291 let values = owner.global_pairs.len() * plan.expert_width;
4292 if let Some(limit) = plan.activation_limit {
4293 engine.silu_clamped_mul_host_expf(
4294 owner.gate_workspace.output(),
4295 owner.up_workspace.output(),
4296 limit,
4297 &mut owner.activation,
4298 values,
4299 )?;
4300 } else {
4301 engine.silu_mul_host_expf(
4302 owner.gate_workspace.output(),
4303 owner.up_workspace.output(),
4304 &mut owner.activation,
4305 values,
4306 )?;
4307 }
4308 }
4309 for owner in &mut plan.owners {
4310 if owner.global_pairs.is_empty() {
4311 continue;
4312 }
4313 let engine = &self.ranks[owner.rank];
4314 let bank = &experts.ranks[owner.rank];
4315 let _main = engine.gpu.enter_main()?;
4316 let local_pairs = owner.global_pairs.len();
4317 owner.down_workspace.quantize_for_shape(
4318 engine,
4319 &owner.activation,
4320 local_pairs,
4321 local_pairs,
4322 )?;
4323 owner.down_workspace.project(
4324 engine,
4325 &bank.down.codes,
4326 &bank.down.scales,
4327 &owner.down_csr,
4328 bank.down.code_stride,
4329 bank.down.scale_stride,
4330 1.0,
4331 )?;
4332 }
4333 plan.executed_generation = Some(plan.generation);
4334 Ok(())
4335 }
4336
4337 pub fn collect_step_grouped_expert_parallel_gate(
4338 &self,
4339 plan: &PreparedStepGroupedExpertParallelGate,
4340 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4341 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4342 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4343 }
4344 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4345 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4346 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4347 for owner in &plan.owners {
4348 if owner.global_pairs.is_empty() {
4349 continue;
4350 }
4351 let engine = &self.ranks[owner.rank];
4352 let _main = engine.gpu.enter_main()?;
4353 let owner_gate = engine.dtoh_view(
4354 &owner
4355 .gate_workspace
4356 .output()
4357 .slice(0..owner.gate_workspace.output_len()),
4358 )?;
4359 let owner_up = engine.dtoh_view(
4360 &owner
4361 .up_workspace
4362 .output()
4363 .slice(0..owner.up_workspace.output_len()),
4364 )?;
4365 let owner_down = engine.dtoh_view(
4366 &owner
4367 .down_workspace
4368 .output()
4369 .slice(0..owner.down_workspace.output_len()),
4370 )?;
4371 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4372 let local_expert = local_pair * plan.expert_width;
4373 let global_expert = global_pair * plan.expert_width;
4374 gate[global_expert..global_expert + plan.expert_width]
4375 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4376 up[global_expert..global_expert + plan.expert_width]
4377 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4378
4379 let local_hidden = local_pair * plan.input_width;
4380 let global_hidden = global_pair * plan.input_width;
4381 down[global_hidden..global_hidden + plan.input_width]
4382 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4383 }
4384 }
4385 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4386 }
4387
4388 pub fn run_step_grouped_expert_parallel_gate(
4389 &self,
4390 experts: &ResidentExpertParallel,
4391 plan: &mut PreparedStepGroupedExpertParallelGate,
4392 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4393 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4394 self.collect_step_grouped_expert_parallel_gate(plan)
4395 }
4396
4397 pub fn prepare_step_grouped_expert_parallel_combine(
4398 &self,
4399 plan: &PreparedStepGroupedExpertParallelGate,
4400 route_weights: &[f32],
4401 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4402 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4403 return Err(
4404 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4405 );
4406 }
4407 let owner_pairs = plan
4408 .owners
4409 .iter()
4410 .map(|owner| owner.global_pairs.as_slice())
4411 .collect::<Vec<_>>();
4412 let shape = validate_weighted_route_combine(
4413 plan.input_width,
4414 STEP_GROUPED_FP8_TOP_K,
4415 plan.max_tokens,
4416 plan.tokens,
4417 &owner_pairs,
4418 route_weights,
4419 )?;
4420 if shape.max_pairs != plan.max_pairs {
4421 return Err(format!(
4422 "Step owner-grouped combine capacity {} != projection capacity {}",
4423 shape.max_pairs, plan.max_pairs
4424 )
4425 .into());
4426 }
4427 let root = self
4428 .ranks
4429 .first()
4430 .ok_or("Step owner-grouped combine has no root rank")?;
4431 let slot_values = shape
4432 .max_pairs
4433 .checked_mul(plan.input_width)
4434 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4435 let output_values = plan
4436 .max_tokens
4437 .checked_mul(plan.input_width)
4438 .ok_or("Step owner-grouped combine output capacity overflow")?;
4439 let (root_device, owners, peer_staging, slots, weights, output) = {
4440 let _main = root.gpu.enter_main()?;
4441 let mut owners = Vec::with_capacity(plan.owners.len());
4442 for _ in &plan.owners {
4443 owners.push(PreparedPeerWeightedRouteOwner {
4444 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4445 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4446 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4447 active_pairs: 0,
4448 });
4449 }
4450 (
4451 root.ctx().ordinal(),
4452 owners,
4453 root.uninit(slot_values)?,
4454 root.uninit(slot_values)?,
4455 root.uninit(shape.max_pairs)?,
4456 root.uninit(output_values)?,
4457 )
4458 };
4459 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4460 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4461 for engine in self.ranks.iter().skip(1) {
4462 let _main = engine.gpu.enter_main()?;
4463 peer_devices.push(engine.ctx().ordinal());
4464 peer_outputs.push(engine.uninit(output_values)?);
4465 }
4466 let mut combine = PreparedPeerWeightedRouteCombine {
4467 root_device,
4468 owners,
4469 peer_staging,
4470 slots,
4471 weights,
4472 output,
4473 peer_devices,
4474 peer_outputs,
4475 width: plan.input_width,
4476 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4477 max_tokens: plan.max_tokens,
4478 max_pairs: shape.max_pairs,
4479 tokens: 0,
4480 pairs: 0,
4481 projection_generation: 0,
4482 output_generation: None,
4483 broadcast_generation: None,
4484 ready: false,
4485 };
4486 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4487 Ok(combine)
4488 }
4489
4490 pub fn refresh_step_grouped_expert_parallel_combine(
4491 &self,
4492 plan: &PreparedStepGroupedExpertParallelGate,
4493 combine: &mut PreparedPeerWeightedRouteCombine,
4494 route_weights: &[f32],
4495 ) -> Result<(), Box<dyn std::error::Error>> {
4496 let output_capacity = combine
4497 .max_tokens
4498 .checked_mul(combine.width)
4499 .ok_or("Step owner-grouped combine output capacity overflow")?;
4500 if !plan.ready
4501 || combine.owners.len() != plan.owners.len()
4502 || combine.peer_devices.len() + 1 != self.ranks.len()
4503 || combine.peer_outputs.len() + 1 != self.ranks.len()
4504 || combine.width != plan.input_width
4505 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4506 || combine.max_tokens != plan.max_tokens
4507 || combine.max_pairs != plan.max_pairs
4508 || combine.output.len() < output_capacity
4509 || combine
4510 .peer_outputs
4511 .iter()
4512 .any(|output| output.len() < output_capacity)
4513 {
4514 return Err("Step owner-grouped combine/projection geometry changed".into());
4515 }
4516 if self
4517 .ranks
4518 .iter()
4519 .skip(1)
4520 .zip(&combine.peer_devices)
4521 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4522 {
4523 return Err("Step owner-grouped combine peer devices changed".into());
4524 }
4525 let owner_pairs = plan
4526 .owners
4527 .iter()
4528 .map(|owner| owner.global_pairs.as_slice())
4529 .collect::<Vec<_>>();
4530 let shape = validate_weighted_route_combine(
4531 combine.width,
4532 combine.experts_per_token,
4533 combine.max_tokens,
4534 plan.tokens,
4535 &owner_pairs,
4536 route_weights,
4537 )?;
4538 if shape.max_pairs != combine.max_pairs {
4539 return Err("Step owner-grouped combine capacity changed during refresh".into());
4540 }
4541 let metadata = owner_pairs
4542 .iter()
4543 .map(|pairs| {
4544 let token_rows = pairs
4545 .iter()
4546 .map(|&pair| (pair / combine.experts_per_token) as i32)
4547 .collect::<Vec<_>>();
4548 let slots = pairs
4549 .iter()
4550 .map(|&pair| (pair % combine.experts_per_token) as i32)
4551 .collect::<Vec<_>>();
4552 let weights = pairs
4553 .iter()
4554 .map(|&pair| route_weights[pair])
4555 .collect::<Vec<_>>();
4556 (token_rows, slots, weights)
4557 })
4558 .collect::<Vec<_>>();
4559
4560 combine.ready = false;
4561 combine.output_generation = None;
4562 combine.broadcast_generation = None;
4563 let root = self
4564 .ranks
4565 .first()
4566 .ok_or("Step owner-grouped combine has no root rank")?;
4567 let _main = root.gpu.enter_main()?;
4568 if root.ctx().ordinal() != combine.root_device {
4569 return Err(format!(
4570 "Step owner-grouped combine root device changed {} != {}",
4571 root.ctx().ordinal(),
4572 combine.root_device
4573 )
4574 .into());
4575 }
4576 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4577 if token_rows.is_empty() {
4578 owner.active_pairs = 0;
4579 continue;
4580 }
4581 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4582 root.htod_i32_into(&mut owner.slots, &slots)?;
4583 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4584 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4585 owner.active_pairs = token_rows.len();
4586 }
4587 combine.tokens = plan.tokens;
4588 combine.pairs = shape.pairs;
4589 combine.projection_generation = plan.generation;
4590 combine.ready = true;
4591 Ok(())
4592 }
4593
4594 pub fn execute_step_grouped_expert_parallel_combine(
4595 &self,
4596 plan: &PreparedStepGroupedExpertParallelGate,
4597 combine: &mut PreparedPeerWeightedRouteCombine,
4598 ) -> Result<(), Box<dyn std::error::Error>> {
4599 if !plan.ready
4600 || plan.executed_generation != Some(plan.generation)
4601 || !combine.ready
4602 || combine.tokens != plan.tokens
4603 || combine.pairs != plan.pairs
4604 || combine.width != plan.input_width
4605 || combine.owners.len() != plan.owners.len()
4606 || combine.projection_generation != plan.generation
4607 {
4608 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4609 }
4610 combine.output_generation = None;
4611 combine.broadcast_generation = None;
4612 for owner in &plan.owners {
4613 if owner.rank == 0 || owner.global_pairs.is_empty() {
4614 continue;
4615 }
4616 let engine = &self.ranks[owner.rank];
4617 let _main = engine.gpu.enter_main()?;
4618 engine.stream().synchronize()?;
4619 }
4620 let root = self
4621 .ranks
4622 .first()
4623 .ok_or("Step owner-grouped combine has no root rank")?;
4624 let _main = root.gpu.enter_main()?;
4625 if root.ctx().ordinal() != combine.root_device {
4626 return Err("Step owner-grouped combine is not resident on the root device".into());
4627 }
4628 for (index, owner) in plan.owners.iter().enumerate() {
4629 let metadata = &combine.owners[index];
4630 if owner.global_pairs.len() != metadata.active_pairs {
4631 return Err(format!(
4632 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4633 owner.global_pairs.len(),
4634 metadata.active_pairs
4635 )
4636 .into());
4637 }
4638 if metadata.active_pairs == 0 {
4639 continue;
4640 }
4641 let values = metadata
4642 .active_pairs
4643 .checked_mul(combine.width)
4644 .ok_or("Step owner-grouped combine peer value count overflow")?;
4645 if owner.rank == 0 {
4646 root.scatter_slot(
4647 owner.down_workspace.output(),
4648 &metadata.token_rows,
4649 &metadata.slots,
4650 &metadata.weights,
4651 &mut combine.slots,
4652 &mut combine.weights,
4653 combine.width,
4654 combine.experts_per_token,
4655 metadata.active_pairs,
4656 )?;
4657 } else {
4658 let source = owner.down_workspace.output().slice(0..values);
4659 let mut destination = combine.peer_staging.slice_mut(0..values);
4660 root.stream().memcpy_dtod(&source, &mut destination)?;
4661 root.scatter_slot(
4662 &combine.peer_staging,
4663 &metadata.token_rows,
4664 &metadata.slots,
4665 &metadata.weights,
4666 &mut combine.slots,
4667 &mut combine.weights,
4668 combine.width,
4669 combine.experts_per_token,
4670 metadata.active_pairs,
4671 )?;
4672 }
4673 }
4674 root.reduce_slots_host(
4675 &combine.slots,
4676 &combine.weights,
4677 &mut combine.output,
4678 combine.width,
4679 combine.experts_per_token,
4680 combine.tokens,
4681 )?;
4682 combine.output_generation = Some(plan.generation);
4683 Ok(())
4684 }
4685
4686 pub fn collect_step_grouped_expert_parallel_combine(
4687 &self,
4688 plan: &PreparedStepGroupedExpertParallelGate,
4689 combine: &PreparedPeerWeightedRouteCombine,
4690 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4691 if !plan.ready
4692 || combine.output_generation != Some(plan.generation)
4693 || combine.projection_generation != plan.generation
4694 {
4695 return Err("Step owner-grouped combine output is stale or has not executed".into());
4696 }
4697 let root = self
4698 .ranks
4699 .first()
4700 .ok_or("Step owner-grouped combine has no root rank")?;
4701 let _main = root.gpu.enter_main()?;
4702 if root.ctx().ordinal() != combine.root_device {
4703 return Err("Step owner-grouped combine is not resident on the root device".into());
4704 }
4705 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4706 }
4707
4708 pub fn copy_step_grouped_expert_parallel_combine_root(
4713 &self,
4714 plan: &PreparedStepGroupedExpertParallelGate,
4715 combine: &PreparedPeerWeightedRouteCombine,
4716 destination: &Engine,
4717 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4718 if !plan.ready
4719 || combine.output_generation != Some(plan.generation)
4720 || combine.projection_generation != plan.generation
4721 {
4722 return Err("Step owner-grouped combine output is stale or has not executed".into());
4723 }
4724 let root = self
4725 .ranks
4726 .first()
4727 .ok_or("Step owner-grouped combine has no root rank")?;
4728 if root.ctx().ordinal() != combine.root_device
4729 || destination.ctx().ordinal() != combine.root_device
4730 {
4731 return Err(format!(
4732 "Step owner-grouped combine root/destination devices {}/{} != {}",
4733 root.ctx().ordinal(),
4734 destination.ctx().ordinal(),
4735 combine.root_device,
4736 )
4737 .into());
4738 }
4739 let values = combine
4740 .tokens
4741 .checked_mul(combine.width)
4742 .ok_or("Step owner-grouped combine copy size overflow")?;
4743 {
4744 let _main = root.gpu.enter_main()?;
4745 root.stream().synchronize()?;
4746 }
4747 let _main = destination.gpu.enter_main()?;
4748 let mut output = destination.uninit(values)?;
4749 destination
4750 .stream()
4751 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4752 Ok(output)
4753 }
4754
4755 pub fn broadcast_step_grouped_expert_parallel_combine(
4756 &self,
4757 plan: &PreparedStepGroupedExpertParallelGate,
4758 combine: &mut PreparedPeerWeightedRouteCombine,
4759 ) -> Result<(), Box<dyn std::error::Error>> {
4760 if !plan.ready
4761 || combine.output_generation != Some(plan.generation)
4762 || combine.projection_generation != plan.generation
4763 || combine.peer_devices.len() + 1 != self.ranks.len()
4764 || combine.peer_outputs.len() + 1 != self.ranks.len()
4765 {
4766 return Err("Step owner-grouped combine output cannot be broadcast".into());
4767 }
4768 combine.broadcast_generation = None;
4769 let values = combine
4770 .tokens
4771 .checked_mul(combine.width)
4772 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4773 {
4774 let root = self
4775 .ranks
4776 .first()
4777 .ok_or("Step owner-grouped combine has no root rank")?;
4778 let _main = root.gpu.enter_main()?;
4779 if root.ctx().ordinal() != combine.root_device {
4780 return Err("Step owner-grouped combine root device changed".into());
4781 }
4782 root.stream().synchronize()?;
4783 }
4784 let source = &combine.output;
4785 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4786 let engine = &self.ranks[index + 1];
4787 let _main = engine.gpu.enter_main()?;
4788 if engine.ctx().ordinal() != combine.peer_devices[index] {
4789 return Err(format!(
4790 "Step owner-grouped combine peer {} device changed",
4791 index + 1
4792 )
4793 .into());
4794 }
4795 let mut destination = destination_buffer.slice_mut(0..values);
4796 engine
4797 .stream()
4798 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4799 }
4800 combine.broadcast_generation = Some(plan.generation);
4801 Ok(())
4802 }
4803
4804 pub fn collect_step_grouped_expert_parallel_broadcast(
4805 &self,
4806 plan: &PreparedStepGroupedExpertParallelGate,
4807 combine: &PreparedPeerWeightedRouteCombine,
4808 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4809 if !plan.ready
4810 || combine.output_generation != Some(plan.generation)
4811 || combine.broadcast_generation != Some(plan.generation)
4812 || combine.peer_outputs.len() + 1 != self.ranks.len()
4813 {
4814 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4815 }
4816 let values = combine
4817 .tokens
4818 .checked_mul(combine.width)
4819 .ok_or("Step owner-grouped combine collection size overflow")?;
4820 let mut outputs = Vec::with_capacity(self.ranks.len());
4821 {
4822 let root = &self.ranks[0];
4823 let _main = root.gpu.enter_main()?;
4824 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4825 }
4826 for (index, output) in combine.peer_outputs.iter().enumerate() {
4827 let engine = &self.ranks[index + 1];
4828 let _main = engine.gpu.enter_main()?;
4829 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4830 }
4831 Ok(outputs)
4832 }
4833
4834 pub fn finish_step_grouped_expert_parallel_layer(
4836 &self,
4837 plan: &PreparedStepGroupedExpertParallelGate,
4838 combine: &PreparedPeerWeightedRouteCombine,
4839 shared: &ResidentReplicatedDeviceRows,
4840 residual: &ResidentReplicatedDeviceRows,
4841 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4842 validate_replicated_device_rows(&self.ranks, shared)?;
4843 validate_replicated_device_rows(&self.ranks, residual)?;
4844 if !plan.ready
4845 || plan.executed_generation != Some(plan.generation)
4846 || combine.output_generation != Some(plan.generation)
4847 || combine.broadcast_generation != Some(plan.generation)
4848 || combine.projection_generation != plan.generation
4849 || combine.peer_outputs.len() + 1 != self.ranks.len()
4850 || shared.tokens != combine.tokens
4851 || residual.tokens != combine.tokens
4852 || shared.width != combine.width
4853 || residual.width != combine.width
4854 {
4855 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4856 }
4857 let values = combine
4858 .tokens
4859 .checked_mul(combine.width)
4860 .ok_or("Step full-layer output size overflow")?;
4861 let mut ranks = Vec::with_capacity(self.ranks.len());
4862 for rank in 0..self.ranks.len() {
4863 let engine = &self.ranks[rank];
4864 let _main = engine.gpu.enter_main()?;
4865 let routed = if rank == 0 {
4866 &combine.output
4867 } else {
4868 &combine.peer_outputs[rank - 1]
4869 };
4870 let mut ffn = engine.uninit(values)?;
4871 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4872 let mut output = engine.uninit(values)?;
4873 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4874 ranks.push(output);
4875 }
4876 Ok(ResidentReplicatedDeviceRows {
4877 ranks,
4878 tokens: combine.tokens,
4879 width: combine.width,
4880 })
4881 }
4882
4883 pub fn run_step_grouped_expert_parallel_combine(
4884 &self,
4885 plan: &PreparedStepGroupedExpertParallelGate,
4886 combine: &mut PreparedPeerWeightedRouteCombine,
4887 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4888 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
4889 self.collect_step_grouped_expert_parallel_combine(plan, combine)
4890 }
4891
4892 pub fn upload_tensor_parallel(
4893 &self,
4894 gate: E4m3ExpertBank<'_>,
4895 up: E4m3ExpertBank<'_>,
4896 down: E4m3ExpertBank<'_>,
4897 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
4898 gate.validate()?;
4899 up.validate()?;
4900 down.validate()?;
4901 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4902 return Err("TP gate/up/down expert counts differ".into());
4903 }
4904 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4905 return Err("TP gate/up dimensions differ".into());
4906 }
4907 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4908 return Err(format!(
4909 "TP down {}x{} does not invert gate/up {}x{}",
4910 down.out_features, down.in_features, gate.out_features, gate.in_features
4911 )
4912 .into());
4913 }
4914 let tp = self.ranks.len();
4915 validate_column_bank_shape(gate, tp)?;
4916 validate_column_bank_shape(up, tp)?;
4917 validate_row_bank_shape(down, tp)?;
4918
4919 let mut gate_ranks = Vec::with_capacity(tp);
4920 let mut up_ranks = Vec::with_capacity(tp);
4921 let mut down_ranks = Vec::with_capacity(tp);
4922 for (rank, engine) in self.ranks.iter().enumerate() {
4923 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
4924 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
4925 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
4926 }
4927 Ok(ResidentTensorParallel {
4928 bank: ResidentTpExpertBank {
4929 gate: gate_ranks,
4930 up: up_ranks,
4931 down: down_ranks,
4932 expert_count: gate.expert_count,
4933 input_width: gate.in_features,
4934 expert_width: gate.out_features,
4935 },
4936 })
4937 }
4938
4939 pub fn run_tensor_parallel_routes(
4940 &self,
4941 experts: &ResidentTensorParallel,
4942 input: &[f32],
4943 tokens: usize,
4944 selected: &[usize],
4945 route_weights: &[f32],
4946 experts_per_token: usize,
4947 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4948 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
4949 validate_activations(input, tokens, experts.bank.input_width)?;
4950 let pairs = tokens
4951 .checked_mul(experts_per_token)
4952 .ok_or("TP route count overflow")?;
4953 if selected.len() != pairs || route_weights.len() != pairs {
4954 return Err(format!(
4955 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
4956 {experts_per_token} ({pairs})",
4957 selected.len(),
4958 route_weights.len(),
4959 )
4960 .into());
4961 }
4962 if !route_weights.iter().all(|weight| weight.is_finite()) {
4963 return Err("TP route weights contain a non-finite value".into());
4964 }
4965
4966 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
4967 for token in 0..tokens {
4968 let input_row =
4969 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
4970 for slot in 0..experts_per_token {
4971 let pair = token * experts_per_token + slot;
4972 let expert = selected[pair];
4973 if expert >= experts.bank.expert_count {
4974 return Err(format!(
4975 "TP selected expert {expert} outside 0..{}",
4976 experts.bank.expert_count
4977 )
4978 .into());
4979 }
4980 let down = if self.native_p2p {
4981 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
4982 } else {
4983 let gate =
4984 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
4985 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
4986 let activated: Vec<f32> = gate
4987 .iter()
4988 .zip(&up)
4989 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4990 .collect();
4991 debug_assert_eq!(activated.len(), experts.bank.expert_width);
4992 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
4993 };
4994 let weight = route_weights[pair];
4995 for (sum, value) in output
4996 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
4997 .iter_mut()
4998 .zip(down)
4999 {
5000 *sum += weight * value;
5001 }
5002 }
5003 }
5004 Ok(output)
5005 }
5006
5007 fn run_column_bank_expert(
5008 &self,
5009 ranks: &[ResidentE4m3ExpertBankRank],
5010 expert: usize,
5011 input: &[f32],
5012 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5013 let local_out = ranks
5014 .first()
5015 .ok_or("TP column bank has no ranks")?
5016 .out_features;
5017 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5018 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5019 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5020 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5021 }
5022 Ok(gathered)
5023 }
5024
5025 fn run_row_bank_expert(
5026 &self,
5027 ranks: &[ResidentE4m3ExpertBankRank],
5028 expert: usize,
5029 input: &[f32],
5030 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5031 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5032 if input.len() != local_in * ranks.len() {
5033 return Err(format!(
5034 "TP row input {} != {} ranks x {local_in}",
5035 input.len(),
5036 ranks.len()
5037 )
5038 .into());
5039 }
5040 let out_features = ranks[0].out_features;
5041 let mut reduced = vec![0.0f32; out_features];
5042 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5043 let blocks = bank
5044 .k_blocks
5045 .ok_or("TP row bank is not packed in native K-block order")?;
5046 if blocks * FP8_BLOCK != local_in {
5047 return Err(format!(
5048 "TP row bank has {blocks} blocks but local input width is {local_in}"
5049 )
5050 .into());
5051 }
5052 for block in 0..blocks {
5053 let global_start = rank * local_in + block * FP8_BLOCK;
5054 let partial = run_resident_bank_expert_block(
5055 engine,
5056 bank,
5057 expert,
5058 block,
5059 &input[global_start..global_start + FP8_BLOCK],
5060 )?;
5061 for (sum, value) in reduced.iter_mut().zip(partial) {
5062 *sum += value;
5063 }
5064 }
5065 }
5066 Ok(reduced)
5067 }
5068
5069 fn run_tensor_parallel_expert_native(
5070 &self,
5071 bank: &ResidentTpExpertBank,
5072 expert: usize,
5073 input: &[f32],
5074 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5075 if !self.native_p2p || self.ranks.len() < 2 {
5076 return Err("native TP expert execution requires at least two P2P ranks".into());
5077 }
5078 let local_out = bank
5079 .gate
5080 .first()
5081 .ok_or("native TP gate bank has no ranks")?
5082 .out_features;
5083 if local_out * self.ranks.len() != bank.expert_width {
5084 return Err(format!(
5085 "native TP gate shards {}x{local_out} != expert width {}",
5086 self.ranks.len(),
5087 bank.expert_width
5088 )
5089 .into());
5090 }
5091
5092 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5095 let root_input = {
5096 let root = &self.ranks[0];
5097 let _main = root.gpu.enter_main()?;
5098 root.htod(input)?
5099 };
5100 rank_inputs.push(root_input);
5101 for engine in &self.ranks[1..] {
5102 let peer_input = {
5103 let _main = engine.gpu.enter_main()?;
5104 let mut peer_input = engine.uninit(input.len())?;
5105 engine
5106 .stream()
5107 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5108 peer_input
5109 };
5110 rank_inputs.push(peer_input);
5111 }
5112
5113 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5114 let mut up_shards = Vec::with_capacity(self.ranks.len());
5115 for rank in 0..self.ranks.len() {
5116 gate_shards.push(run_resident_bank_expert_device(
5117 &self.ranks[rank],
5118 &bank.gate[rank],
5119 expert,
5120 &rank_inputs[rank],
5121 1,
5122 )?);
5123 up_shards.push(run_resident_bank_expert_device(
5124 &self.ranks[rank],
5125 &bank.up[rank],
5126 expert,
5127 &rank_inputs[rank],
5128 1,
5129 )?);
5130 }
5131
5132 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5136 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5137 let activated = gate
5138 .iter()
5139 .zip(&up)
5140 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5141 .collect::<Vec<_>>();
5142 debug_assert_eq!(activated.len(), bank.expert_width);
5143
5144 let root_activated = {
5145 let root = &self.ranks[0];
5146 let _main = root.gpu.enter_main()?;
5147 root.htod(&activated)?
5148 };
5149 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5150 for (rank, engine) in self.ranks.iter().enumerate() {
5151 let start = rank * local_out;
5152 let source = root_activated.slice(start..start + local_out);
5153 let local = {
5154 let _main = engine.gpu.enter_main()?;
5155 let mut local = engine.uninit(local_out)?;
5156 engine.stream().memcpy_dtod(&source, &mut local)?;
5157 local
5158 };
5159 rank_activated.push(local);
5160 }
5161
5162 let out_features = bank
5163 .down
5164 .first()
5165 .ok_or("native TP down bank has no ranks")?
5166 .out_features;
5167 let mut reduced = {
5168 let root = &self.ranks[0];
5169 let _main = root.gpu.enter_main()?;
5170 root.htod(&vec![0.0f32; out_features])?
5171 };
5172 let mut remote_partial_keepalive = Vec::new();
5173 for rank in 0..self.ranks.len() {
5174 let down = &bank.down[rank];
5175 let blocks = down
5176 .k_blocks
5177 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5178 if blocks * FP8_BLOCK != local_out {
5179 return Err(format!(
5180 "native TP rank {rank} has {blocks} blocks but local activation width is \
5181 {local_out}"
5182 )
5183 .into());
5184 }
5185 for block in 0..blocks {
5186 let start = block * FP8_BLOCK;
5187 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5188 let partial = run_resident_bank_expert_block_device(
5189 &self.ranks[rank],
5190 down,
5191 expert,
5192 block,
5193 &input_block,
5194 )?;
5195 let root_partial = if rank == 0 {
5196 partial
5197 } else {
5198 let root = &self.ranks[0];
5199 let _main = root.gpu.enter_main()?;
5200 let mut peer_partial = root.uninit(out_features)?;
5201 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5202 remote_partial_keepalive.push(partial);
5203 peer_partial
5204 };
5205 let next = {
5206 let root = &self.ranks[0];
5207 let _main = root.gpu.enter_main()?;
5208 let mut next = root.uninit(out_features)?;
5209 root.add(&reduced, &root_partial, &mut next, out_features)?;
5210 next
5211 };
5212 reduced = next;
5213 }
5214 }
5215 let output = {
5216 let root = &self.ranks[0];
5217 let _main = root.gpu.enter_main()?;
5218 root.dtoh(&reduced)?
5219 };
5220 drop(remote_partial_keepalive);
5221 Ok(output)
5222 }
5223
5224 pub fn gather_native_column_shards_device(
5226 &self,
5227 shards: &[CudaSlice<f32>],
5228 tokens: usize,
5229 local_out: usize,
5230 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5231 let shard_len = tokens
5232 .checked_mul(local_out)
5233 .ok_or("native TP gather shard size overflow")?;
5234 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5235 return Err("native TP gather shard geometry mismatch".into());
5236 }
5237 for engine in &self.ranks[1..] {
5241 let _main = engine.gpu.enter_main()?;
5242 engine.stream().synchronize()?;
5243 }
5244 let root = &self.ranks[0];
5245 let _main = root.gpu.enter_main()?;
5246 let global_out = shards
5247 .len()
5248 .checked_mul(local_out)
5249 .ok_or("native TP gather output width overflow")?;
5250 let gathered_len = tokens
5251 .checked_mul(global_out)
5252 .ok_or("native TP gather output size overflow")?;
5253 let mut gathered = root.uninit(gathered_len)?;
5254 if self.bulk_p2p {
5255 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5256 if shards.len() > 1 {
5257 let mut staging = root.uninit(shard_len)?;
5258 for (rank, shard) in shards.iter().enumerate().skip(1) {
5259 root.stream().memcpy_dtod(shard, &mut staging)?;
5260 root.place_rows_strided(
5261 &staging,
5262 &mut gathered,
5263 local_out,
5264 tokens,
5265 global_out,
5266 rank * local_out,
5267 )?;
5268 }
5269 }
5270 } else {
5271 for token in 0..tokens {
5272 for (rank, shard) in shards.iter().enumerate() {
5273 let source = shard.slice(token * local_out..(token + 1) * local_out);
5274 let start = token * global_out + rank * local_out;
5275 let mut destination = gathered.slice_mut(start..start + local_out);
5276 root.stream().memcpy_dtod(&source, &mut destination)?;
5277 }
5278 }
5279 }
5280 Ok(gathered)
5281 }
5282
5283 pub fn gather_native_column_shards(
5284 &self,
5285 shards: &[CudaSlice<f32>],
5286 tokens: usize,
5287 local_out: usize,
5288 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5289 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5290 let root = &self.ranks[0];
5291 let _main = root.gpu.enter_main()?;
5292 root.dtoh(&gathered)
5293 }
5294
5295 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5296 &self.decode_v2
5297 }
5298
5299 pub(crate) fn decode_v2_ensure(
5308 &self,
5309 e: &Engine,
5310 q_m: &ResidentBf16ColumnParallel,
5311 k_m: &ResidentBf16ColumnParallel,
5312 v_m: &ResidentBf16ColumnParallel,
5313 o_m: &ResidentStepBf16RowParallel,
5314 heads: usize,
5315 ) -> Result<usize, Box<dyn std::error::Error>> {
5316 if self.ranks.len() > 1 && !self.native_p2p {
5317 return Err("step TP decode v2 requires native P2P ranks".into());
5318 }
5319 let ranks = self.ranks.len();
5320 let fused_door = step_tp_qkv_fused_enabled()?;
5324 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5325 ResidentBf16Weight::F32(_) => true,
5326 ResidentBf16Weight::Bf16(_) => fused_door,
5327 };
5328 for matrix in [q_m, k_m, v_m] {
5329 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5330 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5331 return Err("step TP decode v2 QKV geometry mismatch".into());
5332 }
5333 for rank in &matrix.ranks {
5334 if !arm_ok(&rank.weight) {
5335 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5336 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5337 .into());
5338 }
5339 }
5340 }
5341 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5342 for blocks in &o_m.ranks {
5343 for block in blocks {
5344 if !arm_ok(&block.weight) {
5345 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5346 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5347 .into());
5348 }
5349 }
5350 }
5351 if v_m.out_features != k_m.out_features
5352 || o_m.in_features != q_m.out_features
5353 || heads == 0
5354 || heads % ranks != 0
5355 {
5356 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5357 }
5358 let local_q_dim = q_m.out_features / ranks;
5359 let local_kv_dim = k_m.out_features / ranks;
5360 let o_out = o_m.out_features;
5361 let o_block_cols = o_m.canonical_chunk_cols;
5362 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5363 if blocks_per_rank == 0
5364 || o_m
5365 .ranks
5366 .iter()
5367 .any(|blocks| blocks.len() != blocks_per_rank)
5368 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5369 {
5370 return Err("step TP decode v2 O canonical block grid mismatch".into());
5371 }
5372
5373 let mut guard = self
5374 .decode_v2
5375 .lock()
5376 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5377 if let Some(index) = guard.iter().position(|ws| {
5378 ws.local_q_dim == local_q_dim
5379 && ws.local_kv_dim == local_kv_dim
5380 && ws.heads == heads
5381 && ws.o_out == o_out
5382 && ws.o_block_cols == o_block_cols
5383 && ws.blocks_per_rank == blocks_per_rank
5384 && ws.e_device == e.ctx().ordinal()
5385 && ws.q.len() == ranks
5386 }) {
5387 return Ok(index);
5388 }
5389
5390 let mut q_raw = Vec::with_capacity(ranks);
5391 let mut k_raw = Vec::with_capacity(ranks);
5392 let mut v_raw = Vec::with_capacity(ranks);
5393 let mut q = Vec::with_capacity(ranks);
5394 let mut k = Vec::with_capacity(ranks);
5395 let mut pos = Vec::with_capacity(ranks);
5396 let mut gate = Vec::with_capacity(ranks);
5397 let mut attn_out = Vec::with_capacity(ranks);
5398 let mut gated = Vec::with_capacity(ranks);
5399 let mut fuse_ctr = Vec::with_capacity(ranks);
5400 let mut o_partials = Vec::with_capacity(ranks);
5401 let mut ev_rank = Vec::with_capacity(ranks);
5402 let direct_join = oproj_direct_on();
5403 for (rank, engine) in self.ranks.iter().enumerate() {
5404 let _main = engine.gpu.enter_main()?;
5405 q_raw.push(engine.uninit(local_q_dim)?);
5406 k_raw.push(engine.uninit(local_kv_dim)?);
5407 v_raw.push(engine.uninit(local_kv_dim)?);
5408 q.push(engine.uninit(local_q_dim)?);
5409 k.push(engine.uninit(local_kv_dim)?);
5410 pos.push(engine.htod_i32(&[0])?);
5411 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5412 gate.push(engine.uninit(heads / ranks)?);
5413 attn_out.push(engine.uninit(local_q_dim)?);
5414 gated.push(engine.uninit(local_q_dim)?);
5415 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5416 for _ in 0..blocks_per_rank {
5417 if direct_join && rank != 0 {
5420 let root = &self.ranks[0];
5421 let _root_main = root.gpu.enter_main()?;
5422 rank_partials.push(root.uninit(o_out)?);
5423 } else {
5424 rank_partials.push(engine.uninit(o_out)?);
5425 }
5426 }
5427 o_partials.push(rank_partials);
5428 ev_rank.push(engine.ctx().new_event(None)?);
5429 }
5430 let root = &self.ranks[0];
5431 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5432 let _main = root.gpu.enter_main()?;
5433 (
5434 root.uninit(o_out)?,
5435 root.uninit(o_out)?,
5436 root.uninit(o_out)?,
5437 root.htod(&vec![0.0f32; o_out])?,
5438 root.uninit(ranks * local_kv_dim)?,
5439 root.uninit(ranks * local_kv_dim)?,
5440 root.ctx().new_event(None)?,
5441 root.ctx().new_event(None)?,
5442 )
5443 };
5444 let (gate_e, ev_entry) = {
5445 let _main = e.gpu.enter_main()?;
5446 (e.uninit(heads)?, e.ctx().new_event(None)?)
5447 };
5448 let raw_attn_in = Vec::new();
5449 let raw_pos = Vec::new();
5450 guard.push(StepTpDecodeV2Ws {
5451 tcol_q: Vec::new(),
5452 tcol_k: Vec::new(),
5453 tcol_v: Vec::new(),
5454 tcol_g: Vec::new(),
5455 tcol_in: Vec::new(),
5456 tcol_cap: 0,
5457 fa2_q: Vec::new(),
5458 fa2_gate: Vec::new(),
5459 fa2_gated: Vec::new(),
5460 fa2_cap: 0,
5461 rope_k_t: Vec::new(),
5462 rope_ctr_t: Vec::new(),
5463 rope_pos_t: Vec::new(),
5464 rows_tabs: Vec::new(),
5465 tcol_gated: Vec::new(),
5466 tcol_opart: Vec::new(),
5467 tcol_opeer: None,
5468 tcol_omix: None,
5469 tcol_ocap: 0,
5470 q_raw,
5471 k_raw,
5472 v_raw,
5473 q,
5474 k,
5475 pos,
5476 fuse_ctr,
5477 gate,
5478 attn_out,
5479 gated,
5480 o_partials,
5481 ev_rank,
5482 peer_partial,
5483 reduce_a,
5484 reduce_b,
5485 zeros,
5486 k_shadow,
5487 v_shadow,
5488 ev_refresh,
5489 ev_oproj,
5490 gate_e,
5491 attn_in: Vec::new(),
5492 h_stage: None,
5493 pos_stage: None,
5494 raw_h_stage: 0,
5495 raw_pos_stage: 0,
5496 raw_attn_in,
5497 raw_pos,
5498 raw_o_partial1: 0,
5499 raw_peer_partial: 0,
5500 raw_k1: 0,
5501 raw_v1: 0,
5502 raw_k_shadow: 0,
5503 raw_v_shadow: 0,
5504 raw_mixed_stage_e: 0,
5505 raw_reduce_a: 0,
5506 raw_shadow_stage_e: (0, 0),
5507 ev_entry,
5508 e_device: e.ctx().ordinal(),
5509 local_q_dim,
5510 local_kv_dim,
5511 heads,
5512 o_out,
5513 o_block_cols,
5514 blocks_per_rank,
5515 });
5516 eprintln!(
5517 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5518 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5519 residency=persistent ordering=evented performance_claim=false"
5520 );
5521 Ok(guard.len() - 1)
5522 }
5523
5524 #[allow(clippy::too_many_arguments)]
5532 #[allow(clippy::too_many_arguments)]
5537 pub fn decode_v2_input_qkv_tcol(
5538 &self,
5539 ws_index: usize,
5540 e: &Engine,
5541 h_t: &CudaSlice<f32>,
5542 t: usize,
5543 q_m: &ResidentBf16ColumnParallel,
5544 k_m: &ResidentBf16ColumnParallel,
5545 v_m: &ResidentBf16ColumnParallel,
5546 gate_shards: Option<StepTpGateShards<'_>>,
5547 ) -> Result<(), Box<dyn std::error::Error>> {
5548 let ranks = self.ranks.len();
5549 let mut guard = self
5550 .decode_v2
5551 .lock()
5552 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5553 let ws = guard
5554 .get_mut(ws_index)
5555 .ok_or("step TP decode v2 workspace index out of range")?;
5556 let in_f = q_m.in_features;
5557 if h_t.len() < t * in_f || t == 0 || t > 32 {
5558 return Err("decode_v2_input_qkv_tcol geometry".into());
5559 }
5560 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5562 ws.tcol_q.clear();
5563 ws.tcol_k.clear();
5564 ws.tcol_v.clear();
5565 ws.tcol_g.clear();
5566 ws.tcol_in.clear();
5567 for engine in &self.ranks {
5568 let _m = engine.gpu.enter_main()?;
5569 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
5570 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
5571 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
5572 ws.tcol_g
5573 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
5574 ws.tcol_in.push(engine.uninit(32 * in_f)?);
5575 }
5576 ws.tcol_cap = 32;
5577 }
5578 use cudarc::driver::DevicePtr;
5580 let raw_src = {
5581 let _main = e.gpu.enter_main()?;
5582 let stream = e.stream();
5583 let (p, _g) = h_t.device_ptr(&stream);
5584 ws.ev_entry.record(&stream)?;
5585 p as u64
5586 };
5587 for rank in 0..ranks {
5588 let engine = &self.ranks[rank];
5589 let _main = engine.gpu.enter_main()?;
5590 engine.stream().wait(&ws.ev_entry)?;
5591 let raw_dst = {
5592 let stream = engine.stream();
5593 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5594 p as u64
5595 };
5596 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5597 let out_g = match &gate_shards {
5598 Some(_) => ws.heads / ranks,
5599 None => 0,
5600 };
5601 match (
5602 &q_m.ranks[rank].weight,
5603 &k_m.ranks[rank].weight,
5604 &v_m.ranks[rank].weight,
5605 ) {
5606 (
5607 ResidentBf16Weight::Bf16(wq),
5608 ResidentBf16Weight::Bf16(wk),
5609 ResidentBf16Weight::Bf16(wv),
5610 ) => {
5611 let wg = match &gate_shards {
5612 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5613 Some(StepTpGateShards::F32(_)) => {
5614 return Err(
5615 "tcol verify: gate shard class does not match bf16 QKV".into()
5616 );
5617 }
5618 None => wq,
5619 };
5620 let StepTpDecodeV2Ws {
5621 tcol_q,
5622 tcol_k,
5623 tcol_v,
5624 tcol_g,
5625 tcol_in,
5626 local_q_dim,
5627 local_kv_dim,
5628 ..
5629 } = &mut *ws;
5630 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5633 let refk = *REFK
5634 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5635 if refk {
5636 let lq = *local_q_dim;
5637 let lkv = *local_kv_dim;
5638 let mut hrow = engine.uninit(in_f)?;
5639 let mut qr = engine.uninit(lq)?;
5640 let mut kr = engine.uninit(lkv)?;
5641 let mut vr = engine.uninit(lkv)?;
5642 let mut gr = engine.uninit(out_g.max(1))?;
5643 for c in 0..t {
5644 {
5645 let mut dst = hrow.slice_mut(0..in_f);
5646 engine.stream().memcpy_dtod(
5647 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5648 &mut dst,
5649 )?;
5650 }
5651 engine.matvec_bf16_qkvg_into(
5652 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5653 lq, lkv, out_g,
5654 )?;
5655 let stream = engine.stream();
5656 {
5657 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5658 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5659 }
5660 {
5661 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5662 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5663 }
5664 {
5665 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5666 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5667 }
5668 if out_g > 0 {
5669 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5670 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5671 }
5672 }
5673 } else {
5674 engine.matvec_bf16_qkvg_tcol_into(
5675 wq,
5676 wk,
5677 wv,
5678 wg,
5679 &tcol_in[rank],
5680 &mut tcol_q[rank],
5681 &mut tcol_k[rank],
5682 &mut tcol_v[rank],
5683 &mut tcol_g[rank],
5684 in_f,
5685 *local_q_dim,
5686 *local_kv_dim,
5687 out_g,
5688 t,
5689 )?;
5690 }
5691 }
5692 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5693 }
5694 }
5695 Ok(())
5696 }
5697
5698 pub(crate) fn decode_v2_oproj_tcol_eligible(
5702 &self,
5703 ws: &StepTpDecodeV2Ws,
5704 o_m: &ResidentStepBf16RowParallel,
5705 ) -> bool {
5706 self.ranks.len() == 2
5707 && ws.blocks_per_rank == 4
5708 && step_tp_qkv_fused_enabled().unwrap_or(false)
5709 && no_local_shadow_on()
5710 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5711 && o_m
5712 .ranks
5713 .iter()
5714 .flatten()
5715 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5716 }
5717
5718 pub(crate) fn decode_v2_stash_fa2(
5723 &self,
5724 ws: &mut StepTpDecodeV2Ws,
5725 e: &Engine,
5726 col: usize,
5727 ) -> Result<(), Box<dyn std::error::Error>> {
5728 let ranks = self.ranks.len();
5729 if col >= 32 {
5730 return Err("decode_v2_stash_fa2 column out of range".into());
5731 }
5732 let lq = ws.local_q_dim;
5733 let lg = (ws.heads / ranks).max(1);
5734 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks {
5735 ws.fa2_q.clear();
5736 ws.fa2_gate.clear();
5737 ws.fa2_gated.clear();
5738 ws.rope_k_t.clear();
5739 ws.rope_ctr_t.clear();
5740 ws.rope_pos_t.clear();
5741 for engine in &self.ranks {
5742 let _m = engine.gpu.enter_main()?;
5743 ws.fa2_q.push(engine.uninit(32 * lq)?);
5744 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5745 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5746 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
5747 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5748 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5749 }
5750 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5751 ws.fa2_cap = 32;
5752 }
5753 for rank in 0..ranks {
5754 let engine = &self.ranks[rank];
5755 let _main = engine.gpu.enter_main()?;
5756 {
5757 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5758 engine
5759 .stream()
5760 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5761 }
5762 {
5763 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5764 engine
5765 .stream()
5766 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5767 }
5768 ws.ev_rank[rank].record(&engine.stream())?;
5769 }
5770 {
5771 let _main = e.gpu.enter_main()?;
5772 for ev in ws.ev_rank.iter() {
5773 e.stream().wait(ev)?;
5774 }
5775 }
5776 Ok(())
5777 }
5778
5779 #[allow(clippy::too_many_arguments)]
5786 pub(crate) fn decode_v2_spec_fa2_join(
5787 &self,
5788 ws_index: usize,
5789 e: &Engine,
5790 o_m: &ResidentStepBf16RowParallel,
5791 kv: &ResidentTpKvCache,
5792 head_dim: usize,
5793 window: usize,
5794 bucket_max: usize,
5795 scale: f32,
5796 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5797 let ranks = self.ranks.len();
5798 static ONCE: std::sync::Once = std::sync::Once::new();
5800 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
5801 {
5802 let mut guard = self
5803 .decode_v2
5804 .lock()
5805 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5806 let ws = guard
5807 .get_mut(ws_index)
5808 .ok_or("step TP decode v2 workspace index out of range")?;
5809 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
5810 return Err("spec fa2 join without stashed columns".into());
5811 }
5812 let lq = ws.local_q_dim;
5813 let local_heads = (ws.heads / ranks).max(1);
5814 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
5815 let capacity = kv.physical_capacity();
5816 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
5817 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
5819 ws.tcol_gated.clear();
5820 ws.tcol_opart.clear();
5821 for engine in &self.ranks {
5822 let _m = engine.gpu.enter_main()?;
5823 ws.tcol_gated.push(engine.uninit(32 * lq)?);
5824 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
5825 }
5826 let root = &self.ranks[0];
5827 let _m = root.gpu.enter_main()?;
5828 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
5829 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
5830 ws.tcol_ocap = 32;
5831 }
5832 for rank in 0..ranks {
5833 let engine = &self.ranks[rank];
5834 let _main = engine.gpu.enter_main()?;
5835 let rank_cache = kv
5836 .rank(rank)
5837 .ok_or("spec fa2 join lost its KV cache rank")?;
5838 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
5839 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
5840 {
5841 let StepTpDecodeV2Ws {
5842 fa2_q,
5843 fa2_gate,
5844 fa2_gated,
5845 ..
5846 } = &mut *ws;
5847 engine.fa_decode_dcw2(
5848 &fa2_q[rank],
5849 &k_ring,
5850 &v_ring,
5851 &mut fa2_gated[rank],
5852 head_dim,
5853 local_heads,
5854 local_kv_heads,
5855 rank_cache.len_d(),
5856 rank_cache.base_d(),
5857 window,
5858 bucket_max,
5859 scale,
5860 k_tok_bytes,
5861 v_tok_bytes,
5862 &fa2_gate[rank],
5863 )?;
5864 }
5865 let StepTpDecodeV2Ws {
5868 fa2_gated,
5869 tcol_gated,
5870 ..
5871 } = &mut *ws;
5872 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
5873 engine
5874 .stream()
5875 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
5876 }
5877 }
5878 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
5879 }
5880
5881 #[allow(clippy::too_many_arguments)]
5891 pub(crate) fn decode_v2_rope_fa_rows(
5892 &self,
5893 ws_index: usize,
5894 e: &Engine,
5895 o_m: &ResidentStepBf16RowParallel,
5896 session_parts: &[Vec<[u64; 4]>],
5897 tab_keys: &[u64],
5898 positions: &[i32],
5899 stage_pos: bool,
5900 same_session: bool,
5901 q_norms: &[CudaSlice<f32>],
5902 k_norms: &[CudaSlice<f32>],
5903 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
5904 t: usize,
5905 head_dim: usize,
5906 n_rot: usize,
5907 window: usize,
5908 max_ns: usize,
5909 scale: f32,
5910 k_tok_bytes: usize,
5911 v_tok_bytes: usize,
5912 eps: f32,
5913 rope_base: f32,
5914 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
5915 use cudarc::driver::DevicePtr;
5916 let ranks = self.ranks.len();
5917 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
5918 return Err("rope fa rows geometry".into());
5919 }
5920 {
5921 let mut guard = self
5922 .decode_v2
5923 .lock()
5924 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5925 let ws = guard
5926 .get_mut(ws_index)
5927 .ok_or("step TP decode v2 workspace index out of range")?;
5928 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5929 return Err("rope fa rows without tcol slabs".into());
5930 }
5931 let lq = ws.local_q_dim;
5932 let lkv = ws.local_kv_dim;
5933 let lg = (ws.heads / ranks).max(1);
5934 let local_heads = (ws.heads / ranks).max(1);
5935 let local_kv_heads = (lkv / head_dim).max(1);
5936 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks {
5938 ws.fa2_q.clear();
5939 ws.fa2_gate.clear();
5940 ws.fa2_gated.clear();
5941 ws.rope_k_t.clear();
5942 ws.rope_ctr_t.clear();
5943 ws.rope_pos_t.clear();
5944 for engine in &self.ranks {
5945 let _m = engine.gpu.enter_main()?;
5946 ws.fa2_q.push(engine.uninit(32 * lq)?);
5947 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5948 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5949 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
5950 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5951 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5952 }
5953 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5954 ws.fa2_cap = 32;
5955 }
5956 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
5957 ws.tcol_gated.clear();
5958 ws.tcol_opart.clear();
5959 for engine in &self.ranks {
5960 let _m = engine.gpu.enter_main()?;
5961 ws.tcol_gated.push(engine.uninit(32 * lq)?);
5962 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
5963 }
5964 let root = &self.ranks[0];
5965 let _m = root.gpu.enter_main()?;
5966 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
5967 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
5968 ws.tcol_ocap = 32;
5969 }
5970 for rank in 0..ranks {
5971 let engine = &self.ranks[rank];
5972 let _main = engine.gpu.enter_main()?;
5973 if stage_pos {
5974 let host: Vec<i32> = positions[..t].to_vec();
5975 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
5976 engine.stream().memcpy_htod(&host, &mut view)?;
5977 }
5978 if !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
5981 let ctr_base = {
5982 let s = engine.stream();
5983 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
5984 p as u64
5985 };
5986 let mut host = Vec::with_capacity(t * 6);
5987 for (r, parts) in session_parts[rank].iter().enumerate().take(t) {
5988 host.extend_from_slice(&[
5989 parts[0],
5990 parts[1],
5991 parts[2],
5992 parts[3],
5993 if same_session {
5994 ctr_base
5995 } else {
5996 ctr_base + (r as u64) * 4
5997 },
5998 if same_session {
5999 (t - 1 - r) as u64
6000 } else {
6001 0u64
6002 },
6003 ]);
6004 }
6005 let tab = engine.stream().clone_htod(&host)?;
6006 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6007 }
6008 let StepTpDecodeV2Ws {
6009 tcol_q,
6010 tcol_k,
6011 tcol_v,
6012 tcol_g,
6013 fa2_q,
6014 fa2_gated,
6015 rope_k_t,
6016 rope_pos_t,
6017 rows_tabs,
6018 ..
6019 } = &mut *ws;
6020 let tab = rows_tabs[rank]
6021 .get(&tab_keys[rank])
6022 .expect("inserted above");
6023 engine.qk_norm_rope_append_inc_dcw_rows(
6024 &tcol_q[rank],
6025 &tcol_k[rank],
6026 &tcol_v[rank],
6027 &q_norms[rank],
6028 &k_norms[rank],
6029 &mut fa2_q[rank],
6030 &mut rope_k_t[rank],
6031 tab,
6032 &rope_pos_t[rank],
6033 same_session,
6034 t,
6035 lkv,
6036 lkv,
6037 k_tok_bytes,
6038 v_tok_bytes,
6039 head_dim,
6040 n_rot,
6041 local_heads,
6042 local_kv_heads,
6043 eps,
6044 rope_base,
6045 1.0,
6046 rope_freqs[rank],
6047 )?;
6048 engine.fa_decode_dcw_rows(
6049 &fa2_q[rank],
6050 tab,
6051 &mut fa2_gated[rank],
6052 t,
6053 head_dim,
6054 local_heads,
6055 local_kv_heads,
6056 window,
6057 max_ns,
6058 scale,
6059 k_tok_bytes,
6060 v_tok_bytes,
6061 &tcol_g[rank],
6062 )?;
6063 let StepTpDecodeV2Ws {
6064 fa2_gated,
6065 tcol_gated,
6066 ..
6067 } = &mut *ws;
6068 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6069 engine
6070 .stream()
6071 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6072 }
6073 }
6074 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6075 }
6076
6077 #[allow(clippy::too_many_arguments)]
6084 pub(crate) fn decode_v2_fa_rows_join(
6085 &self,
6086 ws_index: usize,
6087 e: &Engine,
6088 o_m: &ResidentStepBf16RowParallel,
6089 tabs: &[&crate::CudaSlice<u64>],
6090 t: usize,
6091 head_dim: usize,
6092 window: usize,
6093 max_ns: usize,
6094 scale: f32,
6095 k_tok_bytes: usize,
6096 v_tok_bytes: usize,
6097 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6098 let ranks = self.ranks.len();
6099 if tabs.len() != ranks {
6100 return Err("fa rows join needs one table per rank".into());
6101 }
6102 {
6103 let mut guard = self
6104 .decode_v2
6105 .lock()
6106 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6107 let ws = guard
6108 .get_mut(ws_index)
6109 .ok_or("step TP decode v2 workspace index out of range")?;
6110 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6111 return Err("fa rows join without stashed rows".into());
6112 }
6113 let lq = ws.local_q_dim;
6114 let local_heads = (ws.heads / ranks).max(1);
6115 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6116 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6117 ws.tcol_gated.clear();
6118 ws.tcol_opart.clear();
6119 for engine in &self.ranks {
6120 let _m = engine.gpu.enter_main()?;
6121 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6122 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6123 }
6124 let root = &self.ranks[0];
6125 let _m = root.gpu.enter_main()?;
6126 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6127 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6128 ws.tcol_ocap = 32;
6129 }
6130 for rank in 0..ranks {
6131 let engine = &self.ranks[rank];
6132 let _main = engine.gpu.enter_main()?;
6133 {
6134 let StepTpDecodeV2Ws {
6135 fa2_q,
6136 fa2_gate,
6137 fa2_gated,
6138 ..
6139 } = &mut *ws;
6140 engine.fa_decode_dcw_rows(
6141 &fa2_q[rank],
6142 tabs[rank],
6143 &mut fa2_gated[rank],
6144 t,
6145 head_dim,
6146 local_heads,
6147 local_kv_heads,
6148 window,
6149 max_ns,
6150 scale,
6151 k_tok_bytes,
6152 v_tok_bytes,
6153 &fa2_gate[rank],
6154 )?;
6155 }
6156 let StepTpDecodeV2Ws {
6157 fa2_gated,
6158 tcol_gated,
6159 ..
6160 } = &mut *ws;
6161 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6162 engine
6163 .stream()
6164 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6165 }
6166 }
6167 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6168 }
6169
6170 pub(crate) fn decode_v2_stash_gated(
6175 &self,
6176 ws: &mut StepTpDecodeV2Ws,
6177 e: &Engine,
6178 col: usize,
6179 ) -> Result<(), Box<dyn std::error::Error>> {
6180 let ranks = self.ranks.len();
6181 if col >= 8 {
6182 return Err("decode_v2_stash_gated column out of range".into());
6183 }
6184 let lq = ws.local_q_dim;
6185 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6186 ws.tcol_gated.clear();
6187 ws.tcol_opart.clear();
6188 for engine in &self.ranks {
6189 let _m = engine.gpu.enter_main()?;
6190 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6191 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6192 }
6193 let root = &self.ranks[0];
6194 let _m = root.gpu.enter_main()?;
6195 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6196 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6197 ws.tcol_ocap = 32;
6198 }
6199 for rank in 0..ranks {
6200 let engine = &self.ranks[rank];
6201 let _main = engine.gpu.enter_main()?;
6202 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6203 engine
6204 .stream()
6205 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6206 ws.ev_rank[rank].record(&engine.stream())?;
6210 }
6211 {
6212 let _main = e.gpu.enter_main()?;
6213 for ev in ws.ev_rank.iter() {
6214 e.stream().wait(ev)?;
6215 }
6216 }
6217 Ok(())
6218 }
6219
6220 pub(crate) fn decode_v2_oproj_tcol(
6226 &self,
6227 ws_index: usize,
6228 e: &Engine,
6229 o_m: &ResidentStepBf16RowParallel,
6230 t: usize,
6231 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6232 let ranks = self.ranks.len();
6233 let mut guard = self
6234 .decode_v2
6235 .lock()
6236 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6237 let ws = guard
6238 .get_mut(ws_index)
6239 .ok_or("step TP decode v2 workspace index out of range")?;
6240 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6241 return Err("decode_v2_oproj_tcol geometry".into());
6242 }
6243 for rank in 0..ranks {
6244 let engine = &self.ranks[rank];
6245 let _main = engine.gpu.enter_main()?;
6246 let mut weights = Vec::with_capacity(4);
6247 for block in 0..4 {
6248 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6249 return Err("tcol o_proj requires bf16-resident O blocks".into());
6250 };
6251 weights.push(weight);
6252 }
6253 {
6254 let StepTpDecodeV2Ws {
6255 tcol_gated,
6256 tcol_opart,
6257 local_q_dim,
6258 o_block_cols,
6259 o_out,
6260 ..
6261 } = &mut *ws;
6262 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6265 let refk = *REFK
6266 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6267 if refk {
6268 let lq = *local_q_dim;
6269 let mut xr = engine.uninit(lq)?;
6270 let mut yr = engine.uninit(*o_out)?;
6271 for c in 0..t {
6272 {
6273 let mut dst = xr.slice_mut(0..lq);
6274 engine.stream().memcpy_dtod(
6275 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6276 &mut dst,
6277 )?;
6278 }
6279 engine.matvec_bf16_b4_into(
6280 [weights[0], weights[1], weights[2], weights[3]],
6281 &xr,
6282 &mut yr,
6283 *o_block_cols,
6284 *o_out,
6285 )?;
6286 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6287 engine
6288 .stream()
6289 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6290 }
6291 } else {
6292 engine.matvec_bf16_b4_tcol_into(
6293 [weights[0], weights[1], weights[2], weights[3]],
6294 &tcol_gated[rank],
6295 &mut tcol_opart[rank],
6296 *o_block_cols,
6297 *o_out,
6298 t,
6299 )?;
6300 }
6301 }
6302 if rank != 0 {
6303 ws.ev_rank[rank].record(&engine.stream())?;
6304 }
6305 }
6306 let root = &self.ranks[0];
6307 {
6308 let _main = root.gpu.enter_main()?;
6309 for ev in ws.ev_rank.iter().skip(1) {
6310 root.stream().wait(ev)?;
6311 }
6312 {
6313 let StepTpDecodeV2Ws {
6314 tcol_opart,
6315 tcol_opeer,
6316 tcol_omix,
6317 o_out,
6318 ..
6319 } = &mut *ws;
6320 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6321 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6322 {
6323 let mut dst = opeer.slice_mut(0..t * *o_out);
6324 root.stream()
6325 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6326 }
6327 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6330 }
6331 ws.ev_oproj.record(&root.stream())?;
6332 }
6333 let _main = e.gpu.enter_main()?;
6334 e.stream().wait(&ws.ev_oproj)?;
6335 let mut out = e.uninit(t * ws.o_out)?;
6336 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6337 e.stream().memcpy_dtod(
6338 &omix.slice(0..t * ws.o_out),
6339 &mut out.slice_mut(0..t * ws.o_out),
6340 )?;
6341 Ok(out)
6342 }
6343
6344 pub(crate) fn decode_v2_input_qkv(
6345 &self,
6346 ws: &mut StepTpDecodeV2Ws,
6347 e: &Engine,
6348 h: &CudaSlice<f32>,
6349 pos_d: &CudaSlice<i32>,
6350 gate_raw: Option<&CudaSlice<f32>>,
6351 gate_shards: Option<StepTpGateShards<'_>>,
6352 decode_input: &mut ResidentReplicatedDeviceRows,
6353 q_m: &ResidentBf16ColumnParallel,
6354 k_m: &ResidentBf16ColumnParallel,
6355 v_m: &ResidentBf16ColumnParallel,
6356 q_norm: &[CudaSlice<f32>],
6357 k_norm: &[CudaSlice<f32>],
6358 head_dim: usize,
6359 n_rot: usize,
6360 rope_base: f32,
6361 rope_freqs: &[Option<&CudaSlice<f32>>],
6362 rms_eps: f32,
6363 defer_norm_rope: bool,
6364 tcol_col: Option<usize>,
6365 ) -> Result<(), Box<dyn std::error::Error>> {
6366 let ranks = self.ranks.len();
6367 validate_replicated_device_rows(&self.ranks, decode_input)?;
6368 if decode_input.tokens != 1
6369 || decode_input.width != q_m.in_features
6370 || pos_d.len() != 1
6371 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6372 || gate_raw.is_none() != gate_shards.is_some()
6373 || gate_shards.as_ref().is_some_and(|shards| match shards {
6374 StepTpGateShards::F32(shards) => shards.len() != ranks,
6375 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6376 })
6377 || q_norm.len() != ranks
6378 || k_norm.len() != ranks
6379 || rope_freqs.len() != ranks
6380 || e.ctx().ordinal() != ws.e_device
6381 {
6382 return Err("step TP decode v2 input geometry mismatch".into());
6383 }
6384
6385 let qkv_fused = step_tp_qkv_fused_enabled()?;
6386 if gate_shards.is_some() && !qkv_fused {
6387 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6388 }
6389 let values = decode_input.width;
6390 if h.len() != values {
6391 return Err(format!(
6392 "step TP decode v2 hidden width {} != replicated width {values}",
6393 h.len()
6394 )
6395 .into());
6396 }
6397
6398 if qkv_fused {
6399 if ws.h_stage.is_none() {
6403 use cudarc::driver::DevicePtr;
6404 let _main = e.gpu.enter_main()?;
6405 let h_stage = e.uninit(values)?;
6406 let pos_stage = e.htod_i32(&[0])?;
6407 {
6408 let stream = e.stream();
6409 let (hp, _g0) = h_stage.device_ptr(&stream);
6410 let (pp, _g1) = pos_stage.device_ptr(&stream);
6411 ws.raw_h_stage = hp as u64;
6412 ws.raw_pos_stage = pp as u64;
6413 }
6414 ws.h_stage = Some(h_stage);
6415 ws.pos_stage = Some(pos_stage);
6416 for rank in 0..ranks {
6417 use cudarc::driver::DevicePtr;
6418 let engine = &self.ranks[rank];
6419 let _rmain = engine.gpu.enter_main()?;
6420 let attn_in = engine.uninit(values)?;
6421 let (dp, pp) = {
6422 let stream = engine.stream();
6423 let (dp, _g2) = attn_in.device_ptr(&stream);
6424 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6425 (dp as u64, pp as u64)
6426 };
6427 ws.raw_attn_in.push(dp);
6428 ws.raw_pos.push(pp);
6429 ws.attn_in.push(attn_in);
6430 }
6431 {
6432 use cudarc::driver::DevicePtr;
6433 let root = &self.ranks[0];
6434 let _rmain = root.gpu.enter_main()?;
6435 let stream = root.stream();
6436 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6437 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6438 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6439 ws.raw_peer_partial = a as u64;
6440 ws.raw_k_shadow = b as u64;
6441 ws.raw_v_shadow = c as u64;
6442 }
6443 {
6444 use cudarc::driver::DevicePtr;
6445 let rank1 = &self.ranks[1];
6446 let _rmain = rank1.gpu.enter_main()?;
6447 let stream = rank1.stream();
6448 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6449 let (b, _g) = ws.k[1].device_ptr(&stream);
6450 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6451 ws.raw_o_partial1 = a as u64;
6452 ws.raw_k1 = b as u64;
6453 ws.raw_v1 = c as u64;
6454 }
6455 }
6456 {
6457 let _main = e.gpu.enter_main()?;
6458 {
6459 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6462 let mut dst = h_stage.slice_mut(0..values);
6463 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6464 }
6465 {
6466 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6467 let mut dst = pos_stage.slice_mut(0..1);
6468 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6469 }
6470 ws.ev_entry.record(&e.stream())?;
6471 }
6472 for rank in 0..ranks {
6473 let engine = &self.ranks[rank];
6474 let _main = engine.gpu.enter_main()?;
6475 engine.stream().wait(&ws.ev_entry)?;
6476 }
6477 } else {
6478 {
6480 let _main = e.gpu.enter_main()?;
6481 if let Some(gate_raw) = gate_raw {
6482 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6483 e.stream()
6484 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6485 }
6486 ws.ev_entry.record(&e.stream())?;
6487 }
6488 {
6489 let root = &self.ranks[0];
6490 let _main = root.gpu.enter_main()?;
6491 root.stream().wait(&ws.ev_entry)?;
6492 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6493 root.stream()
6494 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6495 ws.ev_refresh.record(&root.stream())?;
6496 }
6497 for rank in 1..ranks {
6498 let engine = &self.ranks[rank];
6499 let _main = engine.gpu.enter_main()?;
6500 engine.stream().wait(&ws.ev_refresh)?;
6501 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6502 let mut destination = peer_rows[0].slice_mut(0..values);
6503 engine
6504 .stream()
6505 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6506 }
6507 }
6508 for rank in 0..ranks {
6509 self.decode_v2_input_qkv_rank(
6510 ws,
6511 pos_d,
6512 decode_input,
6513 q_m,
6514 k_m,
6515 v_m,
6516 q_norm,
6517 k_norm,
6518 head_dim,
6519 n_rot,
6520 rope_base,
6521 rope_freqs,
6522 rms_eps,
6523 gate_shards.as_ref(),
6524 qkv_fused,
6525 defer_norm_rope,
6526 rank,
6527 tcol_col,
6528 )?;
6529 }
6530 Ok(())
6531 }
6532
6533 #[allow(clippy::too_many_arguments)]
6536 pub(crate) fn decode_v2_input_qkv_rank(
6537 &self,
6538 ws: &mut StepTpDecodeV2Ws,
6539 pos_d: &CudaSlice<i32>,
6540 decode_input: &mut ResidentReplicatedDeviceRows,
6541 q_m: &ResidentBf16ColumnParallel,
6542 k_m: &ResidentBf16ColumnParallel,
6543 v_m: &ResidentBf16ColumnParallel,
6544 q_norm: &[CudaSlice<f32>],
6545 k_norm: &[CudaSlice<f32>],
6546 head_dim: usize,
6547 n_rot: usize,
6548 rope_base: f32,
6549 rope_freqs: &[Option<&CudaSlice<f32>>],
6550 rms_eps: f32,
6551 gate_shards: Option<&StepTpGateShards<'_>>,
6552 qkv_fused: bool,
6553 defer_norm_rope: bool,
6554 rank: usize,
6555 tcol_col: Option<usize>,
6556 ) -> Result<(), Box<dyn std::error::Error>> {
6557 let ranks = self.ranks.len();
6558 let local_heads = ws.local_q_dim / head_dim;
6559 let local_kv_heads = ws.local_kv_dim / head_dim;
6560 let engine = &self.ranks[rank];
6561 let _main = engine.gpu.enter_main()?;
6562 let ws_e_device = ws.e_device;
6563 if qkv_fused && tcol_col.is_some() {
6568 let c = tcol_col.expect("checked");
6569 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6570 return Err("tcol select without precompute".into());
6571 }
6572 if engine.ctx().ordinal() != ws_e_device {
6576 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6577 }
6578 let StepTpDecodeV2Ws {
6579 tcol_q,
6580 tcol_k,
6581 tcol_v,
6582 tcol_g,
6583 q_raw,
6584 k_raw,
6585 v_raw,
6586 gate,
6587 local_q_dim,
6588 local_kv_dim,
6589 heads,
6590 ..
6591 } = &mut *ws;
6592 let lg = *heads / ranks;
6593 let stream = engine.stream();
6594 {
6595 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6596 stream.memcpy_dtod(
6597 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6598 &mut dst,
6599 )?;
6600 }
6601 {
6602 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6603 stream.memcpy_dtod(
6604 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6605 &mut dst,
6606 )?;
6607 }
6608 {
6609 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6610 stream.memcpy_dtod(
6611 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6612 &mut dst,
6613 )?;
6614 }
6615 if lg > 0 {
6616 let mut dst = gate[rank].slice_mut(0..lg);
6617 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6618 }
6619 if !defer_norm_rope {
6620 } else {
6624 return Ok(());
6625 }
6626 }
6627 if qkv_fused {
6628 let same_dev = engine.ctx().ordinal() == ws.e_device;
6633 if !same_dev {
6634 raw_copy_bytes(
6635 ws.raw_attn_in[rank],
6636 ws.raw_h_stage,
6637 q_m.in_features * 4,
6638 engine,
6639 )?;
6640 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6641 }
6642 let StepTpDecodeV2Ws {
6643 q_raw,
6644 k_raw,
6645 v_raw,
6646 gate,
6647 gate_e,
6648 attn_in,
6649 h_stage,
6650 heads,
6651 local_q_dim,
6652 local_kv_dim,
6653 ..
6654 } = &mut *ws;
6655 let input_ref: &CudaSlice<f32> = if same_dev {
6656 h_stage
6657 .as_ref()
6658 .ok_or("step TP decode v2 stage not armed")?
6659 } else {
6660 &attn_in[rank]
6661 };
6662 match (
6663 &q_m.ranks[rank].weight,
6664 &k_m.ranks[rank].weight,
6665 &v_m.ranks[rank].weight,
6666 ) {
6667 (
6668 ResidentBf16Weight::F32(wq),
6669 ResidentBf16Weight::F32(wk),
6670 ResidentBf16Weight::F32(wv),
6671 ) => {
6672 let (wg, out_g) = match &gate_shards {
6673 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6674 Some(StepTpGateShards::Bf16(_)) => {
6675 return Err("step TP decode v2 gate shard class does not \
6676 match the F32 projections"
6677 .into());
6678 }
6679 None => (&*gate_e, 0),
6681 };
6682 engine.matvec_f32_qkv_into(
6683 wq,
6684 wk,
6685 wv,
6686 wg,
6687 input_ref,
6688 &mut q_raw[rank],
6689 &mut k_raw[rank],
6690 &mut v_raw[rank],
6691 &mut gate[rank],
6692 q_m.in_features,
6693 *local_q_dim,
6694 *local_kv_dim,
6695 out_g,
6696 )?;
6697 }
6698 (
6699 ResidentBf16Weight::Bf16(wq),
6700 ResidentBf16Weight::Bf16(wk),
6701 ResidentBf16Weight::Bf16(wv),
6702 ) => {
6703 let (wg, out_g) = match &gate_shards {
6704 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6705 Some(StepTpGateShards::F32(_)) => {
6706 return Err("step TP decode v2 gate shard class does not \
6707 match the bf16 projections"
6708 .into());
6709 }
6710 None => (wq, 0),
6711 };
6712 engine.matvec_bf16_qkvg_into(
6713 wq,
6714 wk,
6715 wv,
6716 wg,
6717 input_ref,
6718 &mut q_raw[rank],
6719 &mut k_raw[rank],
6720 &mut v_raw[rank],
6721 &mut gate[rank],
6722 q_m.in_features,
6723 *local_q_dim,
6724 *local_kv_dim,
6725 out_g,
6726 )?;
6727 }
6728 _ => {
6729 return Err("step TP decode v2 QKV projections mix residency classes".into());
6730 }
6731 }
6732 } else {
6733 for (matrix, local_out, raw) in [
6734 (q_m, ws.local_q_dim, &mut ws.q_raw),
6735 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6736 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6737 ] {
6738 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6739 return Err("step TP decode v2 lost its F32 projection residency".into());
6740 };
6741 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6742 engine.linear_f32_resident_canonical_rows_t1_into(
6743 &decode_input.ranks[rank],
6744 values_w,
6745 &mut raw[rank],
6746 matrix.in_features,
6747 local_out,
6748 chunk_rows,
6749 )?;
6750 }
6751 }
6752 if qkv_fused && defer_norm_rope {
6753 } else if qkv_fused {
6755 let StepTpDecodeV2Ws {
6758 q_raw,
6759 k_raw,
6760 q,
6761 k,
6762 pos,
6763 pos_stage,
6764 ..
6765 } = &mut *ws;
6766 let same_dev = engine.ctx().ordinal() == ws_e_device;
6767 let pos_ref: &CudaSlice<i32> = if same_dev {
6768 pos_stage
6769 .as_ref()
6770 .ok_or("step TP decode v2 pos stage not armed")?
6771 } else {
6772 &pos[rank]
6773 };
6774 engine.qk_norm_rope_into(
6775 &q_raw[rank],
6776 &k_raw[rank],
6777 &q_norm[rank],
6778 &k_norm[rank],
6779 &mut q[rank],
6780 &mut k[rank],
6781 pos_ref,
6782 head_dim,
6783 n_rot,
6784 local_heads,
6785 local_kv_heads,
6786 rms_eps,
6787 rope_base,
6788 1.0,
6789 rope_freqs[rank],
6790 )?;
6791 } else {
6792 engine.rms_norm(
6793 &ws.q_raw[rank],
6794 &q_norm[rank],
6795 &mut ws.q[rank],
6796 head_dim,
6797 local_heads,
6798 rms_eps,
6799 )?;
6800 engine.rms_norm(
6801 &ws.k_raw[rank],
6802 &k_norm[rank],
6803 &mut ws.k[rank],
6804 head_dim,
6805 local_kv_heads,
6806 rms_eps,
6807 )?;
6808 {
6809 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6810 engine
6811 .stream()
6812 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6813 }
6814 engine.rope_neox2(
6815 &mut ws.q[rank],
6816 &mut ws.k[rank],
6817 &ws.pos[rank],
6818 head_dim,
6819 n_rot,
6820 local_heads,
6821 local_kv_heads,
6822 1,
6823 rope_base,
6824 1.0,
6825 rope_freqs[rank],
6826 )?;
6827 }
6828 if gate_shards.is_none() {
6829 let gate_start = rank * (ws.heads / ranks);
6830 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6831 engine.stream().memcpy_dtod(
6832 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6833 &mut gate_dst,
6834 )?;
6835 }
6836 Ok(())
6837 }
6838
6839 pub(crate) fn decode_v2_finish_rank_partial(
6843 &self,
6844 ws: &mut StepTpDecodeV2Ws,
6845 o_m: &ResidentStepBf16RowParallel,
6846 o_fused: bool,
6847 rank: usize,
6848 ) -> Result<(), Box<dyn std::error::Error>> {
6849 let engine = &self.ranks[rank];
6850 let _main = engine.gpu.enter_main()?;
6851 if o_fused {
6852 let StepTpDecodeV2Ws {
6853 gated,
6854 o_partials,
6855 o_block_cols,
6856 o_out,
6857 ..
6858 } = &mut *ws;
6859 let all_f32 = o_m.ranks[rank]
6860 .iter()
6861 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6862 if all_f32 {
6863 let mut weights = Vec::with_capacity(4);
6864 for block in 0..4 {
6865 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6866 unreachable!("all_f32 checked above");
6867 };
6868 weights.push(weight);
6869 }
6870 engine.matvec_f32_b4_into(
6871 [weights[0], weights[1], weights[2], weights[3]],
6872 &gated[rank],
6873 &mut o_partials[rank][0],
6874 *o_block_cols,
6875 *o_out,
6876 )?;
6877 } else {
6878 let mut weights = Vec::with_capacity(4);
6879 for block in 0..4 {
6880 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6881 return Err("step TP decode v2 O projections mix residency classes".into());
6882 };
6883 weights.push(weight);
6884 }
6885 engine.matvec_bf16_b4_into(
6886 [weights[0], weights[1], weights[2], weights[3]],
6887 &gated[rank],
6888 &mut o_partials[rank][0],
6889 *o_block_cols,
6890 *o_out,
6891 )?;
6892 }
6893 } else {
6894 for block in 0..ws.blocks_per_rank {
6895 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6896 return Err("step TP decode v2 lost its F32 O residency".into());
6897 };
6898 let x =
6899 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
6900 let w = weight.slice(0..weight.len());
6901 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
6902 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
6903 }
6904 }
6905 Ok(())
6906 }
6907
6908 pub(crate) fn decode_v2_finish(
6916 &self,
6917 ws: &mut StepTpDecodeV2Ws,
6918 e: &Engine,
6919 o_m: &ResidentStepBf16RowParallel,
6920 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6921 let ranks = self.ranks.len();
6922 if e.ctx().ordinal() != ws.e_device {
6923 return Err("step TP decode v2 finish engine changed".into());
6924 }
6925 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
6930
6931 for rank in 0..ranks {
6934 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
6935 if rank == 0 {
6936 continue;
6939 }
6940 let engine = &self.ranks[rank];
6941 let _main = engine.gpu.enter_main()?;
6942 ws.ev_rank[rank].record(&engine.stream())?;
6943 }
6944
6945 let root = &self.ranks[0];
6947 #[allow(unused_assignments)]
6948 let mut final_in_a = false;
6949 {
6950 let _main = root.gpu.enter_main()?;
6951 for ev in ws.ev_rank.iter().skip(1) {
6952 root.stream().wait(ev)?;
6953 }
6954 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
6955 ws.ev_oproj.record(&root.stream())?;
6961 let _main = e.gpu.enter_main()?;
6962 e.stream().wait(&ws.ev_oproj)?;
6963 let mut output = e.uninit(ws.o_out)?;
6964 if oproj_tail_on() && oproj_tail_eligible() {
6965 use cudarc::driver::DevicePtr;
6968 let stream = e.stream();
6969 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
6970 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
6971 set_oproj_tail((p0 as u64, p1 as u64));
6972 return Ok(output);
6973 }
6974 e.add(
6975 &ws.o_partials[0][0],
6976 &ws.o_partials[1][0],
6977 &mut output,
6978 ws.o_out,
6979 )?;
6980 return Ok(output);
6981 }
6982 if o_fused {
6983 self.decode_v2_finish_root_fused(ws)?;
6984 ws.ev_oproj.record(&root.stream())?;
6985 let _main = e.gpu.enter_main()?;
6986 e.stream().wait(&ws.ev_oproj)?;
6987 let mut output = e.uninit(ws.o_out)?;
6988 e.stream().memcpy_dtod(
6989 &ws.reduce_a.slice(0..ws.o_out),
6990 &mut output.slice_mut(0..ws.o_out),
6991 )?;
6992 return Ok(output);
6993 }
6994 let mut first = true;
6995 let mut current_is_a = false;
6996 for rank in 0..ranks {
6997 for block in 0..ws.blocks_per_rank {
6998 let use_peer = rank != 0;
6999 if use_peer {
7000 root.stream()
7001 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
7002 }
7003 match (first, current_is_a, use_peer) {
7005 (true, _, true) => {
7006 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7007 }
7008 (true, _, false) => root.add(
7009 &ws.zeros,
7010 &ws.o_partials[0][block],
7011 &mut ws.reduce_a,
7012 ws.o_out,
7013 )?,
7014 (false, true, true) => {
7015 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7016 }
7017 (false, true, false) => root.add(
7018 &ws.reduce_a,
7019 &ws.o_partials[0][block],
7020 &mut ws.reduce_b,
7021 ws.o_out,
7022 )?,
7023 (false, false, true) => {
7024 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7025 }
7026 (false, false, false) => root.add(
7027 &ws.reduce_b,
7028 &ws.o_partials[0][block],
7029 &mut ws.reduce_a,
7030 ws.o_out,
7031 )?,
7032 }
7033 current_is_a = first || !current_is_a;
7034 first = false;
7035 }
7036 }
7037 final_in_a = current_is_a;
7038
7039 for rank in 0..ranks {
7040 let start = rank * ws.local_kv_dim;
7041 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
7042 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
7043 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
7044 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
7045 }
7046 ws.ev_oproj.record(&root.stream())?;
7047 }
7048
7049 let _main = e.gpu.enter_main()?;
7053 e.stream().wait(&ws.ev_oproj)?;
7054 let mut output = e.uninit(ws.o_out)?;
7055 let source = if final_in_a {
7056 &ws.reduce_a
7057 } else {
7058 &ws.reduce_b
7059 };
7060 e.stream().memcpy_dtod(
7061 &source.slice(0..ws.o_out),
7062 &mut output.slice_mut(0..ws.o_out),
7063 )?;
7064 Ok(output)
7065 }
7066
7067 pub fn run_routed_experts(
7068 &self,
7069 experts: &ResidentExpertParallel,
7070 input: &[f32],
7071 tokens: usize,
7072 selected: &[usize],
7073 route_weights: &[f32],
7074 experts_per_token: usize,
7075 activation_limit: Option<f32>,
7076 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7077 validate_step_expert_activation_limit(activation_limit)?;
7078 validate_ep_residency(&self.ranks, experts)?;
7079 validate_activations(input, tokens, experts.input_width)?;
7080 let pairs = tokens
7081 .checked_mul(experts_per_token)
7082 .ok_or("EP route count overflow")?;
7083 if selected.len() != pairs || route_weights.len() != pairs {
7084 return Err(format!(
7085 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7086 {experts_per_token} ({pairs})",
7087 selected.len(),
7088 route_weights.len(),
7089 )
7090 .into());
7091 }
7092 if !route_weights.iter().all(|weight| weight.is_finite()) {
7093 return Err("EP route weights contain a non-finite value".into());
7094 }
7095 if self.native_p2p {
7096 return self.run_routed_experts_native(
7097 experts,
7098 input,
7099 tokens,
7100 selected,
7101 route_weights,
7102 experts_per_token,
7103 activation_limit,
7104 );
7105 }
7106
7107 let mut output = vec![0.0f32; tokens * experts.input_width];
7108 let per_rank = experts.expert_count / experts.ranks.len();
7109 for token in 0..tokens {
7110 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7111 for slot in 0..experts_per_token {
7112 let pair = token * experts_per_token + slot;
7113 let expert = selected[pair];
7114 if expert >= experts.expert_count {
7115 return Err(format!(
7116 "EP selected expert {expert} outside 0..{}",
7117 experts.expert_count
7118 )
7119 .into());
7120 }
7121 let owner = expert / per_rank;
7122 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7123 let rank = &experts.ranks[owner];
7124 let engine = &self.ranks[owner];
7125 let gate =
7126 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7127 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7128 let activated: Vec<f32> = gate
7129 .iter()
7130 .zip(&up)
7131 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7132 .collect();
7133 debug_assert_eq!(activated.len(), experts.expert_width);
7134 let down =
7135 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7136 let weight = route_weights[pair];
7137 for (sum, value) in output
7138 [token * experts.input_width..(token + 1) * experts.input_width]
7139 .iter_mut()
7140 .zip(down)
7141 {
7142 *sum += weight * value;
7143 }
7144 }
7145 }
7146 Ok(output)
7147 }
7148
7149 fn run_routed_experts_native(
7150 &self,
7151 experts: &ResidentExpertParallel,
7152 input: &[f32],
7153 tokens: usize,
7154 selected: &[usize],
7155 route_weights: &[f32],
7156 experts_per_token: usize,
7157 activation_limit: Option<f32>,
7158 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7159 if !self.native_p2p || self.ranks.len() < 2 {
7160 return Err("native EP execution requires at least two P2P ranks".into());
7161 }
7162 if self.ep_device_arithmetic {
7163 return self.run_routed_experts_native_device(
7164 experts,
7165 input,
7166 tokens,
7167 selected,
7168 route_weights,
7169 experts_per_token,
7170 activation_limit,
7171 );
7172 }
7173 let mut output = vec![0.0f32; tokens * experts.input_width];
7174 let per_rank = experts.expert_count / experts.ranks.len();
7175 for token in 0..tokens {
7176 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7177 let mut rank_inputs = (0..self.ranks.len())
7178 .map(|_| None)
7179 .collect::<Vec<Option<CudaSlice<f32>>>>();
7180 rank_inputs[0] = Some({
7181 let root = &self.ranks[0];
7182 let _main = root.gpu.enter_main()?;
7183 root.htod(input_row)?
7184 });
7185
7186 for slot in 0..experts_per_token {
7187 let pair = token * experts_per_token + slot;
7188 let expert = selected[pair];
7189 if expert >= experts.expert_count {
7190 return Err(format!(
7191 "EP selected expert {expert} outside 0..{}",
7192 experts.expert_count
7193 )
7194 .into());
7195 }
7196 let owner = expert / per_rank;
7197 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7198 if rank_inputs[owner].is_none() {
7199 let peer_input = {
7200 let root_input = rank_inputs[0]
7201 .as_ref()
7202 .ok_or("native EP lost its root input")?;
7203 let engine = &self.ranks[owner];
7204 let _main = engine.gpu.enter_main()?;
7205 let mut peer_input = engine.uninit(experts.input_width)?;
7206 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7207 peer_input
7208 };
7209 rank_inputs[owner] = Some(peer_input);
7210 }
7211
7212 let rank = &experts.ranks[owner];
7213 let engine = &self.ranks[owner];
7214 let owner_input = rank_inputs[owner]
7215 .as_ref()
7216 .ok_or("native EP owner input is absent after dispatch")?;
7217 let gate = run_resident_bank_expert_device(
7218 engine,
7219 &rank.gate,
7220 local_expert,
7221 owner_input,
7222 1,
7223 )?;
7224 let up = run_resident_bank_expert_device(
7225 engine,
7226 &rank.up,
7227 local_expert,
7228 owner_input,
7229 1,
7230 )?;
7231 let (gate, up) = {
7232 let _main = engine.gpu.enter_main()?;
7233 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
7234 };
7235 let activated = gate
7236 .iter()
7237 .zip(&up)
7238 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7239 .collect::<Vec<_>>();
7240 debug_assert_eq!(activated.len(), experts.expert_width);
7241 let activated = {
7242 let _main = engine.gpu.enter_main()?;
7243 engine.htod(&activated)?
7244 };
7245 let down = run_resident_bank_expert_device(
7246 engine,
7247 &rank.down,
7248 local_expert,
7249 &activated,
7250 1,
7251 )?;
7252 let down = if owner == 0 {
7253 let _main = engine.gpu.enter_main()?;
7254 engine.dtoh(&down)?
7255 } else {
7256 let root = &self.ranks[0];
7257 let _main = root.gpu.enter_main()?;
7258 let mut root_down = root.uninit(experts.input_width)?;
7259 root.stream().memcpy_dtod(&down, &mut root_down)?;
7260 root.dtoh(&root_down)?
7261 };
7262 let weight = route_weights[pair];
7263 for (sum, value) in output
7264 [token * experts.input_width..(token + 1) * experts.input_width]
7265 .iter_mut()
7266 .zip(down)
7267 {
7268 *sum += weight * value;
7269 }
7270 }
7271 }
7272 Ok(output)
7273 }
7274
7275 fn run_routed_experts_native_device(
7276 &self,
7277 experts: &ResidentExpertParallel,
7278 input: &[f32],
7279 tokens: usize,
7280 selected: &[usize],
7281 route_weights: &[f32],
7282 experts_per_token: usize,
7283 activation_limit: Option<f32>,
7284 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7285 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
7286 return Err(
7287 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
7288 );
7289 }
7290 let mut output = Vec::with_capacity(tokens * experts.input_width);
7291 let per_rank = experts.expert_count / experts.ranks.len();
7292 let root = &self.ranks[0];
7293 for token in 0..tokens {
7294 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7295 let mut rank_inputs = (0..self.ranks.len())
7296 .map(|_| None)
7297 .collect::<Vec<Option<CudaSlice<f32>>>>();
7298 rank_inputs[0] = Some({
7299 let _main = root.gpu.enter_main()?;
7300 root.htod(input_row)?
7301 });
7302 let mut root_output = {
7303 let _main = root.gpu.enter_main()?;
7304 root.zeros(experts.input_width)?
7305 };
7306 let mut remote_down_keepalive = Vec::new();
7307
7308 for slot in 0..experts_per_token {
7309 let pair = token * experts_per_token + slot;
7310 let expert = selected[pair];
7311 if expert >= experts.expert_count {
7312 return Err(format!(
7313 "EP selected expert {expert} outside 0..{}",
7314 experts.expert_count
7315 )
7316 .into());
7317 }
7318 let owner = expert / per_rank;
7319 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7320 if rank_inputs[owner].is_none() {
7321 let peer_input = {
7322 let root_input = rank_inputs[0]
7323 .as_ref()
7324 .ok_or("native EP lost its root input")?;
7325 let engine = &self.ranks[owner];
7326 let _main = engine.gpu.enter_main()?;
7327 let mut peer_input = engine.uninit(experts.input_width)?;
7328 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7329 peer_input
7330 };
7331 rank_inputs[owner] = Some(peer_input);
7332 }
7333
7334 let rank = &experts.ranks[owner];
7335 let engine = &self.ranks[owner];
7336 let owner_input = rank_inputs[owner]
7337 .as_ref()
7338 .ok_or("native EP owner input is absent after dispatch")?;
7339 let gate = run_resident_bank_expert_device(
7340 engine,
7341 &rank.gate,
7342 local_expert,
7343 owner_input,
7344 1,
7345 )?;
7346 let up = run_resident_bank_expert_device(
7347 engine,
7348 &rank.up,
7349 local_expert,
7350 owner_input,
7351 1,
7352 )?;
7353 let activated = {
7354 let _main = engine.gpu.enter_main()?;
7355 let mut activated = engine.uninit(experts.expert_width)?;
7356 if let Some(limit) = activation_limit {
7357 engine.silu_clamped_mul_host_expf(
7358 &gate,
7359 &up,
7360 limit,
7361 &mut activated,
7362 experts.expert_width,
7363 )?;
7364 } else {
7365 engine.silu_mul_host_expf(
7366 &gate,
7367 &up,
7368 &mut activated,
7369 experts.expert_width,
7370 )?;
7371 }
7372 activated
7373 };
7374 let down = run_resident_bank_expert_device(
7375 engine,
7376 &rank.down,
7377 local_expert,
7378 &activated,
7379 1,
7380 )?;
7381 let root_down = if owner == 0 {
7382 down
7383 } else {
7384 let _main = root.gpu.enter_main()?;
7385 let mut root_down = root.uninit(experts.input_width)?;
7386 root.stream().memcpy_dtod(&down, &mut root_down)?;
7387 remote_down_keepalive.push(down);
7391 root_down
7392 };
7393 let _main = root.gpu.enter_main()?;
7394 let mut destination = root_output.slice_mut(0..experts.input_width);
7395 root.axpy_host_into(
7396 &root_down.slice(0..root_down.len()),
7397 route_weights[pair],
7398 &mut destination,
7399 experts.input_width,
7400 )?;
7401 }
7402
7403 let _main = root.gpu.enter_main()?;
7404 let root_output = root.dtoh(&root_output)?;
7405 drop(remote_down_keepalive);
7406 output.extend(root_output);
7407 }
7408 Ok(output)
7409 }
7410}
7411
7412fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7413 if matrix.out_features % tp != 0 {
7414 return Err(format!(
7415 "column-parallel out_features {} is not divisible by TP={tp}",
7416 matrix.out_features
7417 ));
7418 }
7419 let local_out = matrix.out_features / tp;
7420 if local_out % FP8_BLOCK != 0 {
7421 return Err(format!(
7422 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7423 E4M3 scale block"
7424 ));
7425 }
7426 Ok(())
7427}
7428
7429fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7430 if !matches!(tp, 1 | 2 | 4 | 8) {
7431 return Err(format!(
7432 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7433 ));
7434 }
7435 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7436 return Err(format!(
7437 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7438 ));
7439 }
7440 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7441 let local_out = out_features / tp;
7442 if local_out % canonical_rows != 0 {
7443 return Err(format!(
7444 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7445 {canonical_rows}-row chunks"
7446 ));
7447 }
7448 Ok(canonical_rows)
7449}
7450
7451fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7452 if !matches!(tp, 1 | 2 | 4 | 8) {
7453 return Err(format!(
7454 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7455 ));
7456 }
7457 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7458 return Err(format!(
7459 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7460 ));
7461 }
7462 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7463 let local_in = in_features / tp;
7464 if local_in % canonical_cols != 0 {
7465 return Err(format!(
7466 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7467 {canonical_cols}-column chunks"
7468 ));
7469 }
7470 Ok(canonical_cols)
7471}
7472
7473fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7474 if matrix.in_features % tp != 0 {
7475 return Err(format!(
7476 "row-parallel in_features {} is not divisible by TP={tp}",
7477 matrix.in_features
7478 ));
7479 }
7480 let local_in = matrix.in_features / tp;
7481 if local_in % FP8_BLOCK != 0 {
7482 return Err(format!(
7483 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7484 E4M3 scale block"
7485 ));
7486 }
7487 Ok(())
7488}
7489
7490fn upload_rank(
7491 engine: &Engine,
7492 matrix: E4m3BlockMatrix<'_>,
7493) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7494 let _main = engine.gpu.enter_main()?;
7495 matrix.validate()?;
7496 Ok(ResidentE4m3Rank {
7497 codes: engine.htod_bytes(matrix.codes)?,
7498 scales: engine.htod(matrix.scales)?,
7499 out_features: matrix.out_features,
7500 in_features: matrix.in_features,
7501 })
7502}
7503
7504fn upload_bf16_rank(
7505 engine: &Engine,
7506 matrix: Bf16Matrix<'_>,
7507 f32_mirror: bool,
7508) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7509 let _main = engine.gpu.enter_main()?;
7510 matrix.validate()?;
7511 let bytes = engine.htod_bytes(matrix.bytes)?;
7512 let weight = if f32_mirror {
7513 let values = matrix
7514 .out_features
7515 .checked_mul(matrix.in_features)
7516 .ok_or("resident BF16 mirror element count overflow")?;
7517 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7518 } else {
7519 ResidentBf16Weight::Bf16(bytes)
7520 };
7521 Ok(ResidentBf16Rank {
7522 weight,
7523 out_features: matrix.out_features,
7524 in_features: matrix.in_features,
7525 })
7526}
7527
7528fn upload_expert_bank_rank(
7529 engine: &Engine,
7530 bank: E4m3ExpertBank<'_>,
7531 expert_range: Range<usize>,
7532) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7533 let _main = engine.gpu.enter_main()?;
7534 bank.validate()?;
7535 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7536 return Err(format!(
7537 "invalid EP expert range {expert_range:?} for {} experts",
7538 bank.expert_count
7539 )
7540 .into());
7541 }
7542 let code_stride = bank.out_features * bank.in_features;
7543 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7544 Ok(ResidentE4m3ExpertBankRank {
7545 codes: engine.htod_bytes(
7546 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7547 )?,
7548 scales: engine.htod(
7549 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7550 )?,
7551 expert_range,
7552 out_features: bank.out_features,
7553 in_features: bank.in_features,
7554 code_stride,
7555 scale_stride,
7556 k_blocks: None,
7557 })
7558}
7559
7560fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7561 if bank.out_features % tp != 0 {
7562 return Err(format!(
7563 "TP expert output width {} is not divisible by TP={tp}",
7564 bank.out_features
7565 ));
7566 }
7567 let local_out = bank.out_features / tp;
7568 if local_out % FP8_BLOCK != 0 {
7569 return Err(format!(
7570 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7571 ));
7572 }
7573 Ok(())
7574}
7575
7576fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7577 if bank.in_features % tp != 0 {
7578 return Err(format!(
7579 "TP expert input width {} is not divisible by TP={tp}",
7580 bank.in_features
7581 ));
7582 }
7583 let local_in = bank.in_features / tp;
7584 if local_in % FP8_BLOCK != 0 {
7585 return Err(format!(
7586 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7587 ));
7588 }
7589 Ok(())
7590}
7591
7592fn upload_column_bank_rank(
7593 engine: &Engine,
7594 bank: E4m3ExpertBank<'_>,
7595 tp: usize,
7596 rank: usize,
7597) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7598 let _main = engine.gpu.enter_main()?;
7599 let packed = pack_column_bank_rank(bank, tp, rank)?;
7600 Ok(ResidentE4m3ExpertBankRank {
7601 codes: engine.htod_bytes(&packed.codes)?,
7602 scales: engine.htod(&packed.scales)?,
7603 expert_range: packed.expert_range,
7604 out_features: packed.out_features,
7605 in_features: packed.in_features,
7606 code_stride: packed.code_stride,
7607 scale_stride: packed.scale_stride,
7608 k_blocks: packed.k_blocks,
7609 })
7610}
7611
7612fn pack_column_bank_rank(
7613 bank: E4m3ExpertBank<'_>,
7614 tp: usize,
7615 rank: usize,
7616) -> Result<PackedE4m3ExpertBankRank, String> {
7617 bank.validate()?;
7618 validate_column_bank_shape(bank, tp)?;
7619 if rank >= tp {
7620 return Err(format!("TP rank {rank} outside 0..{tp}"));
7621 }
7622 let local_out = bank.out_features / tp;
7623 let full_code_stride = bank.out_features * bank.in_features;
7624 let local_code_stride = local_out * bank.in_features;
7625 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7626 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7627 let local_scale_rows = local_out / FP8_BLOCK;
7628 let local_scale_stride = local_scale_rows * scale_cols;
7629 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7630 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7631 let row_start = rank * local_out;
7632 let scale_row_start = rank * local_scale_rows;
7633 for expert in 0..bank.expert_count {
7634 let code_start = expert * full_code_stride + row_start * bank.in_features;
7635 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7636 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7637 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7638 }
7639 Ok(PackedE4m3ExpertBankRank {
7640 codes,
7641 scales,
7642 expert_range: 0..bank.expert_count,
7643 out_features: local_out,
7644 in_features: bank.in_features,
7645 code_stride: local_code_stride,
7646 scale_stride: local_scale_stride,
7647 k_blocks: None,
7648 })
7649}
7650
7651fn upload_row_bank_rank(
7652 engine: &Engine,
7653 bank: E4m3ExpertBank<'_>,
7654 tp: usize,
7655 rank: usize,
7656) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7657 let _main = engine.gpu.enter_main()?;
7658 let packed = pack_row_bank_rank(bank, tp, rank)?;
7659 Ok(ResidentE4m3ExpertBankRank {
7660 codes: engine.htod_bytes(&packed.codes)?,
7661 scales: engine.htod(&packed.scales)?,
7662 expert_range: packed.expert_range,
7663 out_features: packed.out_features,
7664 in_features: packed.in_features,
7665 code_stride: packed.code_stride,
7666 scale_stride: packed.scale_stride,
7667 k_blocks: packed.k_blocks,
7668 })
7669}
7670
7671fn pack_row_bank_rank(
7672 bank: E4m3ExpertBank<'_>,
7673 tp: usize,
7674 rank: usize,
7675) -> Result<PackedE4m3ExpertBankRank, String> {
7676 bank.validate()?;
7677 validate_row_bank_shape(bank, tp)?;
7678 if rank >= tp {
7679 return Err(format!("TP rank {rank} outside 0..{tp}"));
7680 }
7681 let local_in = bank.in_features / tp;
7682 let full_code_stride = bank.out_features * bank.in_features;
7683 let local_code_stride = bank.out_features * local_in;
7684 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7685 let local_scale_cols = local_in / FP8_BLOCK;
7686 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7687 let full_scale_stride = scale_rows * full_scale_cols;
7688 let local_scale_stride = scale_rows * local_scale_cols;
7689 let global_block_start = rank * local_scale_cols;
7690 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7691 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7692 for expert in 0..bank.expert_count {
7693 let expert_code_start = expert * full_code_stride;
7694 let expert_scale_start = expert * full_scale_stride;
7695 for local_block in 0..local_scale_cols {
7696 let global_block = global_block_start + local_block;
7697 let column_start = global_block * FP8_BLOCK;
7698 for row in 0..bank.out_features {
7699 let start = expert_code_start + row * bank.in_features + column_start;
7700 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7701 }
7702 for row in 0..scale_rows {
7703 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7704 }
7705 }
7706 }
7707 Ok(PackedE4m3ExpertBankRank {
7708 codes,
7709 scales,
7710 expert_range: 0..bank.expert_count,
7711 out_features: bank.out_features,
7712 in_features: local_in,
7713 code_stride: local_code_stride,
7714 scale_stride: local_scale_stride,
7715 k_blocks: Some(local_scale_cols),
7716 })
7717}
7718
7719fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7720 if engines.len() != ranks.len() {
7721 return Err(format!(
7722 "resident TP rank count {} != runtime rank count {}",
7723 ranks.len(),
7724 engines.len()
7725 ));
7726 }
7727 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7728 let device = engine.ctx().ordinal();
7729 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7730 return Err(format!(
7731 "resident TP rank {rank} is not owned by runtime device {device}"
7732 ));
7733 }
7734 }
7735 Ok(())
7736}
7737
7738fn validate_tp_bank_residency(
7739 engines: &[Engine],
7740 experts: &ResidentTpExpertBank,
7741) -> Result<(), String> {
7742 if engines.len() != experts.gate.len()
7743 || engines.len() != experts.up.len()
7744 || engines.len() != experts.down.len()
7745 {
7746 return Err(format!(
7747 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7748 experts.gate.len(),
7749 experts.up.len(),
7750 experts.down.len(),
7751 engines.len()
7752 ));
7753 }
7754 for (rank, engine) in engines.iter().enumerate() {
7755 let device = engine.ctx().ordinal();
7756 for (projection, bank) in [
7757 ("gate", &experts.gate[rank]),
7758 ("up", &experts.up[rank]),
7759 ("down", &experts.down[rank]),
7760 ] {
7761 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7762 return Err(format!(
7763 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7764 {device}"
7765 ));
7766 }
7767 }
7768 }
7769 Ok(())
7770}
7771
7772fn validate_ep_residency(
7773 engines: &[Engine],
7774 experts: &ResidentExpertParallel,
7775) -> Result<(), String> {
7776 if engines.len() != experts.ranks.len() {
7777 return Err(format!(
7778 "resident EP rank count {} != runtime rank count {}",
7779 experts.ranks.len(),
7780 engines.len()
7781 ));
7782 }
7783 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7784 let device = engine.ctx().ordinal();
7785 for (projection, bank) in [
7786 ("gate", &resident.gate),
7787 ("up", &resident.up),
7788 ("down", &resident.down),
7789 ] {
7790 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7791 return Err(format!(
7792 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7793 {device}"
7794 ));
7795 }
7796 }
7797 }
7798 Ok(())
7799}
7800
7801fn run_rank(
7802 engine: &Engine,
7803 matrix: E4m3BlockMatrix<'_>,
7804 activations: &[f32],
7805 tokens: usize,
7806) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7807 let _main = engine.gpu.enter_main()?;
7808 let codes = engine.htod_bytes(matrix.codes)?;
7809 let scales = engine.htod(matrix.scales)?;
7810 let activations = engine.htod(activations)?;
7811 let output = engine.qmatvec_mmq_fp8_blk(
7812 &codes,
7813 &scales,
7814 &activations,
7815 tokens,
7816 matrix.in_features,
7817 matrix.out_features,
7818 )?;
7819 engine.dtoh(&output)
7820}
7821
7822fn run_resident_rank(
7823 engine: &Engine,
7824 matrix: &ResidentE4m3Rank,
7825 activations: &[f32],
7826 tokens: usize,
7827) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7828 let _main = engine.gpu.enter_main()?;
7829 let activations = engine.htod(activations)?;
7830 let output = engine.qmatvec_mmq_fp8_blk(
7831 &matrix.codes,
7832 &matrix.scales,
7833 &activations,
7834 tokens,
7835 matrix.in_features,
7836 matrix.out_features,
7837 )?;
7838 engine.dtoh(&output)
7839}
7840
7841fn run_resident_bf16_rank(
7842 engine: &Engine,
7843 matrix: &ResidentBf16Rank,
7844 activations: &[f32],
7845 tokens: usize,
7846 canonical_chunk_rows: Option<usize>,
7847) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7848 let _main = engine.gpu.enter_main()?;
7849 let activations = engine.htod(activations)?;
7850 let output = run_resident_bf16_rank_device(
7851 engine,
7852 matrix,
7853 &activations,
7854 tokens,
7855 canonical_chunk_rows,
7856 false,
7857 )?;
7858 engine.dtoh(&output)
7859}
7860
7861fn run_resident_bf16_rank_device(
7862 engine: &Engine,
7863 matrix: &ResidentBf16Rank,
7864 activations: &CudaSlice<f32>,
7865 tokens: usize,
7866 canonical_chunk_rows: Option<usize>,
7867 strided_chunk_output: bool,
7868) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7869 let _main = engine.gpu.enter_main()?;
7870 if activations.ordinal() != engine.ctx().ordinal() {
7871 return Err(format!(
7872 "resident BF16 activation device {} != rank device {}",
7873 activations.ordinal(),
7874 engine.ctx().ordinal()
7875 )
7876 .into());
7877 }
7878 if activations.len() != tokens * matrix.in_features {
7879 return Err(format!(
7880 "resident BF16 activation count {} != {tokens}x{}",
7881 activations.len(),
7882 matrix.in_features
7883 )
7884 .into());
7885 }
7886 match (&matrix.weight, canonical_chunk_rows) {
7887 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
7888 .linear_bf16_resident_canonical_rows(
7889 activations,
7890 bytes,
7891 tokens,
7892 matrix.in_features,
7893 matrix.out_features,
7894 rows,
7895 ),
7896 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
7897 activations,
7898 bytes,
7899 tokens,
7900 matrix.in_features,
7901 matrix.out_features,
7902 ),
7903 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
7904 .linear_f32_resident_canonical_rows_strided(
7905 activations,
7906 values,
7907 tokens,
7908 matrix.in_features,
7909 matrix.out_features,
7910 rows,
7911 ),
7912 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
7913 activations,
7914 values,
7915 tokens,
7916 matrix.in_features,
7917 matrix.out_features,
7918 rows,
7919 ),
7920 (ResidentBf16Weight::F32(values), None) => engine.linear(
7921 activations,
7922 values,
7923 tokens,
7924 matrix.in_features,
7925 matrix.out_features,
7926 ),
7927 }
7928}
7929
7930fn validate_resident_bf16_ranks(
7931 engines: &[Engine],
7932 ranks: &[ResidentBf16Rank],
7933) -> Result<(), String> {
7934 if engines.len() != ranks.len() {
7935 return Err(format!(
7936 "resident BF16 TP rank count {} != runtime rank count {}",
7937 ranks.len(),
7938 engines.len(),
7939 ));
7940 }
7941 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7942 let device = engine.ctx().ordinal();
7943 if matrix.weight.ordinal() != device {
7944 return Err(format!(
7945 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
7946 ));
7947 }
7948 }
7949 Ok(())
7950}
7951
7952fn validate_step_bf16_row_residency(
7953 engines: &[Engine],
7954 matrix: &ResidentStepBf16RowParallel,
7955) -> Result<(), String> {
7956 if engines.len() != matrix.ranks.len() {
7957 return Err(format!(
7958 "resident Step BF16 row rank count {} != runtime rank count {}",
7959 matrix.ranks.len(),
7960 engines.len(),
7961 ));
7962 }
7963 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
7964 if matrix.canonical_chunk_cols != canonical_cols {
7965 return Err(format!(
7966 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
7967 matrix.canonical_chunk_cols
7968 ));
7969 }
7970 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
7971 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
7972 if blocks.len() != blocks_per_rank {
7973 return Err(format!(
7974 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
7975 blocks.len()
7976 ));
7977 }
7978 let device = engine.ctx().ordinal();
7979 for (block, resident) in blocks.iter().enumerate() {
7980 if resident.weight.ordinal() != device
7981 || resident.in_features != canonical_cols
7982 || resident.out_features != matrix.out_features
7983 {
7984 return Err(format!(
7985 "resident Step BF16 row rank {rank} block {block} has inconsistent \
7986 device or geometry"
7987 ));
7988 }
7989 }
7990 }
7991 Ok(())
7992}
7993
7994fn validate_replicated_device_rows(
7995 engines: &[Engine],
7996 rows: &ResidentReplicatedDeviceRows,
7997) -> Result<(), String> {
7998 let rank_lengths = rows
7999 .ranks
8000 .iter()
8001 .map(|rank_rows| rank_rows.len())
8002 .collect::<Vec<_>>();
8003 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8004 if rows
8005 .ranks
8006 .iter()
8007 .zip(engines)
8008 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8009 {
8010 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8011 }
8012 Ok(())
8013}
8014
8015fn replicated_device_row_values(
8016 tokens: usize,
8017 width: usize,
8018 expected_ranks: usize,
8019 rank_lengths: &[usize],
8020) -> Result<usize, String> {
8021 let values = tokens
8022 .checked_mul(width)
8023 .ok_or("replicated device row size overflow")?;
8024 if tokens == 0
8025 || width == 0
8026 || expected_ranks == 0
8027 || rank_lengths.len() != expected_ranks
8028 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8029 {
8030 return Err(format!(
8031 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8032 tokens,
8033 width,
8034 rank_lengths.len(),
8035 expected_ranks
8036 ));
8037 }
8038 Ok(values)
8039}
8040
8041fn replicated_device_row_source_values(
8042 tokens: usize,
8043 width: usize,
8044 source_len: usize,
8045 source_device: usize,
8046 root_device: usize,
8047) -> Result<usize, String> {
8048 let values = tokens
8049 .checked_mul(width)
8050 .ok_or("replicated device row size overflow")?;
8051 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8052 return Err(format!(
8053 "replicated device row source has inconsistent geometry/device \
8054 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8055 ));
8056 }
8057 Ok(values)
8058}
8059
8060fn bf16_column_shard(
8061 matrix: Bf16Matrix<'_>,
8062 tp: usize,
8063 rank: usize,
8064) -> Result<Bf16Matrix<'_>, String> {
8065 matrix.validate()?;
8066 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8067 return Err(format!(
8068 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8069 matrix.out_features
8070 ));
8071 }
8072 let local_out = matrix.out_features / tp;
8073 let row_bytes = matrix.in_features * 2;
8074 let start = rank * local_out * row_bytes;
8075 Ok(Bf16Matrix {
8076 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8077 out_features: local_out,
8078 in_features: matrix.in_features,
8079 })
8080}
8081
8082fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8083 matrix.validate()?;
8084 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8085 return Err(format!(
8086 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8087 matrix.in_features
8088 ));
8089 }
8090 let local_in = matrix.in_features / tp;
8091 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8092 for row in 0..matrix.out_features {
8093 let start = (row * matrix.in_features + rank * local_in) * 2;
8094 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8095 }
8096 Ok(bytes)
8097}
8098
8099fn bf16_row_block(
8100 matrix: Bf16Matrix<'_>,
8101 col_start: usize,
8102 block_cols: usize,
8103) -> Result<Vec<u8>, String> {
8104 matrix.validate()?;
8105 let col_end = col_start
8106 .checked_add(block_cols)
8107 .ok_or("BF16 row block column overflow")?;
8108 if block_cols == 0 || col_end > matrix.in_features {
8109 return Err(format!(
8110 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8111 matrix.in_features
8112 ));
8113 }
8114 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8115 for row in 0..matrix.out_features {
8116 let start = (row * matrix.in_features + col_start) * 2;
8117 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8118 }
8119 Ok(bytes)
8120}
8121
8122fn run_resident_bank_expert(
8123 engine: &Engine,
8124 bank: &ResidentE4m3ExpertBankRank,
8125 local_expert: usize,
8126 activations: &[f32],
8127 tokens: usize,
8128) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8129 let _main = engine.gpu.enter_main()?;
8130 if bank.k_blocks.is_some() {
8131 return Err("block-major TP row bank requires canonical block execution".into());
8132 }
8133 let local_count = bank.expert_range.end - bank.expert_range.start;
8134 if local_expert >= local_count {
8135 return Err(format!(
8136 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8137 bank.expert_range
8138 )
8139 .into());
8140 }
8141 validate_activations(activations, tokens, bank.in_features)?;
8142 let activations = engine.htod(activations)?;
8143 let weight = bank
8144 .codes
8145 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8146 let scales = bank
8147 .scales
8148 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8149 let input = activations.slice(0..activations.len());
8150 let output = engine.qmatvec_mmq_fp8_blk_view(
8151 &weight,
8152 &scales,
8153 &input,
8154 tokens,
8155 bank.in_features,
8156 bank.out_features,
8157 )?;
8158 engine.dtoh(&output)
8159}
8160
8161fn run_resident_bank_expert_block(
8162 engine: &Engine,
8163 bank: &ResidentE4m3ExpertBankRank,
8164 local_expert: usize,
8165 block: usize,
8166 activations: &[f32],
8167) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8168 let _main = engine.gpu.enter_main()?;
8169 let local_count = bank.expert_range.end - bank.expert_range.start;
8170 if local_expert >= local_count {
8171 return Err(format!(
8172 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8173 bank.expert_range
8174 )
8175 .into());
8176 }
8177 let blocks = bank
8178 .k_blocks
8179 .ok_or("TP row bank is not packed in native K-block order")?;
8180 if block >= blocks {
8181 return Err(format!("TP row block {block} outside 0..{blocks}").into());
8182 }
8183 validate_activations(activations, 1, FP8_BLOCK)?;
8184 let block_code_stride = bank.out_features * FP8_BLOCK;
8185 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8186 if bank.in_features != blocks * FP8_BLOCK
8187 || bank.code_stride != blocks * block_code_stride
8188 || bank.scale_stride != blocks * block_scale_stride
8189 {
8190 return Err("TP row bank block-major geometry is inconsistent".into());
8191 }
8192
8193 let expert_code_start = local_expert * bank.code_stride;
8194 let expert_scale_start = local_expert * bank.scale_stride;
8195 let weight = bank.codes.slice(
8196 expert_code_start + block * block_code_stride
8197 ..expert_code_start + (block + 1) * block_code_stride,
8198 );
8199 let scales = bank.scales.slice(
8200 expert_scale_start + block * block_scale_stride
8201 ..expert_scale_start + (block + 1) * block_scale_stride,
8202 );
8203 let activations = engine.htod(activations)?;
8204 let input = activations.slice(0..activations.len());
8205 let output = engine.qmatvec_mmq_fp8_blk_view(
8206 &weight,
8207 &scales,
8208 &input,
8209 1,
8210 FP8_BLOCK,
8211 bank.out_features,
8212 )?;
8213 engine.dtoh(&output)
8214}
8215
8216fn run_resident_bank_expert_device(
8217 engine: &Engine,
8218 bank: &ResidentE4m3ExpertBankRank,
8219 local_expert: usize,
8220 activations: &CudaSlice<f32>,
8221 tokens: usize,
8222) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8223 let _main = engine.gpu.enter_main()?;
8224 if bank.k_blocks.is_some() {
8225 return Err("block-major TP row bank requires canonical block execution".into());
8226 }
8227 let local_count = bank.expert_range.end - bank.expert_range.start;
8228 if local_expert >= local_count {
8229 return Err(format!(
8230 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8231 bank.expert_range
8232 )
8233 .into());
8234 }
8235 let expected = tokens
8236 .checked_mul(bank.in_features)
8237 .ok_or("native TP activation size overflow")?;
8238 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
8239 return Err(format!(
8240 "native TP activation len/device {}/{} != expected {expected}/{}",
8241 activations.len(),
8242 activations.ordinal(),
8243 engine.ctx().ordinal()
8244 )
8245 .into());
8246 }
8247 let weight = bank
8248 .codes
8249 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8250 let scales = bank
8251 .scales
8252 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8253 let input = activations.slice(0..activations.len());
8254 engine.qmatvec_mmq_fp8_blk_view(
8255 &weight,
8256 &scales,
8257 &input,
8258 tokens,
8259 bank.in_features,
8260 bank.out_features,
8261 )
8262}
8263
8264fn run_resident_bank_expert_block_device(
8265 engine: &Engine,
8266 bank: &ResidentE4m3ExpertBankRank,
8267 local_expert: usize,
8268 block: usize,
8269 activations: &cudarc::driver::CudaView<'_, f32>,
8270) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8271 let _main = engine.gpu.enter_main()?;
8272 let local_count = bank.expert_range.end - bank.expert_range.start;
8273 if local_expert >= local_count {
8274 return Err(format!(
8275 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8276 bank.expert_range
8277 )
8278 .into());
8279 }
8280 let blocks = bank
8281 .k_blocks
8282 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
8283 if block >= blocks {
8284 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
8285 }
8286 let activation_device = activations.stream().context().ordinal();
8287 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
8288 return Err(format!(
8289 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
8290 activations.len(),
8291 activation_device,
8292 engine.ctx().ordinal()
8293 )
8294 .into());
8295 }
8296 let block_code_stride = bank.out_features * FP8_BLOCK;
8297 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8298 if bank.in_features != blocks * FP8_BLOCK
8299 || bank.code_stride != blocks * block_code_stride
8300 || bank.scale_stride != blocks * block_scale_stride
8301 {
8302 return Err("native TP row bank block-major geometry is inconsistent".into());
8303 }
8304 let expert_code_start = local_expert * bank.code_stride;
8305 let expert_scale_start = local_expert * bank.scale_stride;
8306 let weight = bank.codes.slice(
8307 expert_code_start + block * block_code_stride
8308 ..expert_code_start + (block + 1) * block_code_stride,
8309 );
8310 let scales = bank.scales.slice(
8311 expert_scale_start + block * block_scale_stride
8312 ..expert_scale_start + (block + 1) * block_scale_stride,
8313 );
8314 engine.qmatvec_mmq_fp8_blk_view(
8315 &weight,
8316 &scales,
8317 activations,
8318 1,
8319 FP8_BLOCK,
8320 bank.out_features,
8321 )
8322}
8323
8324fn configure_native_p2p(
8325 ranks: &[Engine],
8326 devices: &[usize],
8327) -> Result<(), Box<dyn std::error::Error>> {
8328 if ranks.len() != devices.len() || ranks.len() < 2 {
8329 return Err("native TP P2P setup requires matching multi-rank devices".into());
8330 }
8331 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8332 if engine.ctx().ordinal() != device {
8333 return Err(format!(
8334 "native TP rank {rank} context device {} != requested device {device}",
8335 engine.ctx().ordinal()
8336 )
8337 .into());
8338 }
8339 }
8340
8341 for src in 0..ranks.len() {
8342 for dst in 0..ranks.len() {
8343 if src == dst {
8344 continue;
8345 }
8346 let mut can_access = 0;
8347 unsafe {
8348 cudarc::driver::sys::cuDeviceCanAccessPeer(
8349 &mut can_access,
8350 ranks[src].ctx().cu_device(),
8351 ranks[dst].ctx().cu_device(),
8352 )
8353 .result()?;
8354 }
8355 if can_access == 0 {
8356 return Err(format!(
8357 "native TP requires P2P, but dev{} cannot access dev{}",
8358 devices[src], devices[dst]
8359 )
8360 .into());
8361 }
8362 ranks[src].ctx().bind_to_thread()?;
8363 let rc =
8364 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8365 use cudarc::driver::sys::cudaError_enum as E;
8366 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8367 return Err(format!(
8368 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8369 devices[src], devices[dst]
8370 )
8371 .into());
8372 }
8373 }
8374 }
8375
8376 for &owner in devices {
8377 for &accessor in devices {
8378 if owner == accessor {
8379 continue;
8380 }
8381 let device = cudarc::driver::result::device::get(owner as i32)?;
8382 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8383 unsafe {
8384 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8385 }
8386 let desc = cudarc::driver::sys::CUmemAccessDesc {
8387 location: cudarc::driver::sys::CUmemLocation {
8388 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8389 id: accessor as i32,
8390 },
8391 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8392 };
8393 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8394 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8395 return Err(format!(
8396 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8397 {rc:?}"
8398 )
8399 .into());
8400 }
8401 }
8402 }
8403
8404 for src in 0..ranks.len() {
8405 for dst in 0..ranks.len() {
8406 if src == dst {
8407 continue;
8408 }
8409 let expected = (0..NATIVE_P2P_PROBE_WORDS)
8410 .map(|index| {
8411 (index as u32)
8412 .wrapping_mul(0x9e37_79b9)
8413 .wrapping_add(((src as u32) << 16) | dst as u32)
8414 })
8415 .collect::<Vec<_>>();
8416 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8417 let source = ranks[src].htod_u32_v(&expected)?;
8418 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8419 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8420 let actual = ranks[dst].dtoh_u32(&destination)?;
8421 if actual != expected {
8422 let mismatches = actual
8423 .iter()
8424 .zip(&expected)
8425 .filter(|(actual, expected)| actual != expected)
8426 .count();
8427 return Err(format!(
8428 "native TP peer probe dev{}->dev{} failed: {mismatches}/{} words differ",
8429 devices[src],
8430 devices[dst],
8431 expected.len()
8432 )
8433 .into());
8434 }
8435 }
8436 }
8437 ranks[0].ctx().bind_to_thread()?;
8438 eprintln!(
8439 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8440 directions={} bytes={} mismatches=0",
8441 ranks.len() * (ranks.len() - 1),
8442 NATIVE_P2P_PROBE_WORDS * std::mem::size_of::<u32>(),
8443 );
8444 Ok(())
8445}
8446
8447fn validate_activations(
8448 activations: &[f32],
8449 tokens: usize,
8450 in_features: usize,
8451) -> Result<(), String> {
8452 let expected = tokens
8453 .checked_mul(in_features)
8454 .ok_or_else(|| "activation size overflow".to_string())?;
8455 if activations.len() != expected {
8456 return Err(format!(
8457 "activation count {} != {tokens}x{in_features} ({expected})",
8458 activations.len()
8459 ));
8460 }
8461 if !activations.iter().all(|value| value.is_finite()) {
8462 return Err("activations contain a non-finite value".to_string());
8463 }
8464 Ok(())
8465}
8466
8467fn column_shard(
8468 matrix: E4m3BlockMatrix<'_>,
8469 tp: usize,
8470 rank: usize,
8471) -> Result<E4m3BlockMatrix<'_>, String> {
8472 let local_out = matrix.out_features / tp;
8473 let row_start = rank * local_out;
8474 let code_start = row_start * matrix.in_features;
8475 let code_end = code_start + local_out * matrix.in_features;
8476 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8477 let local_scale_rows = local_out / FP8_BLOCK;
8478 let scale_start = rank * local_scale_rows * scale_cols;
8479 let scale_end = scale_start + local_scale_rows * scale_cols;
8480 Ok(E4m3BlockMatrix {
8481 codes: &matrix.codes[code_start..code_end],
8482 scales: &matrix.scales[scale_start..scale_end],
8483 out_features: local_out,
8484 in_features: matrix.in_features,
8485 })
8486}
8487
8488fn row_shard(
8489 matrix: E4m3BlockMatrix<'_>,
8490 tp: usize,
8491 rank: usize,
8492) -> Result<(Vec<u8>, Vec<f32>), String> {
8493 let local_in = matrix.in_features / tp;
8494 let col_start = rank * local_in;
8495 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8496 for row in 0..matrix.out_features {
8497 let start = row * matrix.in_features + col_start;
8498 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8499 }
8500
8501 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8502 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8503 let local_scale_cols = local_in / FP8_BLOCK;
8504 let scale_col_start = rank * local_scale_cols;
8505 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8506 for row in 0..scale_rows {
8507 let start = row * scale_cols + scale_col_start;
8508 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8509 }
8510 Ok((codes, scales))
8511}
8512
8513fn activation_shard(
8514 activations: &[f32],
8515 tokens: usize,
8516 in_features: usize,
8517 tp: usize,
8518 rank: usize,
8519) -> Vec<f32> {
8520 let local_in = in_features / tp;
8521 let col_start = rank * local_in;
8522 let mut shard = Vec::with_capacity(tokens * local_in);
8523 for token in 0..tokens {
8524 let start = token * in_features + col_start;
8525 shard.extend_from_slice(&activations[start..start + local_in]);
8526 }
8527 shard
8528}
8529
8530#[derive(Clone, Copy)]
8550pub struct Nvfp4BlockMatrix<'a> {
8551 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8555 pub in_features: usize,
8556}
8557
8558impl Nvfp4BlockMatrix<'_> {
8559 pub fn validate(&self) -> Result<(), String> {
8560 if self.in_features == 0 || self.out_features == 0 {
8561 return Err("NVFP4 matrix has a zero dimension".to_string());
8562 }
8563 if self.in_features % 64 != 0 {
8564 return Err(format!(
8565 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8566 self.in_features
8567 ));
8568 }
8569 if self.codes.len() != self.out_features * self.in_features / 2 {
8570 return Err(format!(
8571 "NVFP4 code bytes {} != {}x{}/2",
8572 self.codes.len(),
8573 self.out_features,
8574 self.in_features
8575 ));
8576 }
8577 if self.scales.len() != self.out_features * self.in_features / 16 {
8578 return Err(format!(
8579 "NVFP4 scale bytes {} != {}x{}/16",
8580 self.scales.len(),
8581 self.out_features,
8582 self.in_features
8583 ));
8584 }
8585 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8586 return Err(format!(
8587 "NVFP4 macro scale {} is not finite-positive",
8588 self.macro_scale
8589 ));
8590 }
8591 Ok(())
8592 }
8593}
8594
8595#[derive(Clone, Copy)]
8597pub struct Nvfp4ExpertBank<'a> {
8598 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8602 pub out_features: usize,
8603 pub in_features: usize,
8604}
8605
8606impl Nvfp4ExpertBank<'_> {
8607 pub fn validate(&self) -> Result<(), String> {
8608 if self.expert_count == 0 {
8609 return Err("NVFP4 expert bank is empty".to_string());
8610 }
8611 if self.macros.len() != self.expert_count {
8612 return Err(format!(
8613 "NVFP4 bank macros {} != expert count {}",
8614 self.macros.len(),
8615 self.expert_count
8616 ));
8617 }
8618 self.expert(0).map(|_| ())
8619 }
8620
8621 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8622 if expert >= self.expert_count {
8623 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8624 }
8625 let code_stride = self.out_features * self.in_features / 2;
8626 let scale_stride = self.out_features * self.in_features / 16;
8627 if self.codes.len() != self.expert_count * code_stride
8628 || self.scales.len() != self.expert_count * scale_stride
8629 {
8630 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8631 }
8632 let matrix = Nvfp4BlockMatrix {
8633 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8634 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8635 macro_scale: self.macros[expert],
8636 out_features: self.out_features,
8637 in_features: self.in_features,
8638 };
8639 matrix.validate()?;
8640 Ok(matrix)
8641 }
8642}
8643
8644pub struct ResidentNvfp4Rank {
8646 blocks: crate::CudaSlice<u8>,
8647 macro_scale: f32,
8648 out_features: usize,
8649 in_features: usize,
8650 row_bytes: usize,
8651}
8652
8653pub struct ResidentNvfp4ColumnParallel {
8654 ranks: Vec<ResidentNvfp4Rank>,
8655 pub out_features: usize,
8656 pub in_features: usize,
8657}
8658
8659pub struct ResidentNvfp4RowParallel {
8660 ranks: Vec<ResidentNvfp4Rank>,
8661 pub out_features: usize,
8662 pub in_features: usize,
8663}
8664
8665pub struct ResidentTpNvfp4Expert {
8666 gate: ResidentNvfp4ColumnParallel,
8667 up: ResidentNvfp4ColumnParallel,
8668 down: ResidentNvfp4RowParallel,
8669 pub input_width: usize,
8670 pub expert_width: usize,
8671}
8672
8673pub struct ResidentNvfp4ColumnBankRank {
8677 bank: crate::CudaSlice<u8>,
8681 expert_bytes: usize,
8682 local_out: usize,
8683 in_features: usize,
8684 row_bytes: usize,
8685}
8686
8687impl ResidentNvfp4ColumnBankRank {
8688 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8689 self.bank
8690 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8691 }
8692}
8693
8694pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8700
8701pub struct ResidentNvfp4RowBankRank {
8702 bank: crate::CudaSlice<u8>,
8704 expert_bytes: usize,
8705 device_rank: usize, out_features: usize,
8707 local_in: usize,
8708 row_bytes: usize,
8709}
8710
8711impl ResidentNvfp4RowBankRank {
8712 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8713 self.bank
8714 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8715 }
8716}
8717
8718impl ResidentNvfp4TensorParallel {
8719 pub(crate) fn device_workspace_handle(
8720 &self,
8721 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8722 &self.device_workspace
8723 }
8724}
8725
8726pub struct ResidentNvfp4TensorParallel {
8727 gate: Vec<ResidentNvfp4ColumnBankRank>,
8728 up: Vec<ResidentNvfp4ColumnBankRank>,
8729 down: Vec<ResidentNvfp4RowBankRank>,
8730 macros_gate: Vec<f32>,
8731 macros_up: Vec<f32>,
8732 macros_down: Vec<f32>,
8733 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8737 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8738 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8739 pub expert_count: usize,
8740 pub input_width: usize,
8741 pub expert_width: usize,
8742 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8745 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8749 pub(crate) ep2: bool,
8753}
8754
8755pub struct Nvfp4T2Workspace {
8759 input2: Vec<crate::CudaSlice<f32>>,
8760 in_q2: Vec<crate::CudaSlice<i8>>,
8761 in_d2: Vec<crate::CudaSlice<f32>>,
8762 sel2: Vec<crate::CudaSlice<i32>>,
8763 route_w2: Vec<crate::CudaSlice<f32>>,
8764 gate_out2: Vec<crate::CudaSlice<f32>>,
8765 up_out2: Vec<crate::CudaSlice<f32>>,
8766 act_q2: Vec<crate::CudaSlice<i8>>,
8767 act_d2: Vec<crate::CudaSlice<f32>>,
8768 partial2: Vec<crate::CudaSlice<f32>>,
8769 acc_a: Vec<crate::CudaSlice<f32>>,
8771 acc_b: Vec<crate::CudaSlice<f32>>,
8772 acc2: Vec<crate::CudaSlice<f32>>,
8775 peer2: crate::CudaSlice<f32>,
8776 omix2: crate::CudaSlice<f32>,
8777 peer_a: crate::CudaSlice<f32>,
8779 peer_b: crate::CudaSlice<f32>,
8780 omix_a: crate::CudaSlice<f32>,
8781 omix_b: crate::CudaSlice<f32>,
8782 ev_entry: CudaEvent,
8783 ev_rank: Vec<CudaEvent>,
8784 ev_root: CudaEvent,
8785 n_sel: usize,
8786 e_device: usize,
8787}
8788
8789struct RoutesGraph {
8796 exec: cudarc::driver::sys::CUgraphExec,
8797 parent: cudarc::driver::sys::CUgraph,
8798 _children: Vec<cudarc::driver::CudaGraph>,
8799}
8800unsafe impl Send for RoutesGraph {}
8803
8804impl Drop for RoutesGraph {
8805 fn drop(&mut self) {
8806 unsafe {
8807 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8808 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8809 }
8810 }
8811}
8812
8813impl Nvfp4DeviceRoutesWorkspace {
8814 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8815 self.in_stage_e.as_ref()
8816 }
8817 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8818 self.in_stage_e.as_mut()
8819 }
8820 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8821 self.out_stage_e.as_mut()
8822 }
8823 pub(crate) fn arm_stages(
8825 &mut self,
8826 e: &Engine,
8827 width: usize,
8828 n_sel: usize,
8829 ) -> Result<(), Box<dyn std::error::Error>> {
8830 let _main = e.gpu.enter_main()?;
8831 if self.in_stage_e.is_none() {
8832 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8833 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8834 }
8835 if self.dev_route_e.is_none() {
8836 self.dev_route_e = Some((
8837 e.htod_i32(&vec![0i32; n_sel])?,
8838 e.htod(&vec![0.0f32; n_sel])?,
8839 ));
8840 }
8841 Ok(())
8842 }
8843
8844 pub(crate) fn in_and_out_stages_mut(
8846 &mut self,
8847 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
8848 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
8849 (Some(input), Some(output)) => Some((input, output)),
8850 _ => None,
8851 }
8852 }
8853 pub(crate) fn dev_route_e_mut(
8854 &mut self,
8855 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
8856 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
8857 }
8858}
8859
8860pub struct Nvfp4DeviceRoutesWorkspace {
8861 gate_out: Vec<crate::CudaSlice<f32>>,
8864 up_out: Vec<crate::CudaSlice<f32>>,
8865 act_q: Vec<crate::CudaSlice<i8>>,
8866 act_d: Vec<crate::CudaSlice<f32>>,
8867 sel: Vec<crate::CudaSlice<i32>>,
8868 partial: Vec<crate::CudaSlice<f32>>,
8869 accumulator: Vec<crate::CudaSlice<f32>>,
8870 combine_w: Vec<crate::CudaSlice<f32>>,
8872 route_w: Vec<crate::CudaSlice<f32>>,
8875 in_q: Vec<crate::CudaSlice<i8>>,
8878 in_d: Vec<crate::CudaSlice<f32>>,
8879 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
8883 prestaged: bool,
8886 rank1_routed: bool,
8889 fence_flags_raw: u64,
8893 fence_ticket: u32,
8894 ev_input: Option<(CudaEvent, usize)>,
8896 in_stage_e: Option<crate::CudaSlice<f32>>,
8899 out_stage_e: Option<crate::CudaSlice<f32>>,
8900 routes_graph: Option<RoutesGraph>,
8901 raw_dev_route_e: Option<(u64, u64)>,
8903 raw_combine: Option<(u64, u64, u64, u64)>,
8904 raw_input: Vec<u64>,
8905 raw_sel: Vec<u64>,
8906 raw_route_w: Vec<u64>,
8907 remote: crate::CudaSlice<f32>,
8908 combined: crate::CudaSlice<f32>,
8909 n_sel: usize,
8910 input: Vec<crate::CudaSlice<f32>>,
8914 ev_rank: Vec<CudaEvent>,
8915 ev_done: Option<CudaEvent>,
8916 ev_entry: Option<(CudaEvent, usize)>,
8917}
8918
8919struct ResidentNvfp4EpRank {
8921 gate: Vec<crate::CudaSlice<u8>>,
8922 up: Vec<crate::CudaSlice<u8>>,
8923 down: Vec<crate::CudaSlice<u8>>,
8924 #[allow(dead_code)]
8925 expert_range: Range<usize>,
8926}
8927
8928pub struct ResidentNvfp4ExpertParallel {
8929 ranks: Vec<ResidentNvfp4EpRank>,
8930 macros_gate: Vec<f32>,
8931 macros_up: Vec<f32>,
8932 macros_down: Vec<f32>,
8933 pub expert_count: usize,
8934 pub input_width: usize,
8935 pub expert_width: usize,
8936 gate_row_bytes: usize,
8937 down_row_bytes: usize,
8938}
8939
8940fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8941 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
8942 matrix.codes,
8943 matrix.scales,
8944 matrix.out_features,
8945 matrix.in_features,
8946 )
8947}
8948
8949fn nvfp4_row_bytes(in_features: usize) -> usize {
8950 in_features / 64 * 36 }
8952
8953pub(crate) fn fuse_rope_append_on() -> bool {
8961 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8962 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
8963}
8964
8965pub(crate) fn no_local_shadow_on() -> bool {
8966 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8967 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
8968}
8969
8970pub(crate) fn nvfp4_bank_v2_on() -> bool {
8971 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8972 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
8973}
8974
8975fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
8979 let row_bytes = nvfp4_row_bytes(in_features);
8980 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
8981 let n_slots = in_features / 32;
8982 let mut out = Vec::with_capacity(v1.len());
8983 for row in 0..out_features {
8984 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
8985 for g in 0..n_slots {
8986 let (sblk, h) = (g / 2, g % 2);
8987 let b = &r[sblk * 36..sblk * 36 + 36];
8988 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
8989 }
8990 for g in 0..n_slots {
8991 let (sblk, h) = (g / 2, g % 2);
8992 let b = &r[sblk * 36..sblk * 36 + 36];
8993 out.push(b[2 * h]);
8994 out.push(b[2 * h + 1]);
8995 }
8996 }
8997 out
8998}
8999
9000fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9002 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9003 let v1 = nvfp4_repack_matrix(matrix);
9004 if nvfp4_bank_v2_on() {
9005 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9006 } else {
9007 v1
9008 }
9009}
9010
9011fn nvfp4_column_shard<'a>(
9014 matrix: Nvfp4BlockMatrix<'a>,
9015 tp: usize,
9016 rank: usize,
9017) -> Result<Nvfp4BlockMatrix<'a>, String> {
9018 if matrix.out_features % tp != 0 {
9019 return Err(format!(
9020 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
9021 matrix.out_features
9022 ));
9023 }
9024 let local_out = matrix.out_features / tp;
9025 let code_row = matrix.in_features / 2;
9026 let scale_row = matrix.in_features / 16;
9027 Ok(Nvfp4BlockMatrix {
9028 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
9029 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
9030 macro_scale: matrix.macro_scale,
9031 out_features: local_out,
9032 in_features: matrix.in_features,
9033 })
9034}
9035
9036fn nvfp4_row_shard(
9039 matrix: Nvfp4BlockMatrix<'_>,
9040 tp: usize,
9041 rank: usize,
9042) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
9043 if matrix.in_features % tp != 0 {
9044 return Err(format!(
9045 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
9046 matrix.in_features
9047 ));
9048 }
9049 let local_in = matrix.in_features / tp;
9050 if local_in % 64 != 0 {
9051 return Err(format!(
9052 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
9053 ));
9054 }
9055 let code_row = matrix.in_features / 2;
9056 let scale_row = matrix.in_features / 16;
9057 let local_code = local_in / 2;
9058 let local_scale = local_in / 16;
9059 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
9060 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
9061 for row in 0..matrix.out_features {
9062 let code_start = row * code_row + rank * local_code;
9063 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
9064 let scale_start = row * scale_row + rank * local_scale;
9065 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
9066 }
9067 Ok((codes, scales, local_in))
9068}
9069
9070fn run_rank_nvfp4(
9074 engine: &Engine,
9075 matrix: Nvfp4BlockMatrix<'_>,
9076 activations: &[f32],
9077 tokens: usize,
9078) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9079 matrix.validate()?;
9080 validate_activations(activations, tokens, matrix.in_features)?;
9081 let _main = engine.gpu.enter_main()?;
9082 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
9083 let activations = engine.htod(activations)?;
9084 let output = engine.qmatvec_nvfp4_fast(
9085 &blocks.slice(0..blocks.len()),
9086 &activations,
9087 tokens,
9088 matrix.in_features,
9089 matrix.out_features,
9090 nvfp4_row_bytes(matrix.in_features),
9091 )?;
9092 engine.dtoh(&output)
9093}
9094
9095fn upload_rank_nvfp4(
9096 engine: &Engine,
9097 matrix: Nvfp4BlockMatrix<'_>,
9098) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
9099 matrix.validate()?;
9100 let _main = engine.gpu.enter_main()?;
9101 Ok(ResidentNvfp4Rank {
9102 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
9103 macro_scale: matrix.macro_scale,
9104 out_features: matrix.out_features,
9105 in_features: matrix.in_features,
9106 row_bytes: nvfp4_row_bytes(matrix.in_features),
9107 })
9108}
9109
9110fn run_resident_rank_nvfp4(
9111 engine: &Engine,
9112 rank: &ResidentNvfp4Rank,
9113 activations: &[f32],
9114 tokens: usize,
9115) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9116 validate_activations(activations, tokens, rank.in_features)?;
9117 let _main = engine.gpu.enter_main()?;
9118 let activations = engine.htod(activations)?;
9119 let output = engine.qmatvec_nvfp4_fast(
9120 &rank.blocks.slice(0..rank.blocks.len()),
9121 &activations,
9122 tokens,
9123 rank.in_features,
9124 rank.out_features,
9125 rank.row_bytes,
9126 )?;
9127 engine.dtoh(&output)
9128}
9129
9130fn apply_macro(values: &mut [f32], macro_scale: f32) {
9131 for value in values.iter_mut() {
9132 *value *= macro_scale;
9133 }
9134}
9135
9136impl TpE4m3HostBounce {
9137 pub fn full_nvfp4(
9139 &self,
9140 matrix: Nvfp4BlockMatrix<'_>,
9141 activations: &[f32],
9142 tokens: usize,
9143 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9144 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
9145 apply_macro(&mut output, matrix.macro_scale);
9146 Ok(output)
9147 }
9148
9149 pub fn column_parallel_nvfp4(
9152 &self,
9153 matrix: Nvfp4BlockMatrix<'_>,
9154 activations: &[f32],
9155 tokens: usize,
9156 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
9157 matrix.validate()?;
9158 validate_activations(activations, tokens, matrix.in_features)?;
9159 let tp = self.ranks.len();
9160 let local_out = matrix.out_features / tp;
9161 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9162 let mut rank_outputs = Vec::with_capacity(tp);
9163 for (rank_index, rank) in self.ranks.iter().enumerate() {
9164 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
9165 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
9166 let row_start = rank_index * local_out;
9167 for token in 0..tokens {
9168 gathered[token * matrix.out_features + row_start
9169 ..token * matrix.out_features + row_start + local_out]
9170 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9171 }
9172 rank_outputs.push(output);
9173 }
9174 apply_macro(&mut gathered, matrix.macro_scale);
9175 Ok(ColumnParallelResult {
9176 gathered,
9177 rank_outputs,
9178 })
9179 }
9180
9181 pub fn row_parallel_nvfp4(
9184 &self,
9185 matrix: Nvfp4BlockMatrix<'_>,
9186 activations: &[f32],
9187 tokens: usize,
9188 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
9189 matrix.validate()?;
9190 validate_activations(activations, tokens, matrix.in_features)?;
9191 let tp = self.ranks.len();
9192 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9193 let mut rank_partials = Vec::with_capacity(tp);
9194 for (rank_index, rank) in self.ranks.iter().enumerate() {
9195 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
9196 let local_activations =
9197 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9198 let shard = Nvfp4BlockMatrix {
9199 codes: &codes,
9200 scales: &scales,
9201 macro_scale: matrix.macro_scale,
9202 out_features: matrix.out_features,
9203 in_features: local_in,
9204 };
9205 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
9206 for (sum, value) in reduced.iter_mut().zip(&partial) {
9207 *sum += *value;
9208 }
9209 rank_partials.push(partial);
9210 }
9211 apply_macro(&mut reduced, matrix.macro_scale);
9212 Ok(RowParallelResult {
9213 reduced,
9214 rank_partials,
9215 })
9216 }
9217
9218 pub fn upload_expert_nvfp4(
9219 &self,
9220 gate: Nvfp4BlockMatrix<'_>,
9221 up: Nvfp4BlockMatrix<'_>,
9222 down: Nvfp4BlockMatrix<'_>,
9223 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
9224 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9225 return Err("NVFP4 TP expert gate/up dimensions differ".into());
9226 }
9227 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9228 return Err(format!(
9229 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
9230 down.out_features, down.in_features, gate.out_features, gate.in_features
9231 )
9232 .into());
9233 }
9234 let tp = self.ranks.len();
9235 let mut gate_ranks = Vec::with_capacity(tp);
9236 let mut up_ranks = Vec::with_capacity(tp);
9237 let mut down_ranks = Vec::with_capacity(tp);
9238 for (rank_index, engine) in self.ranks.iter().enumerate() {
9239 gate_ranks.push(upload_rank_nvfp4(
9240 engine,
9241 nvfp4_column_shard(gate, tp, rank_index)?,
9242 )?);
9243 up_ranks.push(upload_rank_nvfp4(
9244 engine,
9245 nvfp4_column_shard(up, tp, rank_index)?,
9246 )?);
9247 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
9248 down_ranks.push(upload_rank_nvfp4(
9249 engine,
9250 Nvfp4BlockMatrix {
9251 codes: &codes,
9252 scales: &scales,
9253 macro_scale: down.macro_scale,
9254 out_features: down.out_features,
9255 in_features: local_in,
9256 },
9257 )?);
9258 }
9259 Ok(ResidentTpNvfp4Expert {
9260 gate: ResidentNvfp4ColumnParallel {
9261 ranks: gate_ranks,
9262 out_features: gate.out_features,
9263 in_features: gate.in_features,
9264 },
9265 up: ResidentNvfp4ColumnParallel {
9266 ranks: up_ranks,
9267 out_features: up.out_features,
9268 in_features: up.in_features,
9269 },
9270 down: ResidentNvfp4RowParallel {
9271 ranks: down_ranks,
9272 out_features: down.out_features,
9273 in_features: down.in_features,
9274 },
9275 input_width: gate.in_features,
9276 expert_width: gate.out_features,
9277 })
9278 }
9279
9280 fn column_parallel_resident_nvfp4(
9281 &self,
9282 matrix: &ResidentNvfp4ColumnParallel,
9283 activations: &[f32],
9284 tokens: usize,
9285 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9286 validate_activations(activations, tokens, matrix.in_features)?;
9287 let local_out = matrix.out_features / self.ranks.len();
9288 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9289 let mut macro_scale = None;
9290 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9291 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
9292 let row_start = rank_index * local_out;
9293 for token in 0..tokens {
9294 gathered[token * matrix.out_features + row_start
9295 ..token * matrix.out_features + row_start + local_out]
9296 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9297 }
9298 macro_scale = Some(shard.macro_scale);
9299 }
9300 apply_macro(
9301 &mut gathered,
9302 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
9303 );
9304 Ok(gathered)
9305 }
9306
9307 fn row_parallel_resident_nvfp4(
9308 &self,
9309 matrix: &ResidentNvfp4RowParallel,
9310 activations: &[f32],
9311 tokens: usize,
9312 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9313 validate_activations(activations, tokens, matrix.in_features)?;
9314 let tp = self.ranks.len();
9315 let local_in = matrix.in_features / tp;
9316 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9317 let mut macro_scale = None;
9318 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9319 if shard.in_features != local_in {
9320 return Err(format!(
9321 "NVFP4 resident row shard in_features {} != expected {local_in}",
9322 shard.in_features
9323 )
9324 .into());
9325 }
9326 let local_activations =
9327 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9328 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9329 for (sum, value) in reduced.iter_mut().zip(&partial) {
9330 *sum += *value;
9331 }
9332 macro_scale = Some(shard.macro_scale);
9333 }
9334 apply_macro(
9335 &mut reduced,
9336 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9337 );
9338 Ok(reduced)
9339 }
9340
9341 pub fn run_expert_nvfp4(
9342 &self,
9343 expert: &ResidentTpNvfp4Expert,
9344 input: &[f32],
9345 tokens: usize,
9346 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9347 validate_activations(input, tokens, expert.input_width)?;
9348 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9349 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9350 let activated: Vec<f32> = gate
9351 .iter()
9352 .zip(&up)
9353 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9354 .collect();
9355 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9356 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9357 }
9358
9359 pub fn upload_tensor_parallel_nvfp4(
9361 &self,
9362 gate: Nvfp4ExpertBank<'_>,
9363 up: Nvfp4ExpertBank<'_>,
9364 down: Nvfp4ExpertBank<'_>,
9365 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9366 gate.validate()?;
9367 up.validate()?;
9368 down.validate()?;
9369 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9370 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9371 }
9372 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9373 return Err("NVFP4 TP gate/up dimensions differ".into());
9374 }
9375 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9376 return Err(format!(
9377 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9378 down.out_features, down.in_features, gate.out_features, gate.in_features
9379 )
9380 .into());
9381 }
9382 let tp = self.ranks.len();
9383 if gate.out_features % tp != 0 {
9384 return Err(format!(
9385 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9386 gate.out_features
9387 )
9388 .into());
9389 }
9390 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9391 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9392 {
9393 return Err(format!(
9394 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9395 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9396 down.in_features
9397 )
9398 .into());
9399 }
9400 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9401 return Err(format!(
9402 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9403 ({NVFP4_CANONICAL_ROW_SHARDS})"
9404 )
9405 .into());
9406 }
9407
9408 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9409 let mut gate_ranks = Vec::with_capacity(tp);
9410 let mut up_ranks = Vec::with_capacity(tp);
9411 let mut macros_gate_dev = Vec::with_capacity(tp);
9412 let mut macros_up_dev = Vec::with_capacity(tp);
9413 let mut macros_down_dev = Vec::with_capacity(tp);
9414 for (rank_index, engine) in self.ranks.iter().enumerate() {
9415 let _main = engine.gpu.enter_main()?;
9416 let mut gate_host: Vec<u8> = Vec::new();
9422 let mut up_host: Vec<u8> = Vec::new();
9423 let mut owned = 0usize;
9424 for expert in 0..gate.expert_count {
9425 if ep2 {
9426 if expert % 2 != rank_index {
9427 continue;
9428 }
9429 owned += 1;
9430 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
9431 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
9432 } else {
9433 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9434 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
9435 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9436 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
9437 }
9438 }
9439 let bank_experts = if ep2 { owned } else { gate.expert_count };
9440 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9441 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9442 let local_out = if ep2 {
9443 gate.out_features
9444 } else {
9445 gate.out_features / tp
9446 };
9447 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9448 bank: engine.htod_bytes(&gate_host)?,
9449 expert_bytes: gate_expert_bytes,
9450 local_out,
9451 in_features: gate.in_features,
9452 row_bytes: nvfp4_row_bytes(gate.in_features),
9453 });
9454 up_ranks.push(ResidentNvfp4ColumnBankRank {
9455 bank: engine.htod_bytes(&up_host)?,
9456 expert_bytes: up_expert_bytes,
9457 local_out,
9458 in_features: up.in_features,
9459 row_bytes: nvfp4_row_bytes(up.in_features),
9460 });
9461 macros_gate_dev.push(engine.htod(gate.macros)?);
9462 macros_up_dev.push(engine.htod(up.macros)?);
9463 macros_down_dev.push(engine.htod(down.macros)?);
9464 }
9465 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9469 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9470 let device_rank = shard_index % tp;
9471 let engine = &self.ranks[device_rank];
9472 let _main = engine.gpu.enter_main()?;
9473 let mut down_host: Vec<u8> = Vec::new();
9474 let mut owned = 0usize;
9475 for expert in 0..down.expert_count {
9476 let down_matrix = down.expert(expert)?;
9477 if ep2 {
9478 if expert % 2 != device_rank {
9481 continue;
9482 }
9483 owned += 1;
9484 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
9485 } else {
9486 let (codes, scales, local_in) =
9487 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9488 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
9489 codes: &codes,
9490 scales: &scales,
9491 macro_scale: down_matrix.macro_scale,
9492 out_features: down_matrix.out_features,
9493 in_features: local_in,
9494 }));
9495 }
9496 }
9497 let bank_experts = if ep2 { owned } else { down.expert_count };
9498 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9499 let local_in = if ep2 {
9500 down.in_features
9501 } else {
9502 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9503 };
9504 down_ranks.push(ResidentNvfp4RowBankRank {
9505 bank: engine.htod_bytes(&down_host)?,
9506 expert_bytes: down_expert_bytes,
9507 device_rank,
9508 out_features: down.out_features,
9509 local_in,
9510 row_bytes: nvfp4_row_bytes(local_in),
9511 });
9512 }
9513 Ok(ResidentNvfp4TensorParallel {
9514 gate: gate_ranks,
9515 up: up_ranks,
9516 down: down_ranks,
9517 macros_gate: gate.macros.to_vec(),
9518 macros_up: up.macros.to_vec(),
9519 macros_down: down.macros.to_vec(),
9520 macros_gate_dev,
9521 macros_up_dev,
9522 macros_down_dev,
9523 expert_count: gate.expert_count,
9524 input_width: gate.in_features,
9525 expert_width: gate.out_features,
9526 device_workspace: std::sync::Mutex::new(None),
9527 t2_workspace: std::sync::Mutex::new(None),
9528 ep2,
9529 })
9530 }
9531
9532 fn run_full_bank_expert_nvfp4(
9536 &self,
9537 ranks: &[ResidentNvfp4ColumnBankRank],
9538 macros: &[f32],
9539 expert: usize,
9540 input: &[f32],
9541 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9542 let owner = expert & 1;
9543 let slot = expert >> 1;
9544 let bank = ranks
9545 .get(owner)
9546 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9547 let engine = &self.ranks[owner];
9548 let _main = engine.gpu.enter_main()?;
9549 let activations = engine.htod(input)?;
9550 let output = if nvfp4_bank_v2_on() {
9551 engine.qmatvec_nvfp4_fast_v2(
9552 &bank.expert(slot),
9553 &activations,
9554 1,
9555 bank.in_features,
9556 bank.local_out,
9557 bank.row_bytes,
9558 )?
9559 } else {
9560 engine.qmatvec_nvfp4_fast(
9561 &bank.expert(slot),
9562 &activations,
9563 1,
9564 bank.in_features,
9565 bank.local_out,
9566 bank.row_bytes,
9567 )?
9568 };
9569 let mut out = engine.dtoh(&output)?;
9570 apply_macro(&mut out, macros[expert]);
9571 Ok(out)
9572 }
9573
9574 fn run_full_down_expert_nvfp4(
9577 &self,
9578 shards: &[ResidentNvfp4RowBankRank],
9579 macros: &[f32],
9580 expert: usize,
9581 input: &[f32],
9582 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9583 let owner = expert & 1;
9584 let slot = expert >> 1;
9585 let shard = shards
9586 .get(owner)
9587 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9588 let engine = &self.ranks[owner];
9589 let _main = engine.gpu.enter_main()?;
9590 let activations = engine.htod(input)?;
9591 let output = if nvfp4_bank_v2_on() {
9592 engine.qmatvec_nvfp4_fast_v2(
9593 &shard.expert(slot),
9594 &activations,
9595 1,
9596 shard.local_in,
9597 shard.out_features,
9598 shard.row_bytes,
9599 )?
9600 } else {
9601 engine.qmatvec_nvfp4_fast(
9602 &shard.expert(slot),
9603 &activations,
9604 1,
9605 shard.local_in,
9606 shard.out_features,
9607 shard.row_bytes,
9608 )?
9609 };
9610 let mut out = engine.dtoh(&output)?;
9611 apply_macro(&mut out, macros[expert]);
9612 Ok(out)
9613 }
9614
9615 fn run_column_bank_expert_nvfp4(
9616 &self,
9617 ranks: &[ResidentNvfp4ColumnBankRank],
9618 macros: &[f32],
9619 expert: usize,
9620 input: &[f32],
9621 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9622 let local_out = ranks
9623 .first()
9624 .ok_or("NVFP4 TP column bank has no ranks")?
9625 .local_out;
9626 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9627 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9628 let _main = engine.gpu.enter_main()?;
9629 let activations = engine.htod(input)?;
9630 let output = if nvfp4_bank_v2_on() {
9631 engine.qmatvec_nvfp4_fast_v2(
9632 &bank.expert(expert),
9633 &activations,
9634 1,
9635 bank.in_features,
9636 bank.local_out,
9637 bank.row_bytes,
9638 )?
9639 } else {
9640 engine.qmatvec_nvfp4_fast(
9641 &bank.expert(expert),
9642 &activations,
9643 1,
9644 bank.in_features,
9645 bank.local_out,
9646 bank.row_bytes,
9647 )?
9648 };
9649 let output = engine.dtoh(&output)?;
9650 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9651 }
9652 apply_macro(&mut gathered, macros[expert]);
9653 Ok(gathered)
9654 }
9655
9656 fn run_row_bank_expert_nvfp4(
9660 &self,
9661 shards: &[ResidentNvfp4RowBankRank],
9662 macros: &[f32],
9663 expert: usize,
9664 input: &[f32],
9665 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9666 let out_features = shards
9667 .first()
9668 .ok_or("NVFP4 TP row bank has no canonical shards")?
9669 .out_features;
9670 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
9671 let mut reduced = vec![0.0f32; out_features];
9672 for (shard_index, shard) in shards.iter().enumerate() {
9673 let engine = self
9674 .ranks
9675 .get(shard.device_rank)
9676 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
9677 let _main = engine.gpu.enter_main()?;
9678 let local_activations =
9679 activation_shard(input, 1, in_features, shards.len(), shard_index);
9680 let activations = engine.htod(&local_activations)?;
9681 let output = if nvfp4_bank_v2_on() {
9682 engine.qmatvec_nvfp4_fast_v2(
9683 &shard.expert(expert),
9684 &activations,
9685 1,
9686 shard.local_in,
9687 shard.out_features,
9688 shard.row_bytes,
9689 )?
9690 } else {
9691 engine.qmatvec_nvfp4_fast(
9692 &shard.expert(expert),
9693 &activations,
9694 1,
9695 shard.local_in,
9696 shard.out_features,
9697 shard.row_bytes,
9698 )?
9699 };
9700 let partial = engine.dtoh(&output)?;
9701 for (sum, value) in reduced.iter_mut().zip(&partial) {
9702 *sum += *value;
9703 }
9704 }
9705 apply_macro(&mut reduced, macros[expert]);
9706 Ok(reduced)
9707 }
9708
9709 pub fn upload_expert_parallel_nvfp4(
9713 &self,
9714 gate: Nvfp4ExpertBank<'_>,
9715 up: Nvfp4ExpertBank<'_>,
9716 down: Nvfp4ExpertBank<'_>,
9717 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
9718 gate.validate()?;
9719 up.validate()?;
9720 down.validate()?;
9721 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9722 return Err("NVFP4 EP gate/up/down expert counts differ".into());
9723 }
9724 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9725 return Err("NVFP4 EP gate/up dimensions differ".into());
9726 }
9727 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9728 return Err(format!(
9729 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9730 down.out_features, down.in_features, gate.out_features, gate.in_features
9731 )
9732 .into());
9733 }
9734 let world = self.ranks.len();
9735 if gate.expert_count % world != 0 {
9736 return Err(format!(
9737 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9738 gate.expert_count
9739 )
9740 .into());
9741 }
9742 let experts_per_rank = gate.expert_count / world;
9743 let mut ranks = Vec::with_capacity(world);
9744 for (rank_index, engine) in self.ranks.iter().enumerate() {
9745 let _main = engine.gpu.enter_main()?;
9746 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9747 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9748 let mut up_experts = Vec::with_capacity(experts_per_rank);
9749 let mut down_experts = Vec::with_capacity(experts_per_rank);
9750 for expert in expert_range.clone() {
9751 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9752 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9753 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9754 }
9755 ranks.push(ResidentNvfp4EpRank {
9756 gate: gate_experts,
9757 up: up_experts,
9758 down: down_experts,
9759 expert_range,
9760 });
9761 }
9762 Ok(ResidentNvfp4ExpertParallel {
9763 ranks,
9764 macros_gate: gate.macros.to_vec(),
9765 macros_up: up.macros.to_vec(),
9766 macros_down: down.macros.to_vec(),
9767 expert_count: gate.expert_count,
9768 input_width: gate.in_features,
9769 expert_width: gate.out_features,
9770 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9771 down_row_bytes: nvfp4_row_bytes(down.in_features),
9772 })
9773 }
9774
9775 #[allow(clippy::too_many_arguments)]
9781 pub fn run_routed_experts_nvfp4(
9782 &self,
9783 experts: &ResidentNvfp4ExpertParallel,
9784 input: &[f32],
9785 tokens: usize,
9786 selected: &[usize],
9787 route_weights: &[f32],
9788 experts_per_token: usize,
9789 activation_limit: Option<f32>,
9790 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9791 validate_activations(input, tokens, experts.input_width)?;
9792 let pairs = tokens
9793 .checked_mul(experts_per_token)
9794 .ok_or("NVFP4 EP route count overflow")?;
9795 if selected.len() != pairs || route_weights.len() != pairs {
9796 return Err(format!(
9797 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9798 {experts_per_token} ({pairs})",
9799 selected.len(),
9800 route_weights.len(),
9801 )
9802 .into());
9803 }
9804 if !route_weights.iter().all(|weight| weight.is_finite()) {
9805 return Err("NVFP4 EP route weights contain a non-finite value".into());
9806 }
9807 let experts_per_rank = experts.expert_count / experts.ranks.len();
9808 let mut output = vec![0.0f32; tokens * experts.input_width];
9809 for token in 0..tokens {
9810 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9811 for slot in 0..experts_per_token {
9812 let pair = token * experts_per_token + slot;
9813 let expert = selected[pair];
9814 if expert >= experts.expert_count {
9815 return Err(format!(
9816 "NVFP4 EP selected expert {expert} outside 0..{}",
9817 experts.expert_count
9818 )
9819 .into());
9820 }
9821 let owner = expert / experts_per_rank;
9822 let local = expert - owner * experts_per_rank;
9823 let rank = &experts.ranks[owner];
9824 let engine = &self.ranks[owner];
9825 let _main = engine.gpu.enter_main()?;
9826 let device_input = engine.htod(input_row)?;
9827 let gate_out = engine.qmatvec_nvfp4_fast(
9828 &rank.gate[local].slice(0..rank.gate[local].len()),
9829 &device_input,
9830 1,
9831 experts.input_width,
9832 experts.expert_width,
9833 experts.gate_row_bytes,
9834 )?;
9835 let up_out = engine.qmatvec_nvfp4_fast(
9836 &rank.up[local].slice(0..rank.up[local].len()),
9837 &device_input,
9838 1,
9839 experts.input_width,
9840 experts.expert_width,
9841 experts.gate_row_bytes,
9842 )?;
9843 let mut gate_host = engine.dtoh(&gate_out)?;
9844 let mut up_host = engine.dtoh(&up_out)?;
9845 apply_macro(&mut gate_host, experts.macros_gate[expert]);
9846 apply_macro(&mut up_host, experts.macros_up[expert]);
9847 let activated: Vec<f32> = gate_host
9848 .iter()
9849 .zip(&up_host)
9850 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
9851 .collect();
9852 let device_activated = engine.htod(&activated)?;
9853 let down_out = engine.qmatvec_nvfp4_fast(
9854 &rank.down[local].slice(0..rank.down[local].len()),
9855 &device_activated,
9856 1,
9857 experts.expert_width,
9858 experts.input_width,
9859 experts.down_row_bytes,
9860 )?;
9861 let mut down_host = engine.dtoh(&down_out)?;
9862 apply_macro(&mut down_host, experts.macros_down[expert]);
9863 let weight = route_weights[pair];
9864 for (sum, value) in output
9865 [token * experts.input_width..(token + 1) * experts.input_width]
9866 .iter_mut()
9867 .zip(down_host)
9868 {
9869 *sum += weight * value;
9870 }
9871 }
9872 }
9873 Ok(output)
9874 }
9875
9876 pub fn run_tensor_parallel_routes_nvfp4_device(
9890 &self,
9891 experts: &ResidentNvfp4TensorParallel,
9892 input: &[f32],
9893 selected: &[usize],
9894 route_weights: &[f32],
9895 experts_per_token: usize,
9896 activation_limit: Option<f32>,
9897 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9898 validate_activations(input, 1, experts.input_width)?;
9899 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9900 return Err(format!(
9901 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
9902 selected.len(),
9903 route_weights.len(),
9904 )
9905 .into());
9906 }
9907 if !route_weights.iter().all(|weight| weight.is_finite()) {
9908 return Err("NVFP4 device route weights contain a non-finite value".into());
9909 }
9910 let world = self.ranks.len();
9911 if world != NVFP4_CANONICAL_ROW_SHARDS {
9912 return Err(format!(
9913 "NVFP4 device routes require world == canonical shard grid \
9914 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9915 )
9916 .into());
9917 }
9918 let local_out = if experts.ep2 {
9919 experts.expert_width
9920 } else {
9921 experts.expert_width / world
9922 };
9923
9924 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9928 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9929 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9930 let started = timing.then(std::time::Instant::now);
9931
9932 let n_sel = experts_per_token;
9933 let mut workspace_guard = experts
9934 .device_workspace
9935 .lock()
9936 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9937 if workspace_guard.is_none() {
9938 let mut gate_out = Vec::with_capacity(world);
9939 let mut up_out = Vec::with_capacity(world);
9940 let mut act_q = Vec::with_capacity(world);
9941 let mut act_d = Vec::with_capacity(world);
9942 let mut sel = Vec::with_capacity(world);
9943 let mut partial = Vec::with_capacity(world);
9944 let mut accumulator = Vec::with_capacity(world);
9945 let mut combine_w = Vec::with_capacity(world);
9946 let mut route_w = Vec::with_capacity(world);
9947 let mut in_q = Vec::with_capacity(world);
9948 let mut in_d = Vec::with_capacity(world);
9949 let mut input = Vec::with_capacity(world);
9950 let mut ev_rank = Vec::with_capacity(world);
9951 let moe_direct = moe_direct_on();
9952 for (rank, engine) in self.ranks.iter().enumerate() {
9953 let _main = engine.gpu.enter_main()?;
9954 gate_out.push(engine.uninit(n_sel * local_out)?);
9955 up_out.push(engine.uninit(n_sel * local_out)?);
9956 act_q.push(engine.uninit_i8(n_sel * local_out)?);
9957 act_d.push(engine.uninit(n_sel * local_out / 32)?);
9958 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
9959 partial.push(engine.uninit(n_sel * experts.input_width)?);
9960 if moe_direct && rank != 0 {
9962 let root = &self.ranks[0];
9963 let _root_main = root.gpu.enter_main()?;
9964 accumulator.push(root.zeros(experts.input_width)?);
9965 } else {
9966 accumulator.push(engine.zeros(experts.input_width)?);
9967 }
9968 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9969 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9970 in_q.push(engine.uninit_i8(experts.input_width)?);
9971 in_d.push(engine.uninit(experts.input_width / 32)?);
9972 input.push(engine.uninit(experts.input_width)?);
9973 ev_rank.push(engine.ctx().new_event(None)?);
9974 }
9975 let root = &self.ranks[0];
9976 let _main = root.gpu.enter_main()?;
9977 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
9978 prestaged: false,
9979 rank1_routed: false,
9980 ev_input: None,
9981 fence_flags_raw: 0,
9982 fence_ticket: 0,
9983 gate_out,
9984 up_out,
9985 act_q,
9986 act_d,
9987 sel,
9988 partial,
9989 accumulator,
9990 combine_w,
9991 route_w,
9992 in_q,
9993 in_d,
9994 dev_route_e: None,
9995 in_stage_e: None,
9996 out_stage_e: None,
9997 routes_graph: None,
9998 raw_dev_route_e: None,
9999 raw_combine: None,
10000 raw_input: Vec::new(),
10001 raw_sel: Vec::new(),
10002 raw_route_w: Vec::new(),
10003 remote: root.uninit(experts.input_width)?,
10004 combined: root.uninit(experts.input_width)?,
10005 n_sel,
10006 input,
10007 ev_rank,
10008 ev_done: Some(root.ctx().new_event(None)?),
10009 ev_entry: None,
10010 });
10011 }
10012 let workspace = workspace_guard
10013 .as_mut()
10014 .expect("NVFP4 device routes workspace initialized above");
10015 if experts.ep2 {
10018 return Ok(vec![0.0f32; experts.input_width]);
10019 }
10020 if workspace.n_sel != n_sel {
10021 return Err(format!(
10022 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10023 workspace.n_sel
10024 )
10025 .into());
10026 }
10027 for &expert in selected {
10028 if expert >= experts.expert_count {
10029 return Err(format!(
10030 "NVFP4 device selected expert {expert} outside 0..{}",
10031 experts.expert_count
10032 )
10033 .into());
10034 }
10035 }
10036 let sel_i32 = selected
10037 .iter()
10038 .map(|&expert| expert as i32)
10039 .collect::<Vec<_>>();
10040
10041 for (rank_index, engine) in self.ranks.iter().enumerate() {
10048 let _main = engine.gpu.enter_main()?;
10049 let device_input = engine.htod(input)?;
10050 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
10051 engine.quantize_q8_1_into(
10052 &device_input,
10053 1,
10054 experts.input_width,
10055 &mut in_q[rank_index],
10056 &mut in_d[rank_index],
10057 )?;
10058 }
10060 self.nvfp4_routes_batched_sweeps(
10061 experts,
10062 workspace,
10063 selected,
10064 route_weights,
10065 &sel_i32,
10066 local_out,
10067 n_sel,
10068 activation_limit,
10069 false,
10070 )?;
10071
10072 let root = &self.ranks[0];
10075 for engine in &self.ranks[1..] {
10076 let _main = engine.gpu.enter_main()?;
10077 engine.stream().synchronize()?;
10078 }
10079 let _main = root.gpu.enter_main()?;
10080 root.stream()
10081 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10082 root.add(
10083 &workspace.accumulator[0],
10084 &workspace.remote,
10085 &mut workspace.combined,
10086 experts.input_width,
10087 )?;
10088 let output = root.dtoh(&workspace.combined)?;
10089 if let Some(started) = started {
10090 use std::sync::atomic::Ordering;
10091 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10092 + started.elapsed().as_nanos() as u64;
10093 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10094 if calls % 430 == 0 {
10095 eprintln!(
10096 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10097 ns as f64 / 1.0e6,
10098 ns as f64 / calls as f64 / 1.0e3,
10099 );
10100 }
10101 }
10102 Ok(output)
10103 }
10104
10105 #[allow(clippy::too_many_arguments)]
10110 fn nvfp4_routes_batched_sweeps(
10111 &self,
10112 experts: &ResidentNvfp4TensorParallel,
10113 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10114 selected: &[usize],
10115 route_weights: &[f32],
10116 sel_i32: &[i32],
10117 local_out: usize,
10118 n_sel: usize,
10119 activation_limit: Option<f32>,
10120 device_routed: bool,
10121 ) -> Result<(), Box<dyn std::error::Error>> {
10122 for rank_index in 0..self.ranks.len() {
10123 self.nvfp4_routes_batched_sweeps_rank(
10124 experts,
10125 workspace,
10126 selected,
10127 route_weights,
10128 sel_i32,
10129 local_out,
10130 n_sel,
10131 activation_limit,
10132 device_routed,
10133 rank_index,
10134 )?;
10135 }
10136 Ok(())
10137 }
10138
10139 #[allow(clippy::too_many_arguments)]
10142 fn nvfp4_routes_batched_sweeps_rank(
10143 &self,
10144 experts: &ResidentNvfp4TensorParallel,
10145 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10146 selected: &[usize],
10147 route_weights: &[f32],
10148 sel_i32: &[i32],
10149 local_out: usize,
10150 n_sel: usize,
10151 activation_limit: Option<f32>,
10152 device_routed: bool,
10153 rank_index: usize,
10154 ) -> Result<(), Box<dyn std::error::Error>> {
10155 {
10156 let engine = &self.ranks[rank_index];
10157 let _main = engine.gpu.enter_main()?;
10158 if experts.ep2 {
10163 if !device_routed {
10164 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
10165 }
10166 let gate_bank = &experts.gate[rank_index];
10167 let up_bank = &experts.up[rank_index];
10168 if gate_bank.local_out != experts.expert_width
10169 || gate_bank.expert_bytes != up_bank.expert_bytes
10170 {
10171 return Err("NVFP4 EP2 bank geometry drifted".into());
10172 }
10173 {
10174 let Nvfp4DeviceRoutesWorkspace {
10175 sel,
10176 gate_out,
10177 up_out,
10178 in_q,
10179 in_d,
10180 ..
10181 } = &mut *workspace;
10182 engine.qmatvec_nvfp4_sel_gu_ep_into(
10183 &gate_bank.bank,
10184 &up_bank.bank,
10185 &sel[rank_index],
10186 &in_q[rank_index],
10187 &in_d[rank_index],
10188 &mut gate_out[rank_index],
10189 &mut up_out[rank_index],
10190 n_sel,
10191 gate_bank.in_features,
10192 gate_bank.local_out,
10193 gate_bank.row_bytes,
10194 gate_bank.expert_bytes,
10195 rank_index,
10196 )?;
10197 }
10198 {
10199 let Nvfp4DeviceRoutesWorkspace {
10200 gate_out,
10201 up_out,
10202 sel,
10203 act_q,
10204 act_d,
10205 ..
10206 } = &mut *workspace;
10207 engine.silu_mul_scaled_q8_1_sel_ep_into(
10208 &gate_out[rank_index],
10209 &up_out[rank_index],
10210 &experts.macros_gate_dev[rank_index],
10211 &experts.macros_up_dev[rank_index],
10212 &sel[rank_index],
10213 activation_limit,
10214 &mut act_q[rank_index],
10215 &mut act_d[rank_index],
10216 local_out,
10217 n_sel,
10218 rank_index,
10219 )?;
10220 }
10221 let shard = &experts.down[rank_index];
10222 if shard.device_rank != rank_index || shard.local_in != local_out {
10223 return Err("NVFP4 EP2 down bank placement drifted".into());
10224 }
10225 {
10226 let Nvfp4DeviceRoutesWorkspace {
10227 sel,
10228 act_q,
10229 act_d,
10230 route_w,
10231 accumulator,
10232 ..
10233 } = &mut *workspace;
10234 engine.qmatvec_nvfp4_sel_down8_ep_into(
10235 &shard.bank,
10236 &sel[rank_index],
10237 &act_q[rank_index],
10238 &act_d[rank_index],
10239 &route_w[rank_index],
10240 &experts.macros_down_dev[rank_index],
10241 &mut accumulator[rank_index],
10242 n_sel,
10243 shard.local_in,
10244 shard.out_features,
10245 shard.row_bytes,
10246 shard.expert_bytes,
10247 local_out,
10248 local_out / 32,
10249 rank_index,
10250 )?;
10251 }
10252 return Ok(());
10253 }
10254 if !device_routed {
10255 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
10256 let folded = (0..n_sel)
10259 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
10260 .collect::<Vec<_>>();
10261 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
10262 engine.stream().memcpy_htod(&folded, &mut view)?;
10263 }
10264 let gate_bank = &experts.gate[rank_index];
10265 let up_bank = &experts.up[rank_index];
10266 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
10267 let gu_fused = nvfp4_bank_v2_on()
10270 && gate_bank.in_features == up_bank.in_features
10271 && gate_bank.local_out == up_bank.local_out
10272 && gate_bank.row_bytes == up_bank.row_bytes
10273 && gate_bank.expert_bytes == up_bank.expert_bytes;
10274 if gu_fused {
10275 let Nvfp4DeviceRoutesWorkspace {
10276 sel,
10277 gate_out,
10278 up_out,
10279 in_q,
10280 in_d,
10281 ..
10282 } = &mut *workspace;
10283 engine.qmatvec_nvfp4_sel_gu_into(
10284 &gate_bank.bank,
10285 &up_bank.bank,
10286 &sel[rank_index],
10287 &in_q[rank_index],
10288 &in_d[rank_index],
10289 &mut gate_out[rank_index],
10290 &mut up_out[rank_index],
10291 n_sel,
10292 gate_bank.in_features,
10293 gate_bank.local_out,
10294 gate_bank.row_bytes,
10295 gate_bank.expert_bytes,
10296 )?;
10297 } else {
10298 engine.qmatvec_nvfp4_sel_into(
10299 &gate_bank.bank,
10300 &workspace.sel[rank_index],
10301 aq,
10302 ad,
10303 &mut workspace.gate_out[rank_index],
10304 n_sel,
10305 gate_bank.in_features,
10306 gate_bank.local_out,
10307 gate_bank.row_bytes,
10308 gate_bank.expert_bytes,
10309 0,
10310 0,
10311 )?;
10312 engine.qmatvec_nvfp4_sel_into(
10313 &up_bank.bank,
10314 &workspace.sel[rank_index],
10315 aq,
10316 ad,
10317 &mut workspace.up_out[rank_index],
10318 n_sel,
10319 up_bank.in_features,
10320 up_bank.local_out,
10321 up_bank.row_bytes,
10322 up_bank.expert_bytes,
10323 0,
10324 0,
10325 )?;
10326 }
10327 {
10331 let Nvfp4DeviceRoutesWorkspace {
10332 gate_out,
10333 up_out,
10334 sel,
10335 act_q,
10336 act_d,
10337 ..
10338 } = &mut *workspace;
10339 engine.silu_mul_scaled_q8_1_sel_into(
10340 &gate_out[rank_index],
10341 &up_out[rank_index],
10342 &experts.macros_gate_dev[rank_index],
10343 &experts.macros_up_dev[rank_index],
10344 &sel[rank_index],
10345 activation_limit,
10346 &mut act_q[rank_index],
10347 &mut act_d[rank_index],
10348 local_out,
10349 n_sel,
10350 )?;
10351 }
10352 let shard = &experts.down[rank_index];
10353 if shard.device_rank != rank_index || shard.local_in != local_out {
10354 return Err(
10355 "NVFP4 device routes: down canonical shard placement drifted from \
10356 the gate/up column split"
10357 .into(),
10358 );
10359 }
10360 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
10367 {
10368 static SEEN: std::sync::Mutex<Vec<(bool, bool)>> =
10372 std::sync::Mutex::new(Vec::new());
10373 if std::env::var("MEMRA_SWEEP_TRACE").as_deref() == Ok("1") {
10374 let mut seen = SEEN.lock().unwrap();
10375 if !seen.contains(&(down8, device_routed)) {
10376 seen.push((down8, device_routed));
10377 eprintln!(
10378 "[sweep-trace] down8={down8} device_routed={device_routed} \
10379 sel_down8_on={} local_in={} n_sel={n_sel}",
10380 sel_down8_on(),
10381 shard.local_in
10382 );
10383 }
10384 }
10385 }
10386 if down8 {
10387 let Nvfp4DeviceRoutesWorkspace {
10388 sel,
10389 act_q,
10390 act_d,
10391 route_w,
10392 accumulator,
10393 ..
10394 } = &mut *workspace;
10395 engine.qmatvec_nvfp4_sel_down8_into(
10396 &shard.bank,
10397 &sel[rank_index],
10398 &act_q[rank_index],
10399 &act_d[rank_index],
10400 &route_w[rank_index],
10401 &experts.macros_down_dev[rank_index],
10402 &mut accumulator[rank_index],
10403 n_sel,
10404 shard.local_in,
10405 shard.out_features,
10406 shard.row_bytes,
10407 shard.expert_bytes,
10408 local_out,
10409 local_out / 32,
10410 )?;
10411 } else {
10412 let Nvfp4DeviceRoutesWorkspace {
10413 sel,
10414 act_q,
10415 act_d,
10416 partial,
10417 ..
10418 } = &mut *workspace;
10419 engine.qmatvec_nvfp4_sel_into(
10420 &shard.bank,
10421 &sel[rank_index],
10422 &act_q[rank_index],
10423 &act_d[rank_index],
10424 &mut partial[rank_index],
10425 n_sel,
10426 shard.local_in,
10427 shard.out_features,
10428 shard.row_bytes,
10429 shard.expert_bytes,
10430 local_out,
10431 local_out / 32,
10432 )?;
10433 }
10434 if !down8 {
10439 let Nvfp4DeviceRoutesWorkspace {
10440 partial,
10441 combine_w,
10442 route_w,
10443 sel,
10444 accumulator,
10445 ..
10446 } = &mut *workspace;
10447 if device_routed {
10448 engine.axpy_rows_seq_md_into(
10449 &partial[rank_index],
10450 &route_w[rank_index],
10451 &experts.macros_down_dev[rank_index],
10452 &sel[rank_index],
10453 &mut accumulator[rank_index],
10454 experts.input_width,
10455 n_sel,
10456 )?;
10457 } else {
10458 engine.axpy_rows_seq_into(
10459 &partial[rank_index],
10460 &combine_w[rank_index],
10461 &mut accumulator[rank_index],
10462 experts.input_width,
10463 n_sel,
10464 )?;
10465 }
10466 }
10467 }
10468 Ok(())
10469 }
10470
10471 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10479 &self,
10480 experts: &ResidentNvfp4TensorParallel,
10481 e: &Engine,
10482 input_dev: &crate::CudaSlice<f32>,
10483 selected: &[usize],
10484 route_weights: &[f32],
10485 experts_per_token: usize,
10486 activation_limit: Option<f32>,
10487 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10488 if input_dev.len() != experts.input_width {
10489 return Err(format!(
10490 "NVFP4 device-io routes input {} != width {}",
10491 input_dev.len(),
10492 experts.input_width
10493 )
10494 .into());
10495 }
10496 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10497 return Err(format!(
10498 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10499 selected.len(),
10500 route_weights.len(),
10501 )
10502 .into());
10503 }
10504 if !route_weights.iter().all(|weight| weight.is_finite()) {
10505 return Err("NVFP4 device route weights contain a non-finite value".into());
10506 }
10507 let world = self.ranks.len();
10508 if world != NVFP4_CANONICAL_ROW_SHARDS {
10509 return Err(format!(
10510 "NVFP4 device routes require world == canonical shard grid \
10511 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10512 )
10513 .into());
10514 }
10515 let local_out = experts.expert_width / world;
10516 let n_sel = experts_per_token;
10517
10518 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10519 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10520 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10521 let started = timing.then(std::time::Instant::now);
10522
10523 let mut workspace_guard = experts
10524 .device_workspace
10525 .lock()
10526 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10527 if workspace_guard.is_none() {
10528 drop(workspace_guard);
10529 let zero = vec![0.0f32; experts.input_width];
10532 let zero_sel = vec![0usize; n_sel];
10533 let zero_w = vec![0.0f32; n_sel];
10534 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10535 experts,
10536 &zero,
10537 &zero_sel,
10538 &zero_w,
10539 n_sel,
10540 activation_limit,
10541 )?;
10542 workspace_guard = experts
10543 .device_workspace
10544 .lock()
10545 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10546 }
10547 let workspace = workspace_guard
10548 .as_mut()
10549 .expect("NVFP4 device routes workspace initialized above");
10550 if workspace.n_sel != n_sel {
10551 return Err(format!(
10552 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10553 workspace.n_sel
10554 )
10555 .into());
10556 }
10557 for &expert in selected {
10558 if expert >= experts.expert_count {
10559 return Err(format!(
10560 "NVFP4 device selected expert {expert} outside 0..{}",
10561 experts.expert_count
10562 )
10563 .into());
10564 }
10565 }
10566 let sel_i32 = selected
10567 .iter()
10568 .map(|&expert| expert as i32)
10569 .collect::<Vec<_>>();
10570
10571 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10575 if *device != e.ctx().ordinal() {
10576 return Err("NVFP4 device-io routes engine changed".into());
10577 }
10578 } else {
10579 let _main = e.gpu.enter_main()?;
10580 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10581 }
10582 {
10583 let _main = e.gpu.enter_main()?;
10584 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10585 ev_entry.record(&e.stream())?;
10586 }
10587 for (rank_index, engine) in self.ranks.iter().enumerate() {
10588 let _main = engine.gpu.enter_main()?;
10589 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10590 engine.stream().wait(ev_entry)?;
10591 {
10592 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10593 engine
10594 .stream()
10595 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10596 }
10597 {
10598 let Nvfp4DeviceRoutesWorkspace {
10599 input, in_q, in_d, ..
10600 } = &mut *workspace;
10601 engine.quantize_q8_1_into(
10602 &input[rank_index],
10603 1,
10604 experts.input_width,
10605 &mut in_q[rank_index],
10606 &mut in_d[rank_index],
10607 )?;
10608 }
10609 }
10610 self.nvfp4_routes_batched_sweeps(
10611 experts,
10612 workspace,
10613 selected,
10614 route_weights,
10615 &sel_i32,
10616 local_out,
10617 n_sel,
10618 activation_limit,
10619 false,
10620 )?;
10621
10622 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10628 let _main = engine.gpu.enter_main()?;
10629 workspace.ev_rank[rank_index].record(&engine.stream())?;
10630 }
10631 if moe_direct_on() && self.ranks.len() == 2 {
10632 {
10639 let root = &self.ranks[0];
10640 let _main = root.gpu.enter_main()?;
10641 workspace
10642 .ev_done
10643 .as_ref()
10644 .expect("device routes done event")
10645 .record(&root.stream())?;
10646 }
10647 let _main = e.gpu.enter_main()?;
10648 e.stream().wait(
10649 workspace
10650 .ev_done
10651 .as_ref()
10652 .expect("device routes done event"),
10653 )?;
10654 for ev in workspace.ev_rank.iter().skip(1) {
10655 e.stream().wait(ev)?;
10656 }
10657 let mut output = e.uninit(experts.input_width)?;
10658 e.add(
10659 &workspace.accumulator[0],
10660 &workspace.accumulator[1],
10661 &mut output,
10662 experts.input_width,
10663 )?;
10664 let output = output;
10665 if let Some(started) = started {
10666 use std::sync::atomic::Ordering;
10667 let ns = TIMING_NS
10668 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10669 + started.elapsed().as_nanos() as u64;
10670 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10671 if calls % 430 == 0 {
10672 eprintln!(
10673 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10674 ns as f64 / 1.0e6,
10675 ns as f64 / calls as f64 / 1.0e3,
10676 );
10677 }
10678 }
10679 return Ok(output);
10680 }
10681 {
10682 let root = &self.ranks[0];
10683 let _main = root.gpu.enter_main()?;
10684 for ev in workspace.ev_rank.iter().skip(1) {
10685 root.stream().wait(ev)?;
10686 }
10687 root.stream()
10688 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10689 {
10690 let Nvfp4DeviceRoutesWorkspace {
10691 accumulator,
10692 remote,
10693 combined,
10694 ..
10695 } = &mut *workspace;
10696 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10697 }
10698 workspace
10699 .ev_done
10700 .as_ref()
10701 .expect("device routes done event")
10702 .record(&root.stream())?;
10703 }
10704 let output = {
10705 let _main = e.gpu.enter_main()?;
10706 e.stream().wait(
10707 workspace
10708 .ev_done
10709 .as_ref()
10710 .expect("device routes done event"),
10711 )?;
10712 let mut output = e.uninit(experts.input_width)?;
10715 e.stream().memcpy_dtod(
10716 &workspace.combined.slice(0..experts.input_width),
10717 &mut output.slice_mut(0..experts.input_width),
10718 )?;
10719 output
10720 };
10721 if let Some(started) = started {
10722 use std::sync::atomic::Ordering;
10723 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10724 + started.elapsed().as_nanos() as u64;
10725 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10726 if calls % 430 == 0 {
10727 eprintln!(
10728 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10729 ns as f64 / 1.0e6,
10730 ns as f64 / calls as f64 / 1.0e3,
10731 );
10732 }
10733 }
10734 Ok(output)
10735 }
10736
10737 #[allow(clippy::too_many_arguments)]
10743 pub fn nvfp4_routes_prestage(
10748 &self,
10749 experts: &ResidentNvfp4TensorParallel,
10750 e: &Engine,
10751 input_dev: &crate::CudaSlice<f32>,
10752 ) -> Result<bool, Box<dyn std::error::Error>> {
10753 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
10754 }
10755
10756 pub fn nvfp4_routes_prestage_with(
10762 &self,
10763 experts: &ResidentNvfp4TensorParallel,
10764 e: &Engine,
10765 input_dev: &crate::CudaSlice<f32>,
10766 rank1_router: impl FnOnce(
10767 &Engine,
10768 &crate::CudaSlice<f32>,
10769 &mut crate::CudaSlice<i32>,
10770 &mut crate::CudaSlice<f32>,
10771 ) -> Result<bool, Box<dyn std::error::Error>>,
10772 ) -> Result<bool, Box<dyn std::error::Error>> {
10773 if !routes_prestage_on() || step_tp_graph_enabled()? {
10774 return Ok(false);
10775 }
10776 if input_dev.len() != experts.input_width {
10777 return Err("NVFP4 prestage input width mismatch".into());
10778 }
10779 let mut workspace_guard = experts
10780 .device_workspace
10781 .lock()
10782 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10783 let Some(workspace) = workspace_guard.as_mut() else {
10784 return Ok(false);
10785 };
10786 if workspace.ev_input.is_none() {
10787 let _main = e.gpu.enter_main()?;
10788 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10789 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
10790 return Err("NVFP4 prestage engine changed".into());
10791 }
10792 {
10793 let _main = e.gpu.enter_main()?;
10794 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10795 ev.record(&e.stream())?;
10796 }
10797 for (rank_index, engine) in self.ranks.iter().enumerate() {
10798 let _main = engine.gpu.enter_main()?;
10799 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10800 engine.stream().wait(ev)?;
10801 {
10802 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10803 engine
10804 .stream()
10805 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10806 }
10807 {
10808 let Nvfp4DeviceRoutesWorkspace {
10809 input, in_q, in_d, ..
10810 } = &mut *workspace;
10811 engine.quantize_q8_1_into(
10812 &input[rank_index],
10813 1,
10814 experts.input_width,
10815 &mut in_q[rank_index],
10816 &mut in_d[rank_index],
10817 )?;
10818 }
10819 }
10820 if self.ranks.len() == 2 {
10821 let rank1 = &self.ranks[1];
10822 let _r1 = rank1.gpu.enter_main()?;
10823 let Nvfp4DeviceRoutesWorkspace {
10824 input,
10825 sel,
10826 route_w,
10827 ..
10828 } = &mut *workspace;
10829 let (in1, rest_sel) = (&input[1], &mut sel[1]);
10830 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
10831 workspace.rank1_routed = true;
10832 }
10833 }
10834 workspace.prestaged = true;
10835 Ok(true)
10836 }
10837
10838 #[allow(clippy::too_many_arguments)]
10849 pub fn run_tensor_parallel_routes_nvfp4_device_routed_tn(
10850 &self,
10851 experts: &ResidentNvfp4TensorParallel,
10852 e: &Engine,
10853 z_t: &crate::CudaSlice<f32>,
10854 sel_d: &crate::CudaSlice<i32>,
10855 w_d: &crate::CudaSlice<f32>,
10856 t: usize,
10857 n_sel_col: usize,
10858 activation_limit: Option<f32>,
10859 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10860 let world = self.ranks.len();
10861 if world != NVFP4_CANONICAL_ROW_SHARDS {
10862 return Err("NVFP4 t-row routes require the canonical 2-shard grid".into());
10863 }
10864 let width = experts.input_width;
10865 let n_sel = t * n_sel_col;
10866 if t == 0 || t > 32 || z_t.len() < t * width || sel_d.len() < n_sel || w_d.len() < n_sel {
10867 return Err("NVFP4 t-row routes geometry".into());
10868 }
10869 if !nvfp4_bank_v2_on() {
10870 return Err("NVFP4 t-row routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
10871 }
10872 let local_out = experts.expert_width / world;
10873 let mut guard = experts
10874 .t2_workspace
10875 .lock()
10876 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
10877 if guard.as_ref().is_none_or(|ws| ws.n_sel != n_sel) {
10878 let mut input2 = Vec::new();
10879 let mut in_q2 = Vec::new();
10880 let mut in_d2 = Vec::new();
10881 let mut sel2 = Vec::new();
10882 let mut route_w2 = Vec::new();
10883 let mut gate_out2 = Vec::new();
10884 let mut up_out2 = Vec::new();
10885 let mut act_q2 = Vec::new();
10886 let mut act_d2 = Vec::new();
10887 let mut partial2 = Vec::new();
10888 let mut acc_a = Vec::new();
10889 let mut acc_b = Vec::new();
10890 let mut acc2 = Vec::new();
10891 let mut ev_rank = Vec::new();
10892 for engine in &self.ranks {
10893 let _m = engine.gpu.enter_main()?;
10894 input2.push(engine.uninit(t * width)?);
10895 in_q2.push(engine.alloc_i8_uninit(t * width)?);
10896 in_d2.push(engine.uninit(t * (width / 32))?);
10897 sel2.push(engine.htod_i32(&vec![0i32; n_sel])?);
10898 route_w2.push(engine.uninit(n_sel)?);
10899 gate_out2.push(engine.uninit(n_sel * local_out)?);
10900 up_out2.push(engine.uninit(n_sel * local_out)?);
10901 act_q2.push(engine.alloc_i8_uninit(n_sel * local_out)?);
10902 act_d2.push(engine.uninit(n_sel * (local_out / 32))?);
10903 partial2.push(engine.uninit(n_sel * width)?);
10904 acc_a.push(engine.uninit(width)?);
10905 acc_b.push(engine.uninit(width)?);
10906 acc2.push(engine.uninit(t * width)?);
10907 ev_rank.push(engine.ctx().new_event(None)?);
10908 }
10909 let root = &self.ranks[0];
10910 let (peer_a, peer_b, omix_a, omix_b, peer2, omix2, ev_root) = {
10911 let _m = root.gpu.enter_main()?;
10912 (
10913 root.uninit(width)?,
10914 root.uninit(width)?,
10915 root.uninit(width)?,
10916 root.uninit(width)?,
10917 root.uninit(t * width)?,
10918 root.uninit(t * width)?,
10919 root.ctx().new_event(None)?,
10920 )
10921 };
10922 let ev_entry = {
10923 let _m = e.gpu.enter_main()?;
10924 e.ctx().new_event(None)?
10925 };
10926 *guard = Some(Nvfp4T2Workspace {
10927 input2,
10928 in_q2,
10929 in_d2,
10930 sel2,
10931 route_w2,
10932 gate_out2,
10933 up_out2,
10934 act_q2,
10935 act_d2,
10936 partial2,
10937 acc_a,
10938 acc_b,
10939 acc2,
10940 peer2,
10941 omix2,
10942 peer_a,
10943 peer_b,
10944 omix_a,
10945 omix_b,
10946 ev_entry,
10947 ev_rank,
10948 ev_root,
10949 n_sel,
10950 e_device: e.ctx().ordinal(),
10951 });
10952 }
10953 let ws = guard.as_mut().expect("armed above");
10954 if ws.e_device != e.ctx().ordinal() {
10955 return Err("NVFP4 t2 routes engine changed".into());
10956 }
10957 {
10958 let _main = e.gpu.enter_main()?;
10959 ws.ev_entry.record(&e.stream())?;
10960 }
10961 let down8 = sel_down8_on() && (local_out >> 5) <= 32 && n_sel_col <= 8;
10965 if !down8 && t != 2 {
10966 return Err(
10967 "NVFP4 t-row routes at t != 2 require MEMRA_SEL_DOWN8=1 (fused rows kernel)".into(),
10968 );
10969 }
10970 for rank in 0..world {
10971 let engine = &self.ranks[rank];
10972 let _main = engine.gpu.enter_main()?;
10973 engine.stream().wait(&ws.ev_entry)?;
10974 {
10975 let mut dst = ws.input2[rank].slice_mut(0..t * width);
10976 engine
10977 .stream()
10978 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
10979 }
10980 {
10981 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
10982 engine
10983 .stream()
10984 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10985 }
10986 {
10987 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
10988 engine
10989 .stream()
10990 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10991 }
10992 {
10993 let Nvfp4T2Workspace {
10994 input2,
10995 in_q2,
10996 in_d2,
10997 ..
10998 } = &mut *ws;
10999 engine.quantize_q8_1_into(
11000 &input2[rank],
11001 t,
11002 width,
11003 &mut in_q2[rank],
11004 &mut in_d2[rank],
11005 )?;
11006 }
11007 let gate_bank = &experts.gate[rank];
11008 let up_bank = &experts.up[rank];
11009 if gate_bank.in_features != up_bank.in_features
11010 || gate_bank.local_out != up_bank.local_out
11011 || gate_bank.row_bytes != up_bank.row_bytes
11012 || gate_bank.expert_bytes != up_bank.expert_bytes
11013 {
11014 return Err("NVFP4 t-row routes need matched gate/up bank geometry".into());
11015 }
11016 {
11017 let Nvfp4T2Workspace {
11018 sel2,
11019 in_q2,
11020 in_d2,
11021 gate_out2,
11022 up_out2,
11023 ..
11024 } = &mut *ws;
11025 engine.qmatvec_nvfp4_sel_gu_tcol_into(
11026 &gate_bank.bank,
11027 &up_bank.bank,
11028 &sel2[rank],
11029 &in_q2[rank],
11030 &in_d2[rank],
11031 &mut gate_out2[rank],
11032 &mut up_out2[rank],
11033 n_sel,
11034 n_sel_col,
11035 gate_bank.in_features,
11036 gate_bank.local_out,
11037 gate_bank.row_bytes,
11038 gate_bank.expert_bytes,
11039 width,
11040 width / 32,
11041 )?;
11042 }
11043 {
11044 let Nvfp4T2Workspace {
11045 gate_out2,
11046 up_out2,
11047 sel2,
11048 act_q2,
11049 act_d2,
11050 ..
11051 } = &mut *ws;
11052 engine.silu_mul_scaled_q8_1_sel_into(
11053 &gate_out2[rank],
11054 &up_out2[rank],
11055 &experts.macros_gate_dev[rank],
11056 &experts.macros_up_dev[rank],
11057 &sel2[rank],
11058 activation_limit,
11059 &mut act_q2[rank],
11060 &mut act_d2[rank],
11061 local_out,
11062 n_sel,
11063 )?;
11064 }
11065 let shard = &experts.down[rank];
11066 if shard.device_rank != rank || shard.local_in != local_out {
11067 return Err("NVFP4 t-row routes: down shard placement drifted".into());
11068 }
11069 if down8 {
11073 let Nvfp4T2Workspace {
11074 sel2,
11075 act_q2,
11076 act_d2,
11077 route_w2,
11078 acc2,
11079 ..
11080 } = &mut *ws;
11081 engine.qmatvec_nvfp4_sel_down8_rows_into(
11082 &shard.bank,
11083 &sel2[rank],
11084 &act_q2[rank],
11085 &act_d2[rank],
11086 &route_w2[rank],
11087 &experts.macros_down_dev[rank],
11088 &mut acc2[rank],
11089 t,
11090 n_sel_col,
11091 shard.local_in,
11092 shard.out_features,
11093 shard.row_bytes,
11094 shard.expert_bytes,
11095 local_out,
11096 local_out / 32,
11097 )?;
11098 } else {
11099 {
11100 let Nvfp4T2Workspace {
11101 sel2,
11102 act_q2,
11103 act_d2,
11104 partial2,
11105 ..
11106 } = &mut *ws;
11107 engine.qmatvec_nvfp4_sel_into(
11108 &shard.bank,
11109 &sel2[rank],
11110 &act_q2[rank],
11111 &act_d2[rank],
11112 &mut partial2[rank],
11113 n_sel,
11114 shard.local_in,
11115 shard.out_features,
11116 shard.row_bytes,
11117 shard.expert_bytes,
11118 local_out,
11119 local_out / 32,
11120 )?;
11121 }
11122 let Nvfp4T2Workspace {
11123 partial2,
11124 route_w2,
11125 sel2,
11126 acc_a,
11127 acc_b,
11128 ..
11129 } = &mut *ws;
11130 engine.axpy_rows_seq_md_off_into(
11131 &partial2[rank],
11132 &route_w2[rank],
11133 &experts.macros_down_dev[rank],
11134 &sel2[rank],
11135 &mut acc_a[rank],
11136 width,
11137 n_sel_col,
11138 0,
11139 )?;
11140 engine.axpy_rows_seq_md_off_into(
11141 &partial2[rank],
11142 &route_w2[rank],
11143 &experts.macros_down_dev[rank],
11144 &sel2[rank],
11145 &mut acc_b[rank],
11146 width,
11147 n_sel_col,
11148 n_sel_col,
11149 )?;
11150 }
11151 if rank != 0 {
11152 ws.ev_rank[rank].record(&engine.stream())?;
11153 }
11154 }
11155 let root = &self.ranks[0];
11156 {
11157 let _main = root.gpu.enter_main()?;
11158 for ev in ws.ev_rank.iter().skip(1) {
11159 root.stream().wait(ev)?;
11160 }
11161 if down8 {
11162 let Nvfp4T2Workspace {
11165 acc2, peer2, omix2, ..
11166 } = &mut *ws;
11167 {
11168 let mut dst = peer2.slice_mut(0..t * width);
11169 root.stream()
11170 .memcpy_dtod(&acc2[1].slice(0..t * width), &mut dst)?;
11171 }
11172 root.add(&acc2[0], peer2, omix2, t * width)?;
11173 } else {
11174 let Nvfp4T2Workspace {
11175 acc_a,
11176 acc_b,
11177 peer_a,
11178 peer_b,
11179 omix_a,
11180 omix_b,
11181 ..
11182 } = &mut *ws;
11183 {
11184 let mut dst = peer_a.slice_mut(0..width);
11185 root.stream()
11186 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
11187 }
11188 {
11189 let mut dst = peer_b.slice_mut(0..width);
11190 root.stream()
11191 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
11192 }
11193 root.add(&acc_a[0], peer_a, omix_a, width)?;
11194 root.add(&acc_b[0], peer_b, omix_b, width)?;
11195 }
11196 ws.ev_root.record(&root.stream())?;
11197 }
11198 let _main = e.gpu.enter_main()?;
11199 e.stream().wait(&ws.ev_root)?;
11200 let mut out = e.uninit(t * width)?;
11201 if down8 {
11202 e.stream().memcpy_dtod(
11203 &ws.omix2.slice(0..t * width),
11204 &mut out.slice_mut(0..t * width),
11205 )?;
11206 } else {
11207 e.stream()
11208 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
11209 e.stream().memcpy_dtod(
11210 &ws.omix_b.slice(0..width),
11211 &mut out.slice_mut(width..2 * width),
11212 )?;
11213 }
11214 Ok(out)
11215 }
11216
11217 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
11218 &self,
11219 experts: &ResidentNvfp4TensorParallel,
11220 e: &Engine,
11221 input_dev: &crate::CudaSlice<f32>,
11222 sel_d: &crate::CudaSlice<i32>,
11223 w_d: &crate::CudaSlice<f32>,
11224 experts_per_token: usize,
11225 activation_limit: Option<f32>,
11226 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11227 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11228 experts,
11229 e,
11230 input_dev,
11231 sel_d,
11232 w_d,
11233 experts_per_token,
11234 activation_limit,
11235 || Ok(()),
11236 )
11237 }
11238
11239 #[allow(clippy::too_many_arguments)]
11245 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11246 &self,
11247 experts: &ResidentNvfp4TensorParallel,
11248 e: &Engine,
11249 input_dev: &crate::CudaSlice<f32>,
11250 sel_d: &crate::CudaSlice<i32>,
11251 w_d: &crate::CudaSlice<f32>,
11252 experts_per_token: usize,
11253 activation_limit: Option<f32>,
11254 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11255 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11256 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11257 experts,
11258 e,
11259 input_dev,
11260 sel_d,
11261 w_d,
11262 experts_per_token,
11263 activation_limit,
11264 pre_join,
11265 None,
11266 )
11267 }
11268
11269 #[allow(clippy::too_many_arguments)]
11274 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11275 &self,
11276 experts: &ResidentNvfp4TensorParallel,
11277 e: &Engine,
11278 input_dev: &crate::CudaSlice<f32>,
11279 sel_d: &crate::CudaSlice<i32>,
11280 w_d: &crate::CudaSlice<f32>,
11281 experts_per_token: usize,
11282 activation_limit: Option<f32>,
11283 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11284 post_add: Option<(u64, u64)>,
11285 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11286 if input_dev.len() != experts.input_width {
11287 return Err(format!(
11288 "NVFP4 device-routed input {} != width {}",
11289 input_dev.len(),
11290 experts.input_width
11291 )
11292 .into());
11293 }
11294 let n_sel = experts_per_token;
11295 if sel_d.len() < n_sel || w_d.len() < n_sel {
11296 return Err(format!(
11297 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
11298 sel_d.len(),
11299 w_d.len()
11300 )
11301 .into());
11302 }
11303 let world = self.ranks.len();
11304 if world != NVFP4_CANONICAL_ROW_SHARDS {
11305 return Err(format!(
11306 "NVFP4 device routes require world == canonical shard grid \
11307 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
11308 )
11309 .into());
11310 }
11311 let local_out = if experts.ep2 {
11312 experts.expert_width
11313 } else {
11314 experts.expert_width / world
11315 };
11316
11317 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11318 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11319 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11320 let started = timing.then(std::time::Instant::now);
11321
11322 let mut workspace_guard = experts
11323 .device_workspace
11324 .lock()
11325 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11326 if workspace_guard.is_none() {
11327 drop(workspace_guard);
11328 let zero = vec![0.0f32; experts.input_width];
11329 let zero_sel = vec![0usize; n_sel];
11330 let zero_w = vec![0.0f32; n_sel];
11331 let _ = self.run_tensor_parallel_routes_nvfp4_device(
11332 experts,
11333 &zero,
11334 &zero_sel,
11335 &zero_w,
11336 n_sel,
11337 activation_limit,
11338 )?;
11339 workspace_guard = experts
11340 .device_workspace
11341 .lock()
11342 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11343 }
11344 let workspace = workspace_guard
11345 .as_mut()
11346 .expect("NVFP4 device routes workspace initialized above");
11347 if workspace.n_sel != n_sel {
11348 return Err(format!(
11349 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
11350 workspace.n_sel
11351 )
11352 .into());
11353 }
11354
11355 if step_tp_graph_enabled()? {
11360 if experts.ep2 {
11361 return Err(
11362 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
11363 co-gated; unset one"
11364 .into(),
11365 );
11366 }
11367 if workspace.dev_route_e.is_none() {
11368 let _main = e.gpu.enter_main()?;
11369 workspace.dev_route_e = Some((
11370 e.htod_i32(&vec![0i32; n_sel])?,
11371 e.htod(&vec![0.0f32; n_sel])?,
11372 ));
11373 }
11374 if workspace.in_stage_e.is_none() {
11375 let _main = e.gpu.enter_main()?;
11376 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11377 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11378 }
11379 if workspace.routes_graph.is_none() {
11380 let graph = self.nvfp4_routes_build_graph(
11381 experts,
11382 workspace,
11383 local_out,
11384 n_sel,
11385 activation_limit,
11386 )?;
11387 workspace.routes_graph = Some(graph);
11388 eprintln!(
11389 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
11390 children=3 updates=none performance_claim=false"
11391 );
11392 }
11393 let output = {
11394 let _main = e.gpu.enter_main()?;
11395 {
11396 let (sel_e, w_e) = workspace
11397 .dev_route_e
11398 .as_mut()
11399 .expect("device route staging set above");
11400 {
11401 let mut dst = sel_e.slice_mut(0..n_sel);
11402 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11403 }
11404 {
11405 let mut dst = w_e.slice_mut(0..n_sel);
11406 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11407 }
11408 }
11409 {
11410 let in_stage = workspace
11411 .in_stage_e
11412 .as_mut()
11413 .expect("graph staging set above");
11414 let mut dst = in_stage.slice_mut(0..experts.input_width);
11415 e.stream()
11416 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
11417 }
11418 unsafe {
11419 let r = cudarc::driver::sys::cuGraphLaunch(
11420 workspace
11421 .routes_graph
11422 .as_ref()
11423 .expect("routes graph built above")
11424 .exec,
11425 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11426 );
11427 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11428 return Err(format!("routes graph launch: {r:?}").into());
11429 }
11430 }
11431 let mut output = e.uninit(experts.input_width)?;
11432 {
11433 let out_stage = workspace
11434 .out_stage_e
11435 .as_ref()
11436 .expect("graph staging set above");
11437 e.stream().memcpy_dtod(
11438 &out_stage.slice(0..experts.input_width),
11439 &mut output.slice_mut(0..experts.input_width),
11440 )?;
11441 }
11442 output
11443 };
11444 if let Some(started) = started {
11445 use std::sync::atomic::Ordering;
11446 let ns = TIMING_NS
11447 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11448 + started.elapsed().as_nanos() as u64;
11449 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11450 if calls % 430 == 0 {
11451 eprintln!(
11452 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11453 ns as f64 / 1.0e6,
11454 ns as f64 / calls as f64 / 1.0e3,
11455 );
11456 }
11457 }
11458 return Ok(output);
11459 }
11460
11461 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11465 if *device != e.ctx().ordinal() {
11466 return Err("NVFP4 device-routed routes engine changed".into());
11467 }
11468 } else {
11469 let _main = e.gpu.enter_main()?;
11470 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11471 }
11472 if workspace.dev_route_e.is_none() {
11473 let _main = e.gpu.enter_main()?;
11474 workspace.dev_route_e = Some((
11475 e.htod_i32(&vec![0i32; n_sel])?,
11476 e.htod(&vec![0.0f32; n_sel])?,
11477 ));
11478 }
11479 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
11485 let e_device = e.ctx().ordinal();
11486 let rank1_routed_peek = workspace.rank1_routed;
11488 let stage_needed = !mirror
11489 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
11490 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
11491 });
11492 {
11493 let _main = e.gpu.enter_main()?;
11494 if stage_needed {
11495 let (sel_e, w_e) = workspace
11496 .dev_route_e
11497 .as_mut()
11498 .expect("device route staging set above");
11499 {
11500 let mut dst = sel_e.slice_mut(0..n_sel);
11501 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11502 }
11503 {
11504 let mut dst = w_e.slice_mut(0..n_sel);
11505 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11506 }
11507 }
11508 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11509 ev_entry.record(&e.stream())?;
11510 }
11511 let prestaged = std::mem::take(&mut workspace.prestaged);
11514 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
11515 for (rank_index, engine) in self.ranks.iter().enumerate() {
11516 let _main = engine.gpu.enter_main()?;
11517 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11518 engine.stream().wait(ev_entry)?;
11519 if !prestaged {
11520 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11521 engine
11522 .stream()
11523 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11524 }
11525 if !(rank1_routed && rank_index == 1) {
11526 let same_dev = engine.ctx().ordinal() == e_device;
11530 if mirror {
11531 let Nvfp4DeviceRoutesWorkspace {
11534 sel,
11535 route_w,
11536 dev_route_e,
11537 ..
11538 } = &mut *workspace;
11539 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
11540 if same_dev {
11541 (sel_d, w_d)
11542 } else {
11543 let (sel_e, w_e) = dev_route_e
11544 .as_ref()
11545 .expect("device route staging set above");
11546 (sel_e, w_e)
11547 };
11548 engine.moe_sel_w_mirror(
11549 src_sel,
11550 src_w,
11551 &mut sel[rank_index],
11552 &mut route_w[rank_index],
11553 n_sel,
11554 )?;
11555 } else {
11556 let (sel_e, w_e) = workspace
11557 .dev_route_e
11558 .as_ref()
11559 .expect("device route staging set above");
11560 {
11561 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
11562 engine
11563 .stream()
11564 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
11565 }
11566 {
11567 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
11568 engine
11569 .stream()
11570 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11571 }
11572 }
11573 }
11574 if !prestaged {
11575 let Nvfp4DeviceRoutesWorkspace {
11576 input, in_q, in_d, ..
11577 } = &mut *workspace;
11578 engine.quantize_q8_1_into(
11579 &input[rank_index],
11580 1,
11581 experts.input_width,
11582 &mut in_q[rank_index],
11583 &mut in_d[rank_index],
11584 )?;
11585 }
11586 }
11587 self.nvfp4_routes_batched_sweeps(
11588 experts,
11589 workspace,
11590 &[],
11591 &[],
11592 &[],
11593 local_out,
11594 n_sel,
11595 activation_limit,
11596 true,
11597 )?;
11598
11599 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11602 let _main = engine.gpu.enter_main()?;
11603 workspace.ev_rank[rank_index].record(&engine.stream())?;
11604 }
11605 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
11608 let mut ticket = 0u32;
11609 if memops {
11610 use cudarc::driver::sys;
11611 if workspace.fence_flags_raw == 0 {
11612 let root = &self.ranks[0];
11613 let _main = root.gpu.enter_main()?;
11614 let mut ptr: sys::CUdeviceptr = 0;
11615 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
11616 if r != sys::CUresult::CUDA_SUCCESS {
11617 return Err(format!("fence flag alloc: {r:?}").into());
11618 }
11619 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
11620 if r != sys::CUresult::CUDA_SUCCESS {
11621 return Err(format!("fence flag memset: {r:?}").into());
11622 }
11623 workspace.fence_flags_raw = ptr as u64;
11624 }
11625 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
11626 ticket = workspace.fence_ticket;
11627 let base = workspace.fence_flags_raw;
11628 if fence_rank1_on() {
11634 let peer = &self.ranks[1];
11635 let _pmain = peer.gpu.enter_main()?;
11636 peer.ring_flag_raw(base, ticket)?;
11637 }
11638 {
11639 let root = &self.ranks[0];
11640 let _main = root.gpu.enter_main()?;
11641 let r = unsafe {
11642 sys::cuStreamWriteValue32_v2(
11643 root.stream().cu_stream() as sys::CUstream,
11644 (base + 4) as sys::CUdeviceptr,
11645 ticket,
11646 0,
11647 )
11648 };
11649 if r != sys::CUresult::CUDA_SUCCESS {
11650 return Err(format!("fence write root: {r:?}").into());
11651 }
11652 }
11653 }
11654 pre_join()?;
11657
11658 if moe_direct_on() && self.ranks.len() == 2 {
11659 let _main = e.gpu.enter_main()?;
11666 if memops {
11667 use cudarc::driver::sys;
11668 let base = workspace.fence_flags_raw;
11669 let r = unsafe {
11670 sys::cuStreamWaitValue32_v2(
11671 e.stream().cu_stream() as sys::CUstream,
11672 (base + 4) as sys::CUdeviceptr,
11673 ticket,
11674 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11675 )
11676 };
11677 if r != sys::CUresult::CUDA_SUCCESS {
11678 return Err(format!("fence wait: {r:?}").into());
11679 }
11680 if fence_rank1_on() {
11681 let r = unsafe {
11683 sys::cuStreamWaitValue32_v2(
11684 e.stream().cu_stream() as sys::CUstream,
11685 base as sys::CUdeviceptr,
11686 ticket,
11687 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11688 )
11689 };
11690 if r != sys::CUresult::CUDA_SUCCESS {
11691 return Err(format!("fence wait rank1: {r:?}").into());
11692 }
11693 } else {
11694 for ev in workspace.ev_rank.iter().skip(1) {
11695 e.stream().wait(ev)?;
11696 }
11697 }
11698 } else {
11699 {
11700 let root = &self.ranks[0];
11701 let _rmain = root.gpu.enter_main()?;
11702 workspace
11703 .ev_done
11704 .as_ref()
11705 .expect("device routes done event")
11706 .record(&root.stream())?;
11707 }
11708 e.stream().wait(
11709 workspace
11710 .ev_done
11711 .as_ref()
11712 .expect("device routes done event"),
11713 )?;
11714 for ev in workspace.ev_rank.iter().skip(1) {
11715 e.stream().wait(ev)?;
11716 }
11717 }
11718 let mut output = e.uninit(experts.input_width)?;
11719 if let Some((sh_raw, scale_raw)) = post_add {
11720 e.add3_raw(
11723 &workspace.accumulator[0],
11724 &workspace.accumulator[1],
11725 sh_raw,
11726 scale_raw,
11727 &mut output,
11728 experts.input_width,
11729 )?;
11730 } else {
11731 e.add(
11732 &workspace.accumulator[0],
11733 &workspace.accumulator[1],
11734 &mut output,
11735 experts.input_width,
11736 )?;
11737 }
11738 let output = output;
11739 if let Some(started) = started {
11740 use std::sync::atomic::Ordering;
11741 let ns = TIMING_NS
11742 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11743 + started.elapsed().as_nanos() as u64;
11744 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11745 if calls % 430 == 0 {
11746 eprintln!(
11747 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11748 ns as f64 / 1.0e6,
11749 ns as f64 / calls as f64 / 1.0e3,
11750 );
11751 }
11752 }
11753 return Ok(output);
11754 }
11755 {
11756 let root = &self.ranks[0];
11757 let _main = root.gpu.enter_main()?;
11758 for ev in workspace.ev_rank.iter().skip(1) {
11759 root.stream().wait(ev)?;
11760 }
11761 root.stream()
11762 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11763 {
11764 let Nvfp4DeviceRoutesWorkspace {
11765 accumulator,
11766 remote,
11767 combined,
11768 ..
11769 } = &mut *workspace;
11770 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11771 }
11772 workspace
11773 .ev_done
11774 .as_ref()
11775 .expect("device routes done event")
11776 .record(&root.stream())?;
11777 }
11778 let output = {
11779 let _main = e.gpu.enter_main()?;
11780 e.stream().wait(
11781 workspace
11782 .ev_done
11783 .as_ref()
11784 .expect("device routes done event"),
11785 )?;
11786 let mut output = e.uninit(experts.input_width)?;
11789 e.stream().memcpy_dtod(
11790 &workspace.combined.slice(0..experts.input_width),
11791 &mut output.slice_mut(0..experts.input_width),
11792 )?;
11793 output
11794 };
11795 if let Some(started) = started {
11796 use std::sync::atomic::Ordering;
11797 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11798 + started.elapsed().as_nanos() as u64;
11799 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11800 if calls % 430 == 0 {
11801 eprintln!(
11802 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11803 ns as f64 / 1.0e6,
11804 ns as f64 / calls as f64 / 1.0e3,
11805 );
11806 }
11807 }
11808 Ok(output)
11809 }
11810
11811 pub(crate) fn decode_v2_finish_root_fused(
11815 &self,
11816 ws: &mut StepTpDecodeV2Ws,
11817 ) -> Result<(), Box<dyn std::error::Error>> {
11818 let root = &self.ranks[0];
11819 let _main = root.gpu.enter_main()?;
11820 if ws.raw_peer_partial != 0 {
11821 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
11823 } else {
11824 root.stream()
11825 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
11826 }
11827 {
11828 let StepTpDecodeV2Ws {
11829 o_partials,
11830 peer_partial,
11831 reduce_a,
11832 o_out,
11833 ..
11834 } = &mut *ws;
11835 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
11836 }
11837 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
11838 if shadows {
11839 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
11842 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
11843 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
11844 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
11845 }
11846 if shadows && ws.raw_peer_partial != 0 {
11847 raw_copy_bytes(
11848 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
11849 ws.raw_k1,
11850 ws.local_kv_dim * 4,
11851 root,
11852 )?;
11853 raw_copy_bytes(
11854 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
11855 ws.raw_v1,
11856 ws.local_kv_dim * 4,
11857 root,
11858 )?;
11859 } else if shadows {
11860 let start = ws.local_kv_dim;
11861 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
11862 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
11863 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
11864 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
11865 }
11866 if ws.raw_mixed_stage_e != 0 {
11867 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
11870 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
11871 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
11872 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
11873 }
11874 Ok(())
11875 }
11876
11877 pub(crate) fn decode_v2_arm_token_mirrors(
11880 &self,
11881 ws: &mut StepTpDecodeV2Ws,
11882 mixed_stage_e: u64,
11883 shadow_stage_e: (u64, u64),
11884 ) -> Result<(), Box<dyn std::error::Error>> {
11885 use cudarc::driver::DevicePtr;
11886 let root = &self.ranks[0];
11887 let _main = root.gpu.enter_main()?;
11888 let stream = root.stream();
11889 let (a, _g) = ws.reduce_a.device_ptr(&stream);
11890 ws.raw_reduce_a = a as u64;
11891 ws.raw_mixed_stage_e = mixed_stage_e;
11892 ws.raw_shadow_stage_e = shadow_stage_e;
11893 Ok(())
11894 }
11895
11896 fn nvfp4_routes_build_graph(
11902 &self,
11903 experts: &ResidentNvfp4TensorParallel,
11904 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11905 local_out: usize,
11906 n_sel: usize,
11907 activation_limit: Option<f32>,
11908 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
11909 use cudarc::driver::DevicePtr;
11910 use cudarc::driver::sys;
11911 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
11912 if r == sys::CUresult::CUDA_SUCCESS {
11913 Ok(())
11914 } else {
11915 Err(format!("{what}: {r:?}").into())
11916 }
11917 }
11918 let world = self.ranks.len();
11919 if world != 2 {
11920 return Err("routes graph door is built for the TP2 pair".into());
11921 }
11922 let width = experts.input_width;
11923
11924 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
11926 let stream = engine.stream();
11927 let (ptr, _g) = buf.device_ptr(&stream);
11928 ptr as u64
11929 };
11930 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
11931 let stream = engine.stream();
11932 let (ptr, _g) = buf.device_ptr(&stream);
11933 ptr as u64
11934 };
11935 let (sel_e, w_e) = workspace
11936 .dev_route_e
11937 .as_ref()
11938 .expect("device route staging set before graph build");
11939 let root_engine = &self.ranks[0];
11940 let p_in_stage = ptr_f32(
11941 workspace.in_stage_e.as_ref().expect("graph staging"),
11942 root_engine,
11943 );
11944 let p_out_stage = ptr_f32(
11945 workspace.out_stage_e.as_ref().expect("graph staging"),
11946 root_engine,
11947 );
11948 let p_sel_e = ptr_i32(sel_e, root_engine);
11949 let p_w_e = ptr_f32(w_e, root_engine);
11950 let p_input: Vec<u64> = (0..world)
11951 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
11952 .collect();
11953 let p_sel: Vec<u64> = (0..world)
11954 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
11955 .collect();
11956 let p_route_w: Vec<u64> = (0..world)
11957 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
11958 .collect();
11959 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
11960 let p_remote = ptr_f32(&workspace.remote, root_engine);
11961 let p_combined = ptr_f32(&workspace.combined, root_engine);
11962
11963 let raw_copy = |dst: u64,
11964 src: u64,
11965 bytes: usize,
11966 engine: &Engine|
11967 -> Result<(), Box<dyn std::error::Error>> {
11968 unsafe {
11969 cu_try(
11970 sys::cuMemcpyAsync(
11971 dst as sys::CUdeviceptr,
11972 src as sys::CUdeviceptr,
11973 bytes,
11974 engine.stream().cu_stream() as sys::CUstream,
11975 ),
11976 "routes graph cuMemcpyAsync",
11977 )
11978 }
11979 };
11980
11981 let mut children = Vec::with_capacity(3);
11982 for rank in 0..world {
11983 let engine = &self.ranks[rank];
11984 let _main = engine.gpu.enter_main()?;
11985 let (child, _retained) = engine.capture_graph_retained(|_| {
11986 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
11987 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
11988 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
11989 {
11990 let Nvfp4DeviceRoutesWorkspace {
11991 input, in_q, in_d, ..
11992 } = &mut *workspace;
11993 engine.quantize_q8_1_into(
11994 &input[rank],
11995 1,
11996 width,
11997 &mut in_q[rank],
11998 &mut in_d[rank],
11999 )?;
12000 }
12001 self.nvfp4_routes_batched_sweeps_rank(
12002 experts,
12003 workspace,
12004 &[],
12005 &[],
12006 &[],
12007 local_out,
12008 n_sel,
12009 activation_limit,
12010 true,
12011 rank,
12012 )?;
12013 Ok(())
12014 })?;
12015 children.push(child);
12016 }
12017 {
12018 let root = &self.ranks[0];
12019 let _main = root.gpu.enter_main()?;
12020 let (child, _retained) = root.capture_graph_retained(|_| {
12021 raw_copy(p_remote, p_acc1, width * 4, root)?;
12022 {
12023 let Nvfp4DeviceRoutesWorkspace {
12024 accumulator,
12025 remote,
12026 combined,
12027 ..
12028 } = &mut *workspace;
12029 root.add(&accumulator[0], remote, combined, width)?;
12030 }
12031 raw_copy(p_out_stage, p_combined, width * 4, root)?;
12032 Ok(())
12033 })?;
12034 children.push(child);
12035 }
12036
12037 let mut parent: sys::CUgraph = std::ptr::null_mut();
12038 unsafe {
12039 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
12040 }
12041 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
12042 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
12043 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
12044 unsafe {
12045 cu_try(
12046 sys::cuGraphAddChildGraphNode(
12047 &mut n0,
12048 parent,
12049 std::ptr::null(),
12050 0,
12051 children[0].cu_graph(),
12052 ),
12053 "routes child r0",
12054 )?;
12055 cu_try(
12056 sys::cuGraphAddChildGraphNode(
12057 &mut n1,
12058 parent,
12059 std::ptr::null(),
12060 0,
12061 children[1].cu_graph(),
12062 ),
12063 "routes child r1",
12064 )?;
12065 let deps = [n0, n1];
12066 cu_try(
12067 sys::cuGraphAddChildGraphNode(
12068 &mut n2,
12069 parent,
12070 deps.as_ptr(),
12071 2,
12072 children[2].cu_graph(),
12073 ),
12074 "routes child root",
12075 )?;
12076 }
12077 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12078 unsafe {
12079 cu_try(
12080 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12081 "routes instantiate",
12082 )?;
12083 }
12084 Ok(RoutesGraph {
12085 exec,
12086 parent,
12087 _children: children,
12088 })
12089 }
12090
12091 #[allow(clippy::too_many_arguments)]
12095 pub(crate) fn routes_rank_section(
12096 &self,
12097 experts: &ResidentNvfp4TensorParallel,
12098 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12099 raw_input_src: u64,
12100 local_out: usize,
12101 n_sel: usize,
12102 activation_limit: Option<f32>,
12103 rank_index: usize,
12104 ) -> Result<(), Box<dyn std::error::Error>> {
12105 let engine = &self.ranks[rank_index];
12106 {
12107 let _main = engine.gpu.enter_main()?;
12108 let (sel_e_ptr, w_e_ptr) = workspace
12110 .raw_dev_route_e
12111 .ok_or("routes rank section requires armed staging pointers")?;
12112 raw_copy_bytes(
12113 workspace.raw_input[rank_index],
12114 raw_input_src,
12115 experts.input_width * 4,
12116 engine,
12117 )?;
12118 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
12119 raw_copy_bytes(
12120 workspace.raw_route_w[rank_index],
12121 w_e_ptr,
12122 n_sel * 4,
12123 engine,
12124 )?;
12125 {
12126 let Nvfp4DeviceRoutesWorkspace {
12127 input, in_q, in_d, ..
12128 } = &mut *workspace;
12129 engine.quantize_q8_1_into(
12130 &input[rank_index],
12131 1,
12132 experts.input_width,
12133 &mut in_q[rank_index],
12134 &mut in_d[rank_index],
12135 )?;
12136 }
12137 }
12138 self.nvfp4_routes_batched_sweeps_rank(
12139 experts,
12140 workspace,
12141 &[],
12142 &[],
12143 &[],
12144 local_out,
12145 n_sel,
12146 activation_limit,
12147 true,
12148 rank_index,
12149 )
12150 }
12151
12152 pub(crate) fn routes_root_section(
12155 &self,
12156 experts: &ResidentNvfp4TensorParallel,
12157 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12158 ) -> Result<(), Box<dyn std::error::Error>> {
12159 let root = &self.ranks[0];
12160 let _main = root.gpu.enter_main()?;
12161 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
12162 .raw_combine
12163 .ok_or("routes root section requires armed combine pointers")?;
12164 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
12165 {
12166 let Nvfp4DeviceRoutesWorkspace {
12167 accumulator,
12168 remote,
12169 combined,
12170 ..
12171 } = &mut *workspace;
12172 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12173 }
12174 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
12175 Ok(())
12176 }
12177
12178 pub(crate) fn routes_arm_raw(
12181 &self,
12182 experts: &ResidentNvfp4TensorParallel,
12183 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12184 ) -> Result<(), Box<dyn std::error::Error>> {
12185 use cudarc::driver::DevicePtr;
12186 if workspace.raw_dev_route_e.is_some() {
12187 return Ok(());
12188 }
12189 let _ = experts;
12190 let (sel_e, w_e) = workspace
12191 .dev_route_e
12192 .as_ref()
12193 .ok_or("routes staging not armed")?;
12194 let root = &self.ranks[0];
12195 {
12196 let _main = root.gpu.enter_main()?;
12197 let stream = root.stream();
12198 let (a, _g) = sel_e.device_ptr(&stream);
12199 let (b, _g) = w_e.device_ptr(&stream);
12200 workspace.raw_dev_route_e = Some((a as u64, b as u64));
12201 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
12202 let (d, _g) = workspace.remote.device_ptr(&stream);
12203 let (f, _g) = workspace.combined.device_ptr(&stream);
12204 let out_stage = workspace
12205 .out_stage_e
12206 .as_ref()
12207 .ok_or("routes out stage not armed")?;
12208 let (g_, _g) = out_stage.device_ptr(&stream);
12209 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
12210 }
12211 for rank in 0..self.ranks.len() {
12212 let engine = &self.ranks[rank];
12213 let _main = engine.gpu.enter_main()?;
12214 let stream = engine.stream();
12215 let (a, _g) = workspace.input[rank].device_ptr(&stream);
12216 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
12217 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
12218 workspace.raw_input.push(a as u64);
12219 workspace.raw_sel.push(b as u64);
12220 workspace.raw_route_w.push(c as u64);
12221 }
12222 Ok(())
12223 }
12224
12225 pub fn run_tensor_parallel_routes_nvfp4(
12229 &self,
12230 experts: &ResidentNvfp4TensorParallel,
12231 input: &[f32],
12232 tokens: usize,
12233 selected: &[usize],
12234 route_weights: &[f32],
12235 experts_per_token: usize,
12236 activation_limit: Option<f32>,
12237 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12238 validate_activations(input, tokens, experts.input_width)?;
12239 let pairs = tokens
12240 .checked_mul(experts_per_token)
12241 .ok_or("NVFP4 TP route count overflow")?;
12242 if selected.len() != pairs || route_weights.len() != pairs {
12243 return Err(format!(
12244 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
12245 {experts_per_token} ({pairs})",
12246 selected.len(),
12247 route_weights.len(),
12248 )
12249 .into());
12250 }
12251 if !route_weights.iter().all(|weight| weight.is_finite()) {
12252 return Err("NVFP4 TP route weights contain a non-finite value".into());
12253 }
12254
12255 let mut output = vec![0.0f32; tokens * experts.input_width];
12256 for token in 0..tokens {
12257 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
12258 for slot in 0..experts_per_token {
12259 let pair = token * experts_per_token + slot;
12260 let expert = selected[pair];
12261 if expert >= experts.expert_count {
12262 return Err(format!(
12263 "NVFP4 TP selected expert {expert} outside 0..{}",
12264 experts.expert_count
12265 )
12266 .into());
12267 }
12268 let gate = if experts.ep2 {
12274 self.run_full_bank_expert_nvfp4(
12275 &experts.gate,
12276 &experts.macros_gate,
12277 expert,
12278 input_row,
12279 )?
12280 } else {
12281 self.run_column_bank_expert_nvfp4(
12282 &experts.gate,
12283 &experts.macros_gate,
12284 expert,
12285 input_row,
12286 )?
12287 };
12288 let up = if experts.ep2 {
12289 self.run_full_bank_expert_nvfp4(
12290 &experts.up,
12291 &experts.macros_up,
12292 expert,
12293 input_row,
12294 )?
12295 } else {
12296 self.run_column_bank_expert_nvfp4(
12297 &experts.up,
12298 &experts.macros_up,
12299 expert,
12300 input_row,
12301 )?
12302 };
12303 let activated: Vec<f32> = gate
12304 .iter()
12305 .zip(&up)
12306 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
12307 .collect();
12308 debug_assert_eq!(activated.len(), experts.expert_width);
12309 let down = if experts.ep2 {
12310 self.run_full_down_expert_nvfp4(
12311 &experts.down,
12312 &experts.macros_down,
12313 expert,
12314 &activated,
12315 )?
12316 } else {
12317 self.run_row_bank_expert_nvfp4(
12318 &experts.down,
12319 &experts.macros_down,
12320 expert,
12321 &activated,
12322 )?
12323 };
12324 let weight = route_weights[pair];
12325 for (sum, value) in output
12326 [token * experts.input_width..(token + 1) * experts.input_width]
12327 .iter_mut()
12328 .zip(down)
12329 {
12330 *sum += weight * value;
12331 }
12332 }
12333 }
12334 Ok(output)
12335 }
12336}
12337
12338#[cfg(test)]
12339mod tests {
12340 use super::*;
12341
12342 #[test]
12343 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
12344 let limit = Some(7.0);
12345 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
12346 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
12347 assert!(
12348 step_expert_activation_host(-20.0, 9.0, limit).abs()
12349 < step_expert_activation_host(-20.0, 9.0, None).abs()
12350 );
12351 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
12352 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
12353 assert!(validate_step_expert_activation_limit(limit).is_ok());
12354 }
12355
12356 #[test]
12357 fn moe_residual_host_preserves_official_add_order() {
12358 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
12359 assert_eq!(output, [0.0]);
12360 assert_eq!(
12361 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
12362 "MoE residual lengths residual=1 routed=2 shared=1"
12363 );
12364 }
12365
12366 #[test]
12367 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
12368 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
12369 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
12370 assert_eq!(owners.len(), 4);
12371 for (rank, owner) in owners.iter().enumerate() {
12372 assert_eq!(owner.rank, rank);
12373 assert_eq!(owner.selected, vec![0, 36]);
12374 assert_eq!(owner.token_rows, vec![0, 0]);
12375 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
12376 }
12377 }
12378
12379 #[test]
12380 fn expert_owner_routes_validate_geometry_and_selected_experts() {
12381 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
12382 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
12383 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
12384 assert!(error.contains("outside 0..288"));
12385 }
12386
12387 #[test]
12388 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
12389 let selected = [
12390 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
12391 ];
12392 assert_eq!(
12393 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
12394 16
12395 );
12396 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
12397 assert_eq!(
12398 owners
12399 .iter()
12400 .map(|owner| owner.selected.len())
12401 .collect::<Vec<_>>(),
12402 vec![2, 4, 6, 4]
12403 );
12404 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
12405 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
12406 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
12407 }
12408
12409 #[test]
12410 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
12411 let owner0 = [0usize, 3];
12412 let owner1 = [1usize, 2];
12413 let owners = [owner0.as_slice(), owner1.as_slice()];
12414 assert_eq!(
12415 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
12416 .unwrap(),
12417 WeightedRouteCombineShape {
12418 pairs: 4,
12419 max_pairs: 12,
12420 }
12421 );
12422 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
12423 assert!(
12424 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
12425 .is_err()
12426 );
12427 assert!(
12428 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
12429 .is_err()
12430 );
12431 assert!(
12432 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
12433 .is_err()
12434 );
12435 }
12436
12437 #[test]
12438 fn native_p2p_door_is_strict_and_default_off() {
12439 assert!(!parse_step_tp_native_p2p(None).unwrap());
12440 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
12441 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
12442 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
12443 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
12444 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
12445 }
12446
12447 #[test]
12448 fn bulk_p2p_door_is_strict_and_default_off() {
12449 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
12450 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
12451 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
12452 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
12453 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
12454 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
12455 }
12456
12457 #[test]
12458 fn ep_device_arithmetic_door_is_strict_and_default_off() {
12459 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
12460 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
12461 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
12462 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
12463 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
12464 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
12465 }
12466
12467 #[test]
12468 fn f32_mirror_door_is_strict_and_default_off() {
12469 assert!(!parse_step_tp_f32_mirror(None).unwrap());
12470 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
12471 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
12472 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
12473 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
12474 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
12475 }
12476
12477 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
12478 let codes = (0..out_features * in_features)
12479 .map(|index| (index % 251) as u8)
12480 .collect();
12481 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
12482 .map(|index| index as f32 + 1.0)
12483 .collect();
12484 (codes, scales)
12485 }
12486
12487 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
12488 (0..out_features * in_features)
12489 .flat_map(|value| (value as u16).to_le_bytes())
12490 .collect()
12491 }
12492
12493 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
12494 bytes
12495 .chunks_exact(2)
12496 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
12497 .collect()
12498 }
12499
12500 #[test]
12501 fn bf16_matrix_rejects_wrong_byte_count() {
12502 let bytes = vec![0u8; 4 * 4 * 2 - 1];
12503 let matrix = Bf16Matrix {
12504 bytes: &bytes,
12505 out_features: 4,
12506 in_features: 4,
12507 };
12508 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
12509 }
12510
12511 #[test]
12512 fn replicated_device_rows_require_exact_rank_local_shapes() {
12513 assert_eq!(
12514 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
12515 12_288
12516 );
12517 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
12518 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
12519 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
12520 assert!(
12521 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
12522 );
12523 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
12524 }
12525
12526 #[test]
12527 fn replicated_device_row_refresh_requires_exact_root_source() {
12528 assert_eq!(
12529 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
12530 12_288
12531 );
12532 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
12533 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
12534 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
12535 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
12536 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
12537 }
12538
12539 #[test]
12540 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
12541 for tp in [1, 2, 4, 8] {
12542 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
12543 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
12544 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
12545 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
12546 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
12547 }
12548 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
12549 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
12550 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
12551 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
12552 }
12553
12554 #[test]
12555 fn cache_rows_split_by_token_then_rank() {
12556 let rows = (0u8..24).collect::<Vec<_>>();
12557 assert_eq!(
12558 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
12559 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
12560 );
12561 assert_eq!(
12562 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
12563 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
12564 );
12565 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
12566 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
12567 }
12568
12569 #[test]
12570 fn bf16_column_shard_preserves_contiguous_output_rows() {
12571 let bytes = bf16_matrix_bytes(4, 4);
12572 let matrix = Bf16Matrix {
12573 bytes: &bytes,
12574 out_features: 4,
12575 in_features: 4,
12576 };
12577 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
12578 assert_eq!(shard.out_features, 2);
12579 assert_eq!(shard.in_features, 4);
12580 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
12581 }
12582
12583 #[test]
12584 fn bf16_row_shard_preserves_each_input_column_window() {
12585 let bytes = bf16_matrix_bytes(3, 4);
12586 let matrix = Bf16Matrix {
12587 bytes: &bytes,
12588 out_features: 3,
12589 in_features: 4,
12590 };
12591 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
12592 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
12593 }
12594
12595 #[test]
12596 fn bf16_row_block_preserves_global_column_order() {
12597 let bytes = bf16_matrix_bytes(3, 8);
12598 let matrix = Bf16Matrix {
12599 bytes: &bytes,
12600 out_features: 3,
12601 in_features: 8,
12602 };
12603 let block = bf16_row_block(matrix, 2, 3).unwrap();
12604 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
12605 }
12606
12607 #[test]
12608 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
12609 let (codes, scales) = matrix(1280, 4096);
12610 let matrix = E4m3BlockMatrix {
12611 codes: &codes,
12612 scales: &scales,
12613 out_features: 1280,
12614 in_features: 4096,
12615 };
12616 let shard = column_shard(matrix, 2, 1).unwrap();
12617 assert_eq!(shard.out_features, 640);
12618 assert_eq!(shard.codes, &codes[640 * 4096..]);
12619 assert_eq!(shard.scales, &scales[5 * 32..]);
12620 }
12621
12622 #[test]
12623 fn row_shard_preserves_each_weight_and_scale_column_window() {
12624 let (codes, scales) = matrix(4096, 1280);
12625 let matrix = E4m3BlockMatrix {
12626 codes: &codes,
12627 scales: &scales,
12628 out_features: 4096,
12629 in_features: 1280,
12630 };
12631 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
12632 assert_eq!(shard_codes.len(), 4096 * 640);
12633 assert_eq!(&shard_codes[..640], &codes[640..1280]);
12634 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
12635 assert_eq!(shard_scales.len(), 32 * 5);
12636 assert_eq!(&shard_scales[..5], &scales[5..10]);
12637 assert_eq!(&shard_scales[5..10], &scales[15..20]);
12638 }
12639
12640 #[test]
12641 fn activation_shards_keep_token_rows_separate() {
12642 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
12643 assert_eq!(
12644 activation_shard(&activations, 2, 8, 2, 1),
12645 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
12646 );
12647 }
12648
12649 #[test]
12650 fn expert_bank_selects_expert_major_code_and_scale_planes() {
12651 let expert_count = 2;
12652 let out_features = 128;
12653 let in_features = 128;
12654 let code_stride = out_features * in_features;
12655 let codes: Vec<u8> = (0..expert_count * code_stride)
12656 .map(|index| (index % 251) as u8)
12657 .collect();
12658 let scales = vec![1.0f32, 2.0];
12659 let bank = E4m3ExpertBank {
12660 codes: &codes,
12661 scales: &scales,
12662 expert_count,
12663 out_features,
12664 in_features,
12665 };
12666 bank.validate().unwrap();
12667 let expert = bank.expert(1).unwrap();
12668 assert_eq!(expert.codes, &codes[code_stride..]);
12669 assert_eq!(expert.scales, &[2.0]);
12670 }
12671
12672 #[test]
12673 fn expert_bank_rejects_non_positive_scale() {
12674 let codes = vec![0u8; 128 * 128];
12675 let scales = vec![0.0f32];
12676 let bank = E4m3ExpertBank {
12677 codes: &codes,
12678 scales: &scales,
12679 expert_count: 1,
12680 out_features: 128,
12681 in_features: 128,
12682 };
12683 assert!(bank.validate().unwrap_err().contains("non-positive"));
12684 }
12685
12686 #[test]
12687 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
12688 let expert_count = 2;
12689 let out_features = 256;
12690 let in_features = 128;
12691 let code_stride = out_features * in_features;
12692 let scale_stride = 2;
12693 let codes = (0..expert_count * code_stride)
12694 .map(|index| (index % 251) as u8)
12695 .collect::<Vec<_>>();
12696 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12697 let bank = E4m3ExpertBank {
12698 codes: &codes,
12699 scales: &scales,
12700 expert_count,
12701 out_features,
12702 in_features,
12703 };
12704
12705 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
12706 assert_eq!(rank.out_features, 128);
12707 assert_eq!(rank.in_features, 128);
12708 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12709 assert_eq!(rank.scales, vec![11.0, 21.0]);
12710 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
12711 assert_eq!(
12712 &rank.codes[128 * 128..],
12713 &codes[code_stride + 128 * 128..2 * code_stride]
12714 );
12715 assert_eq!(scale_stride, scales.len() / expert_count);
12716 }
12717
12718 #[test]
12719 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
12720 let expert_count = 2;
12721 let out_features = 128;
12722 let in_features = 256;
12723 let code_stride = out_features * in_features;
12724 let codes = (0..expert_count * code_stride)
12725 .map(|index| (index % 251) as u8)
12726 .collect::<Vec<_>>();
12727 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12728 let bank = E4m3ExpertBank {
12729 codes: &codes,
12730 scales: &scales,
12731 expert_count,
12732 out_features,
12733 in_features,
12734 };
12735
12736 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12737 assert_eq!(rank.out_features, 128);
12738 assert_eq!(rank.in_features, 128);
12739 assert_eq!(rank.k_blocks, Some(1));
12740 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12741 assert_eq!(rank.scales, vec![11.0, 21.0]);
12742 assert_eq!(&rank.codes[..128], &codes[128..256]);
12743 assert_eq!(
12744 &rank.codes[128 * 128..128 * 128 + 128],
12745 &codes[code_stride + 128..code_stride + 256]
12746 );
12747 }
12748
12749 #[test]
12750 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
12751 let expert_count = 2;
12752 let out_features = 256;
12753 let in_features = 512;
12754 let code_stride = out_features * in_features;
12755 let mut codes = vec![0u8; expert_count * code_stride];
12756 for expert in 0..expert_count {
12757 for row in 0..out_features {
12758 for block in 0..4 {
12759 let value = (expert * 80 + block * 16 + row % 16) as u8;
12760 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
12761 codes[start..start + FP8_BLOCK].fill(value);
12762 }
12763 }
12764 }
12765 let scales = vec![
12766 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,
12767 112.0, 113.0, 114.0,
12768 ];
12769 let bank = E4m3ExpertBank {
12770 codes: &codes,
12771 scales: &scales,
12772 expert_count,
12773 out_features,
12774 in_features,
12775 };
12776
12777 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12778 assert_eq!(rank.out_features, out_features);
12779 assert_eq!(rank.in_features, 256);
12780 assert_eq!(rank.k_blocks, Some(2));
12781 assert_eq!(rank.code_stride, out_features * 256);
12782 assert_eq!(rank.scale_stride, 4);
12783 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
12784 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
12785
12786 let block_stride = out_features * FP8_BLOCK;
12787 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
12788 assert!(
12789 rank.codes[block_stride..block_stride + FP8_BLOCK]
12790 .iter()
12791 .all(|&code| code == 48)
12792 );
12793 assert!(
12794 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
12795 .iter()
12796 .all(|&code| code == 112)
12797 );
12798 assert!(
12799 rank.codes
12800 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
12801 .iter()
12802 .all(|&code| code == 128)
12803 );
12804 }
12805
12806 #[test]
12807 fn step_ep_layer_specs_are_literal_and_fail_closed() {
12808 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
12809 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
12810 assert_eq!(
12811 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
12812 vec![StepEpLayerSpec {
12813 layer: 24,
12814 devices: vec![1, 2],
12815 }]
12816 );
12817 assert_eq!(
12818 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
12819 vec![
12820 StepEpLayerSpec {
12821 layer: 24,
12822 devices: vec![1, 2],
12823 },
12824 StepEpLayerSpec {
12825 layer: 25,
12826 devices: vec![1, 2],
12827 },
12828 StepEpLayerSpec {
12829 layer: 31,
12830 devices: vec![0, 2],
12831 },
12832 ]
12833 );
12834 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
12835 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
12836 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
12837 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
12838 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
12839 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12840 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
12841 }
12842
12843 #[test]
12844 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
12845 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
12846 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
12847 assert_eq!(
12848 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
12849 vec![
12850 StepTpLayerSpec {
12851 layer: 24,
12852 devices: vec![1, 2],
12853 },
12854 StepTpLayerSpec {
12855 layer: 25,
12856 devices: vec![1, 2],
12857 },
12858 ]
12859 );
12860 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
12861 assert!(error.contains("MEMRA_STEP_TP"));
12862 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
12863 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12864
12865 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
12866 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
12867 assert_eq!(all.first().unwrap().layer, 0);
12868 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
12869 let devices = (0..8).collect::<Vec<_>>();
12870 assert!(all.iter().all(|spec| spec.devices == devices));
12871 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
12872 }
12873}
12874
12875struct TokenGraphChild {
12887 graph: cudarc::driver::CudaGraph,
12888 node: cudarc::driver::sys::CUgraphNode,
12889 ctx: cudarc::driver::sys::CUcontext,
12890}
12891
12892struct TokenGraphFaSite {
12896 ctx: cudarc::driver::sys::CUcontext,
12897 memset_o: cudarc::driver::sys::CUgraphNode,
12898 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
12899 fa: cudarc::driver::sys::CUgraphNode,
12900 combine: cudarc::driver::sys::CUgraphNode,
12901 window: usize,
12902 n_head: usize,
12903 n_head_kv: usize,
12904 head_dim: usize,
12905}
12906
12907pub struct TokenGraphBuilder {
12908 parent: cudarc::driver::sys::CUgraph,
12909 children: Vec<TokenGraphChild>,
12910 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
12913 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
12916 group: Option<(
12919 u32,
12920 Vec<cudarc::driver::sys::CUgraphNode>,
12921 Vec<cudarc::driver::sys::CUgraphNode>,
12922 )>,
12923}
12924
12925unsafe impl Send for TokenGraphBuilder {}
12927
12928impl TokenGraphBuilder {
12929 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
12930 use cudarc::driver::sys;
12931 let mut parent: sys::CUgraph = std::ptr::null_mut();
12932 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
12933 if r != sys::CUresult::CUDA_SUCCESS {
12934 return Err(format!("token graph create: {r:?}").into());
12935 }
12936 Ok(Self {
12937 parent,
12938 children: Vec::new(),
12939 frontier: Vec::new(),
12940 pending_detached: Vec::new(),
12941 group: None,
12942 })
12943 }
12944
12945 fn push_child(
12946 &mut self,
12947 graph: cudarc::driver::CudaGraph,
12948 parallel_group: Option<u32>,
12949 detached: bool,
12950 absorb: bool,
12951 ctx: cudarc::driver::sys::CUcontext,
12952 ) -> Result<(), Box<dyn std::error::Error>> {
12953 use cudarc::driver::sys;
12954 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
12958 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
12959 (state, Some(group)) => {
12960 if let Some((_, _, members)) = state.take() {
12962 self.frontier = members;
12963 }
12964 let base = self.frontier.clone();
12965 *state = Some((group, base.clone(), Vec::new()));
12966 base
12967 }
12968 (state, None) if detached => match state.as_ref() {
12969 Some((_, base, _)) => base.clone(),
12970 None => self.frontier.clone(),
12971 },
12972 (state, None) => {
12973 if let Some((_, _, members)) = state.take() {
12974 self.frontier = members;
12975 }
12976 let mut deps = self.frontier.clone();
12977 if absorb {
12978 deps.append(&mut self.pending_detached);
12979 }
12980 deps
12981 }
12982 };
12983 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12984 let r = unsafe {
12985 sys::cuGraphAddChildGraphNode(
12986 &mut node,
12987 self.parent,
12988 if deps.is_empty() {
12989 std::ptr::null()
12990 } else {
12991 deps.as_ptr()
12992 },
12993 deps.len(),
12994 graph.cu_graph(),
12995 )
12996 };
12997 if r != sys::CUresult::CUDA_SUCCESS {
12998 return Err(format!("token graph child: {r:?}").into());
12999 }
13000 match (&mut self.group, parallel_group, detached) {
13001 (_, None, true) => self.pending_detached.push(node),
13002 (Some((_, _, members)), Some(_), _) => members.push(node),
13003 _ => self.frontier = vec![node],
13004 }
13005 self.children.push(TokenGraphChild { graph, node, ctx });
13006 Ok(())
13007 }
13008
13009 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
13010 use cudarc::driver::sys;
13011 if let Some((_, _, members)) = self.group.take() {
13012 self.frontier = members;
13013 }
13014 let mut fa_sites = Vec::new();
13017 for child in &self.children {
13018 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
13019 fa_sites.push(site);
13020 }
13021 }
13022 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
13023 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
13024 if r != sys::CUresult::CUDA_SUCCESS {
13025 return Err(format!("token graph instantiate: {r:?}").into());
13026 }
13027 Ok(TokenGraph {
13028 exec,
13029 parent: self.parent,
13030 _children: self.children,
13031 fa_sites,
13032 })
13033 }
13034}
13035
13036fn discover_fa_site(
13039 child_node: cudarc::driver::sys::CUgraphNode,
13040 ctx: cudarc::driver::sys::CUcontext,
13041) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
13042 use cudarc::driver::sys;
13043 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13044 if r == sys::CUresult::CUDA_SUCCESS {
13045 Ok(())
13046 } else {
13047 Err(format!("{what}: {r:?}").into())
13048 }
13049 }
13050 let mut graph: sys::CUgraph = std::ptr::null_mut();
13051 unsafe {
13052 cu_try(
13053 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
13054 "fa-site child GetGraph",
13055 )?;
13056 }
13057 let mut count: usize = 0;
13058 unsafe {
13059 cu_try(
13060 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
13061 "fa-site GetNodes(count)",
13062 )?;
13063 }
13064 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
13065 unsafe {
13066 cu_try(
13067 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
13068 "fa-site GetNodes",
13069 )?;
13070 }
13071 nodes.truncate(count);
13072 let node_type =
13073 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
13074 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
13075 unsafe {
13076 cu_try(
13077 sys::cuGraphNodeGetType(node, &mut ty),
13078 "fa-site NodeGetType",
13079 )?;
13080 }
13081 Ok(ty)
13082 };
13083 let memsets: Vec<sys::CUgraphNode> = {
13084 let mut v = Vec::new();
13085 for &node in &nodes {
13086 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
13087 v.push(node);
13088 }
13089 }
13090 v
13091 };
13092 if memsets.len() != 3 {
13093 return Ok(None);
13094 }
13095 let dependents =
13097 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
13098 let mut n: usize = 0;
13099 unsafe {
13100 cu_try(
13101 sys::cuGraphNodeGetDependentNodes_v2(
13102 node,
13103 std::ptr::null_mut(),
13104 std::ptr::null_mut(),
13105 &mut n,
13106 ),
13107 "fa-site GetDependentNodes(count)",
13108 )?;
13109 }
13110 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
13111 unsafe {
13112 cu_try(
13113 sys::cuGraphNodeGetDependentNodes_v2(
13114 node,
13115 v.as_mut_ptr(),
13116 std::ptr::null_mut(),
13117 &mut n,
13118 ),
13119 "fa-site GetDependentNodes",
13120 )?;
13121 }
13122 v.truncate(n);
13123 Ok(v)
13124 };
13125 let mut fa: Option<sys::CUgraphNode> = None;
13128 let mut last_memset: Option<sys::CUgraphNode> = None;
13129 for &ms in &memsets {
13130 for dep in dependents(ms)? {
13131 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13132 fa = Some(dep);
13133 last_memset = Some(ms);
13134 }
13135 }
13136 }
13137 let (Some(fa), Some(_last)) = (fa, last_memset) else {
13138 return Ok(None);
13139 };
13140 let mut combine: Option<sys::CUgraphNode> = None;
13141 for dep in dependents(fa)? {
13142 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13143 combine = Some(dep);
13144 }
13145 }
13146 let Some(combine) = combine else {
13147 return Ok(None);
13148 };
13149 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13152 unsafe {
13153 cu_try(
13154 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
13155 "fa-site KernelNodeGetParams",
13156 )?;
13157 }
13158 let arg_i32 =
13159 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
13160 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
13161 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
13163 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13164 unsafe {
13165 cu_try(
13166 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13167 "fa-site MemsetNodeGetParams",
13168 )?;
13169 }
13170 Ok(mp.width)
13171 };
13172 let mut widest = memsets[0];
13173 for &ms in &memsets[1..] {
13174 if width_of(ms)? > width_of(widest)? {
13175 widest = ms;
13176 }
13177 }
13178 let memset_m: Vec<sys::CUgraphNode> =
13179 memsets.iter().copied().filter(|&m| m != widest).collect();
13180 Ok(Some(TokenGraphFaSite {
13181 ctx,
13182 memset_o: widest,
13183 memset_m: [memset_m[0], memset_m[1]],
13184 fa,
13185 combine,
13186 window: win as usize,
13187 n_head: nh as usize,
13188 n_head_kv: nhkv as usize,
13189 head_dim: hd as usize,
13190 }))
13191}
13192
13193pub struct TokenGraph {
13194 exec: cudarc::driver::sys::CUgraphExec,
13195 parent: cudarc::driver::sys::CUgraph,
13196 _children: Vec<TokenGraphChild>,
13197 fa_sites: Vec<TokenGraphFaSite>,
13198}
13199
13200unsafe impl Send for TokenGraph {}
13201
13202impl TokenGraph {
13203 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
13208 use cudarc::driver::sys;
13209 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13210 if r == sys::CUresult::CUDA_SUCCESS {
13211 Ok(())
13212 } else {
13213 Err(format!("{what}: {r:?}").into())
13214 }
13215 }
13216 for site in &self.fa_sites {
13217 let layer_bucket = if site.window > 0 {
13218 bucket.min(site.window)
13219 } else {
13220 bucket
13221 };
13222 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
13223 let nsp = layer_bucket.div_ceil(sp).max(1);
13224 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13226 unsafe {
13227 cu_try(
13228 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
13229 "retarget fa GetParams",
13230 )?;
13231 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
13232 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
13233 params.gridDimY = nsp as u32;
13234 cu_try(
13235 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
13236 "retarget fa SetParams",
13237 )?;
13238 }
13239 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13241 unsafe {
13242 cu_try(
13243 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
13244 "retarget combine GetParams",
13245 )?;
13246 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
13247 cu_try(
13248 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
13249 "retarget combine SetParams",
13250 )?;
13251 }
13252 let set_width =
13254 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
13255 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13256 unsafe {
13257 cu_try(
13258 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13259 "retarget memset GetParams",
13260 )?;
13261 }
13262 mp.width = width;
13263 unsafe {
13264 cu_try(
13265 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
13266 "retarget memset SetParams",
13267 )?;
13268 }
13269 Ok(())
13270 };
13271 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
13272 set_width(site.memset_m[0], site.n_head * nsp)?;
13273 set_width(site.memset_m[1], site.n_head * nsp)?;
13274 }
13275 Ok(())
13276 }
13277
13278 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
13279 use cudarc::driver::sys;
13280 let _main = e.gpu.enter_main()?;
13281 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
13282 if r != sys::CUresult::CUDA_SUCCESS {
13283 return Err(format!("token graph launch: {r:?}").into());
13284 }
13285 Ok(())
13286 }
13287}
13288
13289impl Drop for TokenGraph {
13290 fn drop(&mut self) {
13291 unsafe {
13292 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
13293 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
13294 }
13295 }
13296}
13297
13298std::thread_local! {
13299 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
13300 const { std::cell::RefCell::new(None) };
13301}
13302
13303pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
13305 let builder = TokenGraphBuilder::new()?;
13306 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
13307 Ok(())
13308}
13309
13310pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
13312 let builder = TOKEN_GRAPH_BUILDER
13313 .with(|cell| cell.borrow_mut().take())
13314 .ok_or("token graph build was not begun")?;
13315 builder.finish()
13316}
13317
13318pub fn token_graph_building() -> bool {
13320 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
13321}
13322
13323pub fn graph_section<F>(
13328 engine: &Engine,
13329 parallel_group: Option<u32>,
13330 f: F,
13331) -> Result<(), Box<dyn std::error::Error>>
13332where
13333 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13334{
13335 graph_section_opts(engine, parallel_group, false, false, f)
13336}
13337
13338pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13340where
13341 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13342{
13343 graph_section_opts(engine, None, false, true, f)
13344}
13345
13346pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13349where
13350 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13351{
13352 graph_section_opts(engine, None, true, false, f)
13353}
13354
13355pub fn graph_section_opts<F>(
13356 engine: &Engine,
13357 parallel_group: Option<u32>,
13358 detached: bool,
13359 absorb: bool,
13360 f: F,
13361) -> Result<(), Box<dyn std::error::Error>>
13362where
13363 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13364{
13365 let building = token_graph_building();
13366 if !building {
13367 let mut f = f;
13368 return f();
13369 }
13370 let (child, ctx) = {
13371 let _main = engine.gpu.enter_main()?;
13372 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
13373 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
13374 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13375 return Err(format!("graph section ctx query: {r:?}").into());
13376 }
13377 let mut f = f;
13378 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
13381 (child, ctx)
13382 };
13383 TOKEN_GRAPH_BUILDER.with(|cell| {
13384 cell.borrow_mut()
13385 .as_mut()
13386 .expect("builder checked above")
13387 .push_child(child, parallel_group, detached, absorb, ctx)
13388 })
13389}