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, 16_384, 262_144, 16_777_216];
16const STEP_GROUPED_FP8_EXPERTS: usize = 288;
17const STEP_GROUPED_FP8_TOP_K: usize = 8;
18const STEP_GROUPED_FP8_WIDTH: usize = 1280;
19
20fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
21 if let Some(limit) = limit {
22 if !limit.is_finite() || limit <= 0.0 {
23 return Err(format!(
24 "Step routed-expert activation limit must be positive and finite, got {limit}"
25 ));
26 }
27 }
28 Ok(())
29}
30
31pub(crate) fn routes_prestage_on() -> bool {
54 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
55 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
56}
57
58pub(crate) fn oproj_tail_on() -> bool {
76 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
77 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
78}
79thread_local! {
80 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
81 const { std::cell::Cell::new(None) };
82}
83thread_local! {
84 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
89}
90pub(crate) struct OprojTailScope(());
92pub(crate) fn oproj_tail_scope() -> OprojTailScope {
93 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
94 OprojTailScope(())
95}
96impl Drop for OprojTailScope {
97 fn drop(&mut self) {
98 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
99 OPROJ_TAIL_PENDING.with(|c| c.set(None));
101 }
102}
103thread_local! {
104 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
107}
108pub(crate) fn set_verify_tcol(c: Option<usize>) {
109 VERIFY_TCOL.with(|x| x.set(c));
110}
111pub(crate) fn take_verify_tcol() -> Option<usize> {
112 VERIFY_TCOL.with(|x| x.take())
113}
114
115pub(crate) fn tcol_oproj_on() -> bool {
122 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
123 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
124}
125thread_local! {
126 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
130 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
131}
132pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
133 TCOL_OPROJ_DEFER.with(|x| x.set(c));
134}
135pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
136 TCOL_OPROJ_DEFER.with(|x| x.take())
137}
138pub(crate) fn set_tcol_oproj_stashed() {
139 TCOL_OPROJ_STASHED.with(|x| x.set(true));
140}
141pub(crate) fn take_tcol_oproj_stashed() -> bool {
142 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
143}
144
145pub(crate) fn oproj_tail_eligible() -> bool {
146 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
147}
148pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
149 OPROJ_TAIL_PENDING.with(|c| c.take())
150}
151pub(crate) fn set_oproj_tail(v: (u64, u64)) {
152 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
153}
154
155pub(crate) fn rank0_merge_on() -> bool {
156 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
157 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
158}
159
160pub(crate) fn len_mirror_lazy_on() -> bool {
161 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
162 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
163}
164
165pub(crate) fn fence_memops_on() -> bool {
166 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
167 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
168}
169
170pub(crate) fn moe_direct_on() -> bool {
171 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
172 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
173}
174
175pub(crate) fn fence_rank1_on() -> bool {
190 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
191 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
192}
193
194pub(crate) fn spec_fa2_on() -> bool {
200 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
201 *ON.get_or_init(|| std::env::var("MEMRA_SPEC_FA2").as_deref() == Ok("1"))
202}
203thread_local! {
204 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
207 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
208}
209pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
210 SPEC_FA2_DEFER.with(|x| x.set(c));
211}
212pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
213 SPEC_FA2_DEFER.with(|x| x.take())
214}
215pub(crate) fn set_spec_fa2_stashed() {
216 SPEC_FA2_STASHED.with(|x| x.set(true));
217}
218pub(crate) fn take_spec_fa2_stashed() -> bool {
219 SPEC_FA2_STASHED.with(|x| x.replace(false))
220}
221
222pub(crate) fn sel_mirror_on() -> bool {
223 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
224 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
225}
226
227pub(crate) fn step_nvfp4_ep2_on() -> bool {
234 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
235 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
236}
237
238pub(crate) fn sel_down8_on() -> bool {
239 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
240 *ON.get_or_init(|| std::env::var("MEMRA_SEL_DOWN8").as_deref() == Ok("1"))
241}
242
243pub(crate) fn oproj_direct_on() -> bool {
244 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
245 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
246}
247
248pub(crate) fn raw_copy_bytes(
249 dst: u64,
250 src: u64,
251 bytes: usize,
252 engine: &Engine,
253) -> Result<(), Box<dyn std::error::Error>> {
254 use cudarc::driver::sys;
255 let r = unsafe {
256 sys::cuMemcpyAsync(
257 dst as sys::CUdeviceptr,
258 src as sys::CUdeviceptr,
259 bytes,
260 engine.stream().cu_stream() as sys::CUstream,
261 )
262 };
263 if r == sys::CUresult::CUDA_SUCCESS {
264 Ok(())
265 } else {
266 if std::env::var("MEMRA_RAW_COPY_TRACE").as_deref() == Ok("1") {
269 eprintln!(
270 "[raw-copy-fail] dst={dst:#x} src={src:#x} bytes={bytes} {r:?}\n{}",
271 std::backtrace::Backtrace::force_capture()
272 );
273 }
274 Err(format!("raw_copy_bytes: {r:?} bytes={bytes} dst={dst:#x} src={src:#x}").into())
275 }
276}
277
278pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
279 let silu = gate / (1.0 + (-gate).exp());
280 match limit {
281 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
282 None => silu * up,
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
287struct ExpertOwnerRoutes {
288 rank: usize,
289 selected: Vec<usize>,
290 token_rows: Vec<usize>,
291 global_pairs: Vec<usize>,
292}
293
294fn partition_expert_owner_routes(
295 expert_count: usize,
296 ranks: usize,
297 tokens: usize,
298 experts_per_token: usize,
299 selected: &[usize],
300) -> Result<Vec<ExpertOwnerRoutes>, String> {
301 if expert_count == 0
302 || ranks == 0
303 || tokens == 0
304 || experts_per_token == 0
305 || expert_count % ranks != 0
306 {
307 return Err(format!(
308 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
309 tokens={tokens} experts_per_token={experts_per_token}"
310 ));
311 }
312 let pairs = tokens
313 .checked_mul(experts_per_token)
314 .ok_or("expert-owner route count overflow")?;
315 if selected.len() != pairs {
316 return Err(format!(
317 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
318 selected.len()
319 ));
320 }
321 let per_rank = expert_count / ranks;
322 let mut owners = (0..ranks)
323 .map(|rank| ExpertOwnerRoutes {
324 rank,
325 selected: Vec::new(),
326 token_rows: Vec::new(),
327 global_pairs: Vec::new(),
328 })
329 .collect::<Vec<_>>();
330 for (pair, &expert) in selected.iter().enumerate() {
331 if expert >= expert_count {
332 return Err(format!(
333 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
334 ));
335 }
336 let rank = expert / per_rank;
337 owners[rank].selected.push(expert - rank * per_rank);
338 owners[rank].token_rows.push(pair / experts_per_token);
339 owners[rank].global_pairs.push(pair);
340 }
341 Ok(owners)
342}
343
344fn validate_step_grouped_owner_routes(
345 expert_count: usize,
346 tokens: usize,
347 selected: &[usize],
348) -> Result<usize, String> {
349 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
350 return Err(format!(
351 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
352 experts={expert_count} tokens={tokens}",
353 STEP_GROUPED_FP8_EXPERTS
354 ));
355 }
356 let pairs = tokens
357 .checked_mul(STEP_GROUPED_FP8_TOP_K)
358 .ok_or("official Step owner-grouped FP8 route count overflow")?;
359 if selected.len() != pairs {
360 return Err(format!(
361 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
362 selected.len(),
363 STEP_GROUPED_FP8_TOP_K,
364 ));
365 }
366 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
367 let mut unique = routes.to_vec();
368 unique.sort_unstable();
369 unique.dedup();
370 if unique.len() != STEP_GROUPED_FP8_TOP_K {
371 return Err(format!(
372 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
373 {routes:?}"
374 ));
375 }
376 }
377 Ok(pairs)
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381struct WeightedRouteCombineShape {
382 pairs: usize,
383 max_pairs: usize,
384}
385
386fn validate_weighted_route_combine(
387 width: usize,
388 experts_per_token: usize,
389 max_tokens: usize,
390 tokens: usize,
391 owner_global_pairs: &[&[usize]],
392 route_weights: &[f32],
393) -> Result<WeightedRouteCombineShape, String> {
394 if width == 0
395 || experts_per_token == 0
396 || max_tokens == 0
397 || tokens == 0
398 || tokens > max_tokens
399 || width > i32::MAX as usize
400 || experts_per_token > i32::MAX as usize
401 || tokens > i32::MAX as usize
402 {
403 return Err(format!(
404 "invalid weighted route combine geometry width={width} experts_per_token=\
405 {experts_per_token} tokens={tokens}/{max_tokens}"
406 ));
407 }
408 let pairs = tokens
409 .checked_mul(experts_per_token)
410 .ok_or("weighted route combine pair count overflow")?;
411 let max_pairs = max_tokens
412 .checked_mul(experts_per_token)
413 .ok_or("weighted route combine capacity overflow")?;
414 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
415 return Err(format!(
416 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
417 route_weights.len()
418 ));
419 }
420 let mut seen = vec![false; pairs];
421 let mut observed = 0usize;
422 for pairs_for_owner in owner_global_pairs {
423 observed = observed
424 .checked_add(pairs_for_owner.len())
425 .ok_or("weighted route combine observed pair count overflow")?;
426 for &pair in *pairs_for_owner {
427 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
428 return Err(format!(
429 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
430 ));
431 }
432 }
433 }
434 if observed != pairs || seen.iter().any(|present| !present) {
435 return Err(format!(
436 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
437 ));
438 }
439 Ok(WeightedRouteCombineShape { pairs, max_pairs })
440}
441
442fn cache_rank_rows(
443 rows: &[u8],
444 tokens: usize,
445 local_token_bytes: usize,
446 ranks: usize,
447 rank: usize,
448) -> Result<Vec<u8>, String> {
449 if ranks == 0 || rank >= ranks {
450 return Err(format!(
451 "TP cache rank {rank} is outside a {ranks}-rank layout"
452 ));
453 }
454 let global_token_bytes = local_token_bytes
455 .checked_mul(ranks)
456 .ok_or("TP cache global token-byte overflow")?;
457 let expected = tokens
458 .checked_mul(global_token_bytes)
459 .ok_or("TP cache row-byte overflow")?;
460 if rows.len() != expected {
461 return Err(format!(
462 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
463 rows.len()
464 ));
465 }
466 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
467 for token in 0..tokens {
468 let start = token * global_token_bytes + rank * local_token_bytes;
469 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
470 }
471 Ok(shard)
472}
473
474fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
475 match value {
476 None | Some("") | Some("0") => Ok(false),
477 Some("1") => Ok(true),
478 Some(value) => Err(format!(
479 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
480 )),
481 }
482}
483
484pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
485 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
486}
487
488fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
489 match value {
490 None | Some("") | Some("0") => Ok(false),
491 Some("1") => Ok(true),
492 Some(value) => Err(format!(
493 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
494 )),
495 }
496}
497
498pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
499 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
500}
501
502fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
503 match value {
504 None | Some("") | Some("0") => Ok(false),
505 Some("1") => Ok(true),
506 Some(value) => Err(format!(
507 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
508 )),
509 }
510}
511
512fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
513 match value {
514 None | Some("") | Some("0") => Ok(false),
515 Some("1") => Ok(true),
516 Some(value) => Err(format!(
517 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
518 )),
519 }
520}
521
522pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
525 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
526}
527
528pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
529 parse_step_ep_device_arithmetic(
530 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
531 .ok()
532 .as_deref(),
533 )
534}
535
536fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
537 match value {
538 None | Some("") | Some("0") => Ok(false),
539 Some("1") => Ok(true),
540 Some(value) => Err(format!(
541 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
542 )),
543 }
544}
545
546pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
547 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
548}
549
550fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
551 match value {
552 None | Some("") | Some("0") => Ok(false),
553 Some("1") => Ok(true),
554 Some(value) => Err(format!(
555 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
556 )),
557 }
558}
559
560pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
565 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
566}
567
568fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
569 match value {
570 None | Some("") | Some("0") => Ok(false),
571 Some("1") => Ok(true),
572 Some(value) => Err(format!(
573 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
574 )),
575 }
576}
577
578fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
579 match value {
580 None | Some("") | Some("0") => Ok(false),
581 Some("1") => Ok(true),
582 Some(value) => Err(format!(
583 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
584 )),
585 }
586}
587
588pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
592 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
593}
594
595fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
596 match value {
597 None | Some("") | Some("0") => Ok(false),
598 Some("1") => Ok(true),
599 Some(value) => Err(format!(
600 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
601 )),
602 }
603}
604
605fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
606 match value {
607 None | Some("") | Some("0") => Ok(false),
608 Some("1") => Ok(true),
609 Some(value) => Err(format!(
610 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
611 )),
612 }
613}
614
615pub fn step_tp_dcw_enabled() -> Result<bool, String> {
620 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
621}
622
623pub fn step_tp_graph_enabled() -> Result<bool, String> {
628 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
629}
630
631pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
635 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
636}
637
638#[derive(Debug, Clone, PartialEq, Eq)]
639pub struct StepEpLayerSpec {
640 pub layer: usize,
641 pub devices: Vec<usize>,
642}
643
644pub type StepTpLayerSpec = StepEpLayerSpec;
645
646fn parse_step_layer_specs(
647 flag: &str,
648 value: Option<&str>,
649 allow_full_model: bool,
650) -> Result<Vec<StepEpLayerSpec>, String> {
651 let Some(value) = value else {
652 return Ok(Vec::new());
653 };
654 if value.is_empty() || value == "0" {
655 return Ok(Vec::new());
656 }
657
658 let mut specs = Vec::new();
659 for item in value.split(';') {
660 let (layers, devices) = item.split_once('@').ok_or_else(|| {
661 let layers = if allow_full_model {
662 "LAYER[-LAYER] or all"
663 } else {
664 "LAYER[-LAYER]"
665 };
666 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
667 })?;
668 let (first, last) = if layers == "all" {
669 if !allow_full_model {
670 return Err(format!(
671 "{flag} does not support the full-model shorthand; assign routed layers \
672 explicitly"
673 ));
674 }
675 (0, STEP37_TRUNK_LAYERS - 1)
676 } else {
677 match layers.split_once('-') {
678 Some((first, last)) => {
679 let first = first
680 .parse::<usize>()
681 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
682 let last = last
683 .parse::<usize>()
684 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
685 if first > last {
686 return Err(format!("{flag} layer range {first}-{last} is reversed"));
687 }
688 if last - first + 1 > 128 {
689 return Err(format!(
690 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
691 ));
692 }
693 (first, last)
694 }
695 None => {
696 let layer = layers
697 .parse::<usize>()
698 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
699 (layer, layer)
700 }
701 }
702 };
703 let devices = devices
704 .split(',')
705 .map(|device| {
706 device
707 .parse::<usize>()
708 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
709 })
710 .collect::<Result<Vec<_>, _>>()?;
711 if !(2..=8).contains(&devices.len()) {
712 return Err(format!(
713 "{flag} requires 2..=8 devices, got {}",
714 devices.len()
715 ));
716 }
717 let mut unique = devices.clone();
718 unique.sort_unstable();
719 unique.dedup();
720 if unique.len() != devices.len() {
721 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
722 }
723 for layer in first..=last {
724 if specs
725 .iter()
726 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
727 {
728 return Err(format!("{flag} assigns layer {layer} more than once"));
729 }
730 specs.push(StepEpLayerSpec {
731 layer,
732 devices: devices.clone(),
733 });
734 }
735 }
736 Ok(specs)
737}
738
739pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
740 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
741}
742
743pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
744 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
745}
746
747pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
748 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
749}
750
751pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
752 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
753}
754
755#[derive(Clone, Copy)]
756pub struct E4m3BlockMatrix<'a> {
757 pub codes: &'a [u8],
758 pub scales: &'a [f32],
759 pub out_features: usize,
760 pub in_features: usize,
761}
762
763impl E4m3BlockMatrix<'_> {
764 fn validate(&self) -> Result<(), String> {
765 let code_count = self
766 .out_features
767 .checked_mul(self.in_features)
768 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
769 if self.codes.len() != code_count {
770 return Err(format!(
771 "E4M3 code count {} != {}x{} ({code_count})",
772 self.codes.len(),
773 self.out_features,
774 self.in_features,
775 ));
776 }
777 let scale_count =
778 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
779 if self.scales.len() != scale_count {
780 return Err(format!(
781 "E4M3 scale count {} != {scale_count} for {}x{}",
782 self.scales.len(),
783 self.out_features,
784 self.in_features,
785 ));
786 }
787 if !self
788 .scales
789 .iter()
790 .all(|scale| scale.is_finite() && *scale > 0.0)
791 {
792 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
793 }
794 Ok(())
795 }
796}
797
798#[derive(Clone, Copy)]
799pub struct E4m3ExpertBank<'a> {
800 pub codes: &'a [u8],
801 pub scales: &'a [f32],
802 pub expert_count: usize,
803 pub out_features: usize,
804 pub in_features: usize,
805}
806
807impl E4m3ExpertBank<'_> {
808 fn validate(&self) -> Result<(), String> {
809 if self.expert_count == 0 {
810 return Err("E4M3 expert bank is empty".to_string());
811 }
812 let code_stride = self
813 .out_features
814 .checked_mul(self.in_features)
815 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
816 let code_count = self
817 .expert_count
818 .checked_mul(code_stride)
819 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
820 if self.codes.len() != code_count {
821 return Err(format!(
822 "E4M3 expert code count {} != {}x{} ({code_count})",
823 self.codes.len(),
824 self.expert_count,
825 code_stride,
826 ));
827 }
828 let scale_stride =
829 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
830 let scale_count = self
831 .expert_count
832 .checked_mul(scale_stride)
833 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
834 if self.scales.len() != scale_count {
835 return Err(format!(
836 "E4M3 expert scale count {} != {}x{} ({scale_count})",
837 self.scales.len(),
838 self.expert_count,
839 scale_stride,
840 ));
841 }
842 if !self
843 .scales
844 .iter()
845 .all(|scale| scale.is_finite() && *scale > 0.0)
846 {
847 return Err(
848 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
849 );
850 }
851 Ok(())
852 }
853
854 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
855 if expert >= self.expert_count {
856 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
857 }
858 let code_stride = self.out_features * self.in_features;
859 let scale_stride =
860 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
861 Ok(E4m3BlockMatrix {
862 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
863 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
864 out_features: self.out_features,
865 in_features: self.in_features,
866 })
867 }
868}
869
870pub struct ColumnParallelResult {
871 pub gathered: Vec<f32>,
872 pub rank_outputs: Vec<Vec<f32>>,
873}
874
875pub struct RowParallelResult {
876 pub reduced: Vec<f32>,
877 pub rank_partials: Vec<Vec<f32>>,
878}
879
880#[derive(Clone, Copy)]
881pub struct Bf16Matrix<'a> {
882 pub bytes: &'a [u8],
883 pub out_features: usize,
884 pub in_features: usize,
885}
886
887impl Bf16Matrix<'_> {
888 pub fn validate(&self) -> Result<(), String> {
889 if self.out_features == 0 || self.in_features == 0 {
890 return Err("BF16 matrix dimensions must be nonzero".into());
891 }
892 let expected = self
893 .out_features
894 .checked_mul(self.in_features)
895 .and_then(|values| values.checked_mul(2))
896 .ok_or("BF16 matrix byte count overflow")?;
897 if self.bytes.len() != expected {
898 return Err(format!(
899 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
900 self.bytes.len(),
901 self.out_features,
902 self.in_features,
903 ));
904 }
905 Ok(())
906 }
907}
908
909struct ResidentE4m3Rank {
910 codes: CudaSlice<u8>,
911 scales: CudaSlice<f32>,
912 out_features: usize,
913 in_features: usize,
914}
915
916enum ResidentBf16Weight {
917 Bf16(CudaSlice<u8>),
918 F32(CudaSlice<f32>),
919}
920
921impl ResidentBf16Weight {
922 fn ordinal(&self) -> usize {
923 match self {
924 Self::Bf16(bytes) => bytes.ordinal(),
925 Self::F32(values) => values.ordinal(),
926 }
927 }
928}
929
930struct ResidentBf16Rank {
931 weight: ResidentBf16Weight,
932 out_features: usize,
933 in_features: usize,
934 q8: Option<CudaSlice<u8>>,
937}
938
939pub struct ResidentColumnParallel {
940 ranks: Vec<ResidentE4m3Rank>,
941 out_features: usize,
942 in_features: usize,
943}
944
945pub struct ResidentRowParallel {
946 ranks: Vec<ResidentE4m3Rank>,
947 out_features: usize,
948 in_features: usize,
949}
950
951pub struct ResidentBf16ColumnParallel {
952 ranks: Vec<ResidentBf16Rank>,
953 out_features: usize,
954 in_features: usize,
955 canonical_chunk_rows: Option<usize>,
956}
957
958pub struct ResidentBf16RowParallel {
959 ranks: Vec<ResidentBf16Rank>,
960 out_features: usize,
961 in_features: usize,
962}
963
964pub struct ResidentStepBf16RowParallel {
965 ranks: Vec<Vec<ResidentBf16Rank>>,
966 out_features: usize,
967 in_features: usize,
968 canonical_chunk_cols: usize,
969}
970
971pub struct ResidentSigmoidTopKRouter {
973 weight: CudaSlice<f32>,
974 correction_bias: CudaSlice<f32>,
975 active: CudaSlice<u8>,
976 root_device: usize,
977 input_width: usize,
978 expert_count: usize,
979 experts_per_token: usize,
980 active_count: usize,
981 scaling_factor: f32,
982 route_norm: bool,
983}
984
985pub struct SigmoidTopKHostOutput {
986 pub logits: Vec<f32>,
987 pub selected: Vec<u32>,
988 pub weights: Vec<f32>,
989}
990
991pub struct ResidentReplicatedBf16SwiGlu {
993 gate: Vec<ResidentBf16Rank>,
994 up: Vec<ResidentBf16Rank>,
995 down: Vec<ResidentBf16Rank>,
996 input_width: usize,
997 intermediate_width: usize,
998}
999
1000pub struct ResidentReplicatedDeviceRows {
1005 ranks: Vec<CudaSlice<f32>>,
1006 tokens: usize,
1007 width: usize,
1008}
1009
1010impl ResidentReplicatedDeviceRows {
1011 pub fn tokens(&self) -> usize {
1012 self.tokens
1013 }
1014
1015 pub fn width(&self) -> usize {
1016 self.width
1017 }
1018
1019 pub fn ranks(&self) -> usize {
1020 self.ranks.len()
1021 }
1022}
1023
1024pub fn moe_residual_host(
1026 residual: &[f32],
1027 routed: &[f32],
1028 shared: &[f32],
1029) -> Result<Vec<f32>, String> {
1030 if residual.len() != routed.len() || residual.len() != shared.len() {
1031 return Err(format!(
1032 "MoE residual lengths residual={} routed={} shared={}",
1033 residual.len(),
1034 routed.len(),
1035 shared.len()
1036 ));
1037 }
1038 let ffn = routed
1039 .iter()
1040 .zip(shared)
1041 .map(|(&routed, &shared)| routed + shared)
1042 .collect::<Vec<_>>();
1043 Ok(residual
1044 .iter()
1045 .zip(ffn)
1046 .map(|(&residual, ffn)| residual + ffn)
1047 .collect())
1048}
1049
1050pub use memra_kv::{
1051 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1052};
1053
1054pub struct ResidentTpExpert {
1060 gate: ResidentColumnParallel,
1061 up: ResidentColumnParallel,
1062 down: ResidentRowParallel,
1063 input_width: usize,
1064 expert_width: usize,
1065}
1066
1067struct ResidentE4m3ExpertBankRank {
1068 codes: CudaSlice<u8>,
1069 scales: CudaSlice<f32>,
1070 expert_range: Range<usize>,
1071 out_features: usize,
1072 in_features: usize,
1073 code_stride: usize,
1074 scale_stride: usize,
1075 k_blocks: Option<usize>,
1078}
1079
1080struct PackedE4m3ExpertBankRank {
1081 codes: Vec<u8>,
1082 scales: Vec<f32>,
1083 expert_range: Range<usize>,
1084 out_features: usize,
1085 in_features: usize,
1086 code_stride: usize,
1087 scale_stride: usize,
1088 k_blocks: Option<usize>,
1089}
1090
1091struct ResidentEpRank {
1092 gate: ResidentE4m3ExpertBankRank,
1093 up: ResidentE4m3ExpertBankRank,
1094 down: ResidentE4m3ExpertBankRank,
1095}
1096
1097pub struct ResidentExpertParallel {
1104 ranks: Vec<ResidentEpRank>,
1105 expert_count: usize,
1106 input_width: usize,
1107 expert_width: usize,
1108}
1109
1110pub struct StepGroupedFp8ProjectionOutput {
1115 pub gate: Vec<f32>,
1116 pub up: Vec<f32>,
1117 pub down: Vec<f32>,
1118}
1119
1120pub struct PreparedStepGroupedFp8Gate {
1125 device: usize,
1126 gate: ResidentE4m3ExpertBankRank,
1127 up: ResidentE4m3ExpertBankRank,
1128 down: ResidentE4m3ExpertBankRank,
1129 input: CudaSlice<f32>,
1130 route_csr: DeviceExpertCsr,
1131 down_csr: DeviceExpertCsr,
1132 gate_workspace: Fp8GroupedWorkspace,
1133 up_workspace: Fp8GroupedWorkspace,
1134 down_workspace: Fp8GroupedWorkspace,
1135 activation: CudaSlice<f32>,
1136 activation_limit: Option<f32>,
1137 tokens: usize,
1138 pairs: usize,
1139}
1140
1141impl PreparedStepGroupedFp8Gate {
1142 pub fn tokens(&self) -> usize {
1143 self.tokens
1144 }
1145
1146 pub fn pairs(&self) -> usize {
1147 self.pairs
1148 }
1149}
1150
1151struct PreparedStepGroupedExpertOwner {
1152 rank: usize,
1153 global_pairs: Vec<usize>,
1154 route_csr: DeviceExpertCsr,
1155 down_csr: DeviceExpertCsr,
1156 gate_workspace: Fp8GroupedWorkspace,
1157 up_workspace: Fp8GroupedWorkspace,
1158 down_workspace: Fp8GroupedWorkspace,
1159 activation: CudaSlice<f32>,
1160}
1161
1162struct StepGroupedExpertOwnerSchedule {
1163 global_pairs: Vec<usize>,
1164 route_csr: ExpertCsr,
1165 down_csr: ExpertCsr,
1166}
1167
1168pub struct PreparedStepGroupedExpertParallelGate {
1174 rank_inputs: Vec<CudaSlice<f32>>,
1175 owners: Vec<PreparedStepGroupedExpertOwner>,
1176 activation_limit: Option<f32>,
1177 tokens: usize,
1178 pairs: usize,
1179 max_tokens: usize,
1180 max_pairs: usize,
1181 input_width: usize,
1182 expert_width: usize,
1183 generation: u64,
1184 executed_generation: Option<u64>,
1185 ready: bool,
1186}
1187
1188impl PreparedStepGroupedExpertParallelGate {
1189 pub fn tokens(&self) -> usize {
1190 self.tokens
1191 }
1192
1193 pub fn pairs(&self) -> usize {
1194 self.pairs
1195 }
1196
1197 pub fn max_tokens(&self) -> usize {
1198 self.max_tokens
1199 }
1200
1201 pub fn input_width(&self) -> usize {
1202 self.input_width
1203 }
1204
1205 pub fn expert_width(&self) -> usize {
1206 self.expert_width
1207 }
1208
1209 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1210 validate_step_expert_activation_limit(limit)?;
1211 self.activation_limit = limit;
1212 self.executed_generation = None;
1213 Ok(())
1214 }
1215
1216 pub fn active_owners(&self) -> usize {
1217 self.owners
1218 .iter()
1219 .filter(|owner| !owner.global_pairs.is_empty())
1220 .count()
1221 }
1222
1223 pub fn owner_pair_counts(&self) -> Vec<usize> {
1224 self.owners
1225 .iter()
1226 .map(|owner| owner.global_pairs.len())
1227 .collect()
1228 }
1229
1230 pub fn generation(&self) -> u64 {
1231 self.generation
1232 }
1233}
1234
1235struct PreparedPeerWeightedRouteOwner {
1236 token_rows: CudaSlice<i32>,
1237 slots: CudaSlice<i32>,
1238 weights: CudaSlice<f32>,
1239 active_pairs: usize,
1240}
1241
1242pub struct PreparedPeerWeightedRouteCombine {
1248 root_device: usize,
1249 owners: Vec<PreparedPeerWeightedRouteOwner>,
1250 peer_staging: CudaSlice<f32>,
1251 slots: CudaSlice<f32>,
1252 weights: CudaSlice<f32>,
1253 output: CudaSlice<f32>,
1254 peer_devices: Vec<usize>,
1255 peer_outputs: Vec<CudaSlice<f32>>,
1256 width: usize,
1257 experts_per_token: usize,
1258 max_tokens: usize,
1259 max_pairs: usize,
1260 tokens: usize,
1261 pairs: usize,
1262 projection_generation: u64,
1263 output_generation: Option<u64>,
1264 broadcast_generation: Option<u64>,
1265 ready: bool,
1266}
1267
1268impl PreparedPeerWeightedRouteCombine {
1269 pub fn tokens(&self) -> usize {
1270 self.tokens
1271 }
1272
1273 pub fn pairs(&self) -> usize {
1274 self.pairs
1275 }
1276
1277 pub fn owner_pair_counts(&self) -> Vec<usize> {
1278 self.owners.iter().map(|owner| owner.active_pairs).collect()
1279 }
1280
1281 pub fn distributed_ranks(&self) -> usize {
1282 1 + self.peer_outputs.len()
1283 }
1284}
1285
1286struct ResidentTpExpertBank {
1287 gate: Vec<ResidentE4m3ExpertBankRank>,
1288 up: Vec<ResidentE4m3ExpertBankRank>,
1289 down: Vec<ResidentE4m3ExpertBankRank>,
1290 expert_count: usize,
1291 input_width: usize,
1292 expert_width: usize,
1293}
1294
1295pub struct ResidentTensorParallel {
1301 bank: ResidentTpExpertBank,
1302}
1303
1304pub struct TpE4m3HostBounce {
1310 devices: Vec<usize>,
1311 ranks: Vec<Engine>,
1312 native_p2p: bool,
1313 ep_device_arithmetic: bool,
1314 bulk_p2p: bool,
1315 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1318}
1319
1320pub enum StepTpGateShards<'a> {
1329 F32(&'a [crate::CudaSlice<f32>]),
1330 Bf16(&'a [crate::CudaSlice<u8>]),
1331}
1332
1333pub struct StepTpDecodeV2Ws {
1334 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1338 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1339 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1340 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1341 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1342 pub(crate) tcol_cap: usize,
1343 w8_aq: Vec<CudaSlice<i8>>,
1347 w8_ad: Vec<CudaSlice<f32>>,
1348 w8_in: usize,
1349 w8o_aq: Vec<CudaSlice<i8>>,
1352 w8o_ad: Vec<CudaSlice<f32>>,
1353 w8o_in: usize,
1354 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1361 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1362 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1363 pub(crate) fa2_cap: usize,
1364 rope_k_t: Vec<CudaSlice<f32>>,
1368 rope_ctr_t: Vec<CudaSlice<u32>>,
1369 rope_pos_t: Vec<CudaSlice<i32>>,
1370 rows_tabs: Vec<std::collections::HashMap<u64, CudaSlice<u64>>>,
1373 tcol_gated: Vec<CudaSlice<f32>>,
1374 tcol_opart: Vec<CudaSlice<f32>>,
1375 tcol_opeer: Option<CudaSlice<f32>>,
1376 tcol_omix: Option<CudaSlice<f32>>,
1377 tcol_ocap: usize,
1378 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1381 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1382 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1383 pub(crate) q: Vec<CudaSlice<f32>>,
1384 pub(crate) k: Vec<CudaSlice<f32>>,
1385 pub(crate) pos: Vec<CudaSlice<i32>>,
1386 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1388 pub(crate) gate: Vec<CudaSlice<f32>>,
1389 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1390 pub(crate) gated: Vec<CudaSlice<f32>>,
1391 o_partials: Vec<Vec<CudaSlice<f32>>>,
1393 ev_rank: Vec<CudaEvent>,
1395 peer_partial: CudaSlice<f32>,
1397 reduce_a: CudaSlice<f32>,
1398 reduce_b: CudaSlice<f32>,
1399 zeros: CudaSlice<f32>,
1401 pub(crate) k_shadow: CudaSlice<f32>,
1402 pub(crate) v_shadow: CudaSlice<f32>,
1403 ev_refresh: CudaEvent,
1404 ev_oproj: CudaEvent,
1405 gate_e: CudaSlice<f32>,
1407 pub(crate) h_stage: Option<CudaSlice<f32>>,
1410 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1411 attn_in: Vec<CudaSlice<f32>>,
1415 raw_h_stage: u64,
1417 raw_pos_stage: u64,
1418 raw_attn_in: Vec<u64>,
1419 raw_pos: Vec<u64>,
1420 raw_o_partial1: u64,
1421 raw_peer_partial: u64,
1422 raw_k1: u64,
1423 raw_v1: u64,
1424 raw_k_shadow: u64,
1425 raw_v_shadow: u64,
1426 raw_mixed_stage_e: u64,
1430 raw_reduce_a: u64,
1431 raw_shadow_stage_e: (u64, u64),
1432 ev_entry: CudaEvent,
1433 e_device: usize,
1434 local_q_dim: usize,
1436 local_kv_dim: usize,
1437 heads: usize,
1438 pub(crate) o_out: usize,
1439 o_block_cols: usize,
1440 blocks_per_rank: usize,
1441}
1442
1443impl TpE4m3HostBounce {
1444 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1445 Self::new_inner(devices, false, false, false, false)
1446 }
1447
1448 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1449 Self::new_inner(devices, false, true, false, false)
1450 }
1451
1452 pub fn new_native_p2p_device_arithmetic(
1453 devices: &[usize],
1454 ) -> Result<Self, Box<dyn std::error::Error>> {
1455 Self::new_inner(devices, false, true, true, false)
1456 }
1457
1458 pub(crate) fn new_configured(
1459 devices: &[usize],
1460 native_p2p: bool,
1461 ep_device_arithmetic: bool,
1462 bulk_p2p: bool,
1463 ) -> Result<Self, Box<dyn std::error::Error>> {
1464 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1465 }
1466
1467 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1472 Self::new_inner(&[device], true, false, false, false)
1473 }
1474
1475 fn new_inner(
1476 devices: &[usize],
1477 allow_single_rank: bool,
1478 native_p2p: bool,
1479 ep_device_arithmetic: bool,
1480 bulk_p2p: bool,
1481 ) -> Result<Self, Box<dyn std::error::Error>> {
1482 if ep_device_arithmetic && !native_p2p {
1483 return Err("device-resident EP arithmetic requires native P2P".into());
1484 }
1485 if bulk_p2p && !native_p2p {
1486 return Err("bulk TP transport requires native P2P".into());
1487 }
1488 let minimum = if allow_single_rank { 1 } else { 2 };
1489 if !(minimum..=8).contains(&devices.len()) {
1490 return Err(format!(
1491 "TP reference requires {minimum}..=8 devices, got {}",
1492 devices.len()
1493 )
1494 .into());
1495 }
1496 let mut unique = devices.to_vec();
1497 unique.sort_unstable();
1498 unique.dedup();
1499 if unique.len() != devices.len() {
1500 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1501 }
1502 let ranks = devices
1503 .iter()
1504 .map(|&device| Engine::new(device))
1505 .collect::<Result<Vec<_>, _>>()?;
1506 if native_p2p {
1507 configure_native_p2p(&ranks, devices)?;
1508 }
1509 if allow_single_rank {
1510 eprintln!(
1511 "[tp] canonical oracle transport=local device={} performance_claim=false",
1512 devices[0]
1513 );
1514 } else if native_p2p {
1515 if ep_device_arithmetic {
1516 eprintln!(
1517 "[tp] correctness transport=native-p2p devices={devices:?} \
1518 native_p2p=true activation=device-host-exact \
1519 accumulation=device-host-exact output=root-readback \
1520 bulk_p2p={bulk_p2p} performance_claim=false"
1521 );
1522 } else {
1523 eprintln!(
1524 "[tp] correctness transport=native-p2p devices={devices:?} \
1525 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1526 performance_claim=false"
1527 );
1528 }
1529 } else {
1530 eprintln!(
1531 "[tp] correctness transport=host-bounce devices={devices:?} \
1532 native_p2p=false performance_claim=false"
1533 );
1534 }
1535 Ok(Self {
1536 devices: devices.to_vec(),
1537 ranks,
1538 native_p2p,
1539 ep_device_arithmetic,
1540 bulk_p2p,
1541 decode_v2: std::sync::Mutex::new(Vec::new()),
1542 })
1543 }
1544
1545 pub fn devices(&self) -> &[usize] {
1546 &self.devices
1547 }
1548
1549 pub fn native_p2p(&self) -> bool {
1550 self.native_p2p
1551 }
1552
1553 pub fn bulk_p2p(&self) -> bool {
1554 self.bulk_p2p
1555 }
1556
1557 pub fn expert_activation_label(&self) -> &'static str {
1558 if self.ep_device_arithmetic {
1559 "device-host-exact"
1560 } else {
1561 "host-canonical"
1562 }
1563 }
1564
1565 pub fn expert_accumulation_label(&self) -> &'static str {
1566 self.expert_activation_label()
1567 }
1568
1569 pub fn expert_output_label(&self) -> &'static str {
1570 if self.ep_device_arithmetic {
1571 "root-readback"
1572 } else {
1573 "host-accumulated"
1574 }
1575 }
1576
1577 pub fn transport_label(&self) -> &'static str {
1578 if self.devices.len() == 1 {
1579 "local"
1580 } else if self.native_p2p {
1581 "native-p2p"
1582 } else {
1583 "host-bounce"
1584 }
1585 }
1586
1587 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1588 self.ranks
1589 .iter()
1590 .map(|rank| rank.ctx().name().map_err(Into::into))
1591 .collect()
1592 }
1593
1594 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1600 self.ranks.get(rank)
1601 }
1602
1603 pub fn allocate_tp_kv_cache(
1604 &self,
1605 kv_dim_k: usize,
1606 kv_dim_v: usize,
1607 capacity: usize,
1608 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1609 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1610 }
1611
1612 pub fn allocate_tp_swa_kv_cache(
1613 &self,
1614 kv_dim_k: usize,
1615 kv_dim_v: usize,
1616 capacity: usize,
1617 window: usize,
1618 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1619 if window == 0 {
1620 return Err("TP SWA KV window must be nonzero".into());
1621 }
1622 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1623 }
1624
1625 fn allocate_tp_kv_cache_inner(
1626 &self,
1627 kv_dim_k: usize,
1628 kv_dim_v: usize,
1629 capacity: usize,
1630 window: Option<usize>,
1631 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1632 if capacity == 0 || capacity > i32::MAX as usize {
1633 return Err(
1634 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1635 );
1636 }
1637 let tp = self.ranks.len();
1638 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1639 let physical_rows = window
1640 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1641 .unwrap_or(capacity);
1642 let k_plane_bytes = physical_rows
1643 .checked_mul(shape.k_token_bytes)
1644 .and_then(|bytes| bytes.checked_add(8))
1645 .ok_or("TP KV K plane-byte overflow")?;
1646 let v_plane_bytes = physical_rows
1647 .checked_mul(shape.v_token_bytes)
1648 .and_then(|bytes| bytes.checked_add(8))
1649 .ok_or("TP KV V plane-byte overflow")?;
1650 let mut ranks = Vec::with_capacity(tp);
1651 for engine in &self.ranks {
1652 let _main = engine.gpu.enter_main()?;
1653 ranks.push(ResidentTpKvCacheRank::new(
1654 engine.alloc_u8(k_plane_bytes)?,
1655 engine.alloc_u8(v_plane_bytes)?,
1656 engine.htod_i32(&[0])?,
1657 ));
1658 }
1659 Ok(match window {
1660 Some(window) => ResidentTpKvCache::new_swa(
1661 ranks,
1662 shape.kv_dim_k,
1663 shape.kv_dim_v,
1664 shape.k_token_bytes,
1665 shape.v_token_bytes,
1666 capacity,
1667 window,
1668 ),
1669 None => ResidentTpKvCache::new(
1670 ranks,
1671 shape.kv_dim_k,
1672 shape.kv_dim_v,
1673 shape.k_token_bytes,
1674 shape.v_token_bytes,
1675 capacity,
1676 ),
1677 })
1678 }
1679
1680 pub fn grow_tp_kv_cache(
1681 &self,
1682 source: &ResidentTpKvCache,
1683 target_capacity: usize,
1684 rows: usize,
1685 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1686 self.validate_tp_kv_cache(source)?;
1687 let plan = source.prepare_grow(target_capacity, rows)?;
1688 let ranks = self.ranks.len();
1689 let global_k = source
1690 .kv_dim_k()
1691 .checked_mul(ranks)
1692 .ok_or("TP KV grow global K dimension overflow")?;
1693 let global_v = source
1694 .kv_dim_v()
1695 .checked_mul(ranks)
1696 .ok_or("TP KV grow global V dimension overflow")?;
1697 let mut target = match source.ring_window() {
1698 Some(window) => {
1699 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1700 }
1701 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1702 };
1703 self.validate_tp_kv_cache(&target)?;
1704
1705 for (rank, engine) in self.ranks.iter().enumerate() {
1706 let _main = engine.gpu.enter_main()?;
1707 let src = source
1708 .rank(rank)
1709 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1710 let dst = target
1711 .rank_mut(rank)
1712 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1713 if plan.k_bytes() > 0 {
1714 engine.copy_u8_range_into(
1715 dst.k_mut(),
1716 0,
1717 src.k(),
1718 plan.source_row() * source.k_tok_bytes(),
1719 plan.k_bytes(),
1720 )?;
1721 }
1722 if plan.v_bytes() > 0 {
1723 engine.copy_u8_range_into(
1724 dst.v_mut(),
1725 0,
1726 src.v(),
1727 plan.source_row() * source.v_tok_bytes(),
1728 plan.v_bytes(),
1729 )?;
1730 }
1731 }
1732 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1733
1734 for engine in &self.ranks {
1737 let _main = engine.gpu.enter_main()?;
1738 engine.stream().synchronize()?;
1739 }
1740 let physical_copy_rows = plan.copy_rows();
1741 target.publish_grow(plan)?;
1742 eprintln!(
1743 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1744 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1745 rank_streams_synchronized=true generation_preserved=true",
1746 rows,
1747 source.capacity(),
1748 target_capacity,
1749 ranks,
1750 physical_copy_rows,
1751 source.ring_window(),
1752 );
1753 Ok(target)
1754 }
1755
1756 pub fn hydrate_tp_kv_cache(
1757 &self,
1758 cache: &mut ResidentTpKvCache,
1759 rows: usize,
1760 k_rows: &[u8],
1761 v_rows: &[u8],
1762 ) -> Result<(), Box<dyn std::error::Error>> {
1763 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1764 }
1765
1766 pub fn hydrate_tp_kv_cache_from(
1767 &self,
1768 cache: &mut ResidentTpKvCache,
1769 logical_len: usize,
1770 resident_start: usize,
1771 k_rows: &[u8],
1772 v_rows: &[u8],
1773 ) -> Result<(), Box<dyn std::error::Error>> {
1774 self.validate_tp_kv_cache(cache)?;
1775 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1776 return Err(format!(
1777 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1778 cache.committed_len(),
1779 cache.staged_len()
1780 )
1781 .into());
1782 }
1783 if resident_start > logical_len || logical_len > cache.capacity() {
1784 return Err(format!(
1785 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1786 cache.capacity(),
1787 )
1788 .into());
1789 }
1790 let rows = logical_len - resident_start;
1791 if rows > cache.physical_capacity() {
1792 return Err(format!(
1793 "TP KV hydration rows {rows} exceed physical capacity {}",
1794 cache.physical_capacity()
1795 )
1796 .into());
1797 }
1798 for rank in 0..self.ranks.len() {
1799 let k_rank =
1800 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1801 let v_rank =
1802 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1803 let engine = &self.ranks[rank];
1804 let _main = engine.gpu.enter_main()?;
1805 let rank_cache = cache
1806 .rank_mut(rank)
1807 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1808 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1809 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1810 }
1811 cache.publish_hydration(logical_len, resident_start)?;
1812 Ok(())
1813 }
1814
1815 pub fn append_tp_kv_transaction(
1816 &self,
1817 cache: &mut ResidentTpKvCache,
1818 transaction: TpKvTransaction,
1819 k_shards: &[CudaSlice<f32>],
1820 v_shards: &[CudaSlice<f32>],
1821 rows: usize,
1822 ) -> Result<(), Box<dyn std::error::Error>> {
1823 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1824 }
1825
1826 #[allow(clippy::too_many_arguments)]
1831 pub fn append_tp_kv_transaction_inner(
1832 &self,
1833 cache: &mut ResidentTpKvCache,
1834 transaction: TpKvTransaction,
1835 k_shards: &[CudaSlice<f32>],
1836 v_shards: &[CudaSlice<f32>],
1837 rows: usize,
1838 external_rank_appends: bool,
1839 ) -> Result<(), Box<dyn std::error::Error>> {
1840 self.validate_tp_kv_cache(cache)?;
1841 let plan = cache.prepare_append(transaction, rows)?;
1842 let target = plan.target();
1843 let expected_k = rows
1844 .checked_mul(cache.kv_dim_k())
1845 .ok_or("TP KV K append size overflow")?;
1846 let expected_v = rows
1847 .checked_mul(cache.kv_dim_v())
1848 .ok_or("TP KV V append size overflow")?;
1849 if !external_rank_appends
1852 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1853 {
1854 return Err(format!(
1855 "TP KV append shard counts k={} v={} != ranks {}",
1856 k_shards.len(),
1857 v_shards.len(),
1858 self.ranks.len()
1859 )
1860 .into());
1861 }
1862 let kv_dim_k = cache.kv_dim_k();
1863 let kv_dim_v = cache.kv_dim_v();
1864 let k_tok_bytes = cache.k_tok_bytes();
1865 let v_tok_bytes = cache.v_tok_bytes();
1866 if let Some(KvRingAppend::Rebase {
1867 src_row,
1868 keep_rows,
1869 new_base,
1870 ..
1871 }) = plan.ring_append()
1872 {
1873 for rank in 0..self.ranks.len() {
1874 let engine = &self.ranks[rank];
1875 let _main = engine.gpu.enter_main()?;
1876 let rank_cache = cache
1877 .rank_mut(rank)
1878 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1879 if keep_rows > 0 {
1880 let k_len = keep_rows
1881 .checked_mul(k_tok_bytes)
1882 .ok_or("TP KV K rebase-byte overflow")?;
1883 let v_len = keep_rows
1884 .checked_mul(v_tok_bytes)
1885 .ok_or("TP KV V rebase-byte overflow")?;
1886 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1887 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
1888 engine.copy_u8_range_into(
1889 &mut k_tmp,
1890 0,
1891 rank_cache.k(),
1892 src_row * k_tok_bytes,
1893 k_len,
1894 )?;
1895 engine.copy_u8_range_into(
1896 &mut v_tmp,
1897 0,
1898 rank_cache.v(),
1899 src_row * v_tok_bytes,
1900 v_len,
1901 )?;
1902 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
1903 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
1904 }
1905 if rank_cache.base_d().is_some() {
1909 let value = new_base as i32;
1910 let rank_cache = cache
1911 .rank_mut(rank)
1912 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1913 if let Some(base_d) = rank_cache.base_d_mut() {
1914 engine.set_i32_one(base_d, value)?;
1915 }
1916 }
1917 }
1918 }
1919 cache.publish_append_rebase(plan)?;
1920 let write_row = plan.write_row();
1921 for rank in 0..self.ranks.len() {
1922 if external_rank_appends {
1923 break;
1924 }
1925 let engine = &self.ranks[rank];
1926 let _main = engine.gpu.enter_main()?;
1927 if k_shards[rank].len() != expected_k
1928 || v_shards[rank].len() != expected_v
1929 || k_shards[rank].ordinal() != engine.ctx().ordinal()
1930 || v_shards[rank].ordinal() != engine.ctx().ordinal()
1931 {
1932 return Err(format!(
1933 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
1934 != expected {expected_k}/{expected_v} on device {}",
1935 k_shards[rank].len(),
1936 k_shards[rank].ordinal(),
1937 v_shards[rank].len(),
1938 v_shards[rank].ordinal(),
1939 engine.ctx().ordinal(),
1940 )
1941 .into());
1942 }
1943 let rank_cache = cache
1944 .rank_mut(rank)
1945 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1946 let (rank_k, rank_v) = rank_cache.planes_mut();
1947 engine.append_kv_quantized_rows(
1948 &k_shards[rank],
1949 &v_shards[rank],
1950 rank_k,
1951 rank_v,
1952 write_row,
1953 rows,
1954 kv_dim_k,
1955 kv_dim_v,
1956 k_tok_bytes,
1957 v_tok_bytes,
1958 Engine::kv_fp8_on(),
1959 )?;
1960 }
1961 if !external_rank_appends {
1962 self.set_tp_kv_len_mirrors(cache, target)?;
1965 }
1966 cache.publish_append_plan(plan)?;
1967 Ok(())
1968 }
1969
1970 pub fn commit_tp_kv_transaction(
1971 &self,
1972 cache: &mut ResidentTpKvCache,
1973 transaction: TpKvTransaction,
1974 accepted_rows: usize,
1975 ) -> Result<(), Box<dyn std::error::Error>> {
1976 self.validate_tp_kv_cache(cache)?;
1977 let target = cache.commit_target(transaction, accepted_rows)?;
1978 self.set_tp_kv_len_mirrors(cache, target)?;
1979 cache.publish_finalize(transaction, target)?;
1980 Ok(())
1981 }
1982
1983 pub fn commit_tp_kv_transaction_external(
1989 &self,
1990 cache: &mut ResidentTpKvCache,
1991 transaction: TpKvTransaction,
1992 accepted_rows: usize,
1993 ) -> Result<(), Box<dyn std::error::Error>> {
1994 self.validate_tp_kv_cache(cache)?;
1995 let target = cache.commit_target(transaction, accepted_rows)?;
1996 cache.publish_finalize(transaction, target)?;
1997 Ok(())
1998 }
1999
2000 pub fn rollback_tp_kv_transaction(
2001 &self,
2002 cache: &mut ResidentTpKvCache,
2003 transaction: TpKvTransaction,
2004 ) -> Result<(), Box<dyn std::error::Error>> {
2005 self.validate_tp_kv_cache(cache)?;
2006 cache.validate_transaction(transaction)?;
2007 let target = transaction.base_len();
2008 self.set_tp_kv_len_mirrors(cache, target)?;
2009 cache.publish_finalize(transaction, target)?;
2010 Ok(())
2011 }
2012
2013 pub fn tp_kv_device_lengths(
2014 &self,
2015 cache: &ResidentTpKvCache,
2016 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
2017 self.validate_tp_kv_cache(cache)?;
2018 let mut lengths = Vec::with_capacity(self.ranks.len());
2019 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
2020 let _main = engine.gpu.enter_main()?;
2021 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
2022 }
2023 Ok(lengths)
2024 }
2025
2026 fn set_tp_kv_len_mirrors(
2027 &self,
2028 cache: &mut ResidentTpKvCache,
2029 len: usize,
2030 ) -> Result<(), Box<dyn std::error::Error>> {
2031 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2032 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2033 let _main = engine.gpu.enter_main()?;
2034 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2035 }
2036 Ok(())
2037 }
2038
2039 fn validate_tp_kv_cache(
2040 &self,
2041 cache: &ResidentTpKvCache,
2042 ) -> Result<(), Box<dyn std::error::Error>> {
2043 if cache.ranks_len() != self.ranks.len() {
2044 return Err(format!(
2045 "TP KV cache ranks {} != runtime ranks {}",
2046 cache.ranks_len(),
2047 self.ranks.len()
2048 )
2049 .into());
2050 }
2051 let expected_k = cache
2052 .physical_capacity()
2053 .checked_mul(cache.k_tok_bytes())
2054 .and_then(|bytes| bytes.checked_add(8))
2055 .ok_or("TP KV K plane validation overflow")?;
2056 let expected_v = cache
2057 .physical_capacity()
2058 .checked_mul(cache.v_tok_bytes())
2059 .and_then(|bytes| bytes.checked_add(8))
2060 .ok_or("TP KV V plane validation overflow")?;
2061 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2062 let device = engine.ctx().ordinal();
2063 if rank_cache.k().len() != expected_k
2064 || rank_cache.v().len() != expected_v
2065 || rank_cache.len_d().len() != 1
2066 || rank_cache.k().ordinal() != device
2067 || rank_cache.v().ordinal() != device
2068 || rank_cache.len_d().ordinal() != device
2069 {
2070 return Err(format!(
2071 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2072 )
2073 .into());
2074 }
2075 }
2076 Ok(())
2077 }
2078
2079 pub fn full(
2080 &self,
2081 matrix: E4m3BlockMatrix<'_>,
2082 activations: &[f32],
2083 tokens: usize,
2084 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2085 matrix.validate()?;
2086 validate_activations(activations, tokens, matrix.in_features)?;
2087 run_rank(&self.ranks[0], matrix, activations, tokens)
2088 }
2089
2090 pub fn column_parallel(
2094 &self,
2095 matrix: E4m3BlockMatrix<'_>,
2096 activations: &[f32],
2097 tokens: usize,
2098 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2099 matrix.validate()?;
2100 validate_activations(activations, tokens, matrix.in_features)?;
2101 let tp = self.ranks.len();
2102 if matrix.out_features % tp != 0 {
2103 return Err(format!(
2104 "column-parallel out_features {} is not divisible by TP={tp}",
2105 matrix.out_features
2106 )
2107 .into());
2108 }
2109 let local_out = matrix.out_features / tp;
2110 if local_out % FP8_BLOCK != 0 {
2111 return Err(format!(
2112 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2113 E4M3 scale block"
2114 )
2115 .into());
2116 }
2117
2118 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2119 let mut rank_outputs = Vec::with_capacity(tp);
2120 for (rank_index, rank) in self.ranks.iter().enumerate() {
2121 let shard = column_shard(matrix, tp, rank_index)?;
2122 let output = run_rank(rank, shard, activations, tokens)?;
2123 let row_start = rank_index * local_out;
2124 for token in 0..tokens {
2125 gathered[token * matrix.out_features + row_start
2126 ..token * matrix.out_features + row_start + local_out]
2127 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2128 }
2129 rank_outputs.push(output);
2130 }
2131 Ok(ColumnParallelResult {
2132 gathered,
2133 rank_outputs,
2134 })
2135 }
2136
2137 pub fn upload_column_parallel(
2138 &self,
2139 matrix: E4m3BlockMatrix<'_>,
2140 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2141 matrix.validate()?;
2142 let tp = self.ranks.len();
2143 validate_column_shape(matrix, tp)?;
2144 let mut ranks = Vec::with_capacity(tp);
2145 for (rank_index, engine) in self.ranks.iter().enumerate() {
2146 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2147 }
2148 Ok(ResidentColumnParallel {
2149 ranks,
2150 out_features: matrix.out_features,
2151 in_features: matrix.in_features,
2152 })
2153 }
2154
2155 pub fn column_parallel_resident(
2156 &self,
2157 matrix: &ResidentColumnParallel,
2158 activations: &[f32],
2159 tokens: usize,
2160 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2161 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2162 validate_activations(activations, tokens, matrix.in_features)?;
2163 let local_out = matrix.out_features / self.ranks.len();
2164 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2165 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2166 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2167 let output = run_resident_rank(engine, shard, activations, tokens)?;
2168 let row_start = rank_index * local_out;
2169 for token in 0..tokens {
2170 gathered[token * matrix.out_features + row_start
2171 ..token * matrix.out_features + row_start + local_out]
2172 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2173 }
2174 rank_outputs.push(output);
2175 }
2176 Ok(ColumnParallelResult {
2177 gathered,
2178 rank_outputs,
2179 })
2180 }
2181
2182 pub fn row_parallel(
2186 &self,
2187 matrix: E4m3BlockMatrix<'_>,
2188 activations: &[f32],
2189 tokens: usize,
2190 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2191 matrix.validate()?;
2192 validate_activations(activations, tokens, matrix.in_features)?;
2193 let tp = self.ranks.len();
2194 if matrix.in_features % tp != 0 {
2195 return Err(format!(
2196 "row-parallel in_features {} is not divisible by TP={tp}",
2197 matrix.in_features
2198 )
2199 .into());
2200 }
2201 let local_in = matrix.in_features / tp;
2202 if local_in % FP8_BLOCK != 0 {
2203 return Err(format!(
2204 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2205 E4M3 scale block"
2206 )
2207 .into());
2208 }
2209
2210 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2211 let mut rank_partials = Vec::with_capacity(tp);
2212 for (rank_index, rank) in self.ranks.iter().enumerate() {
2213 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2214 let local_activations =
2215 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2216 let shard = E4m3BlockMatrix {
2217 codes: &codes,
2218 scales: &scales,
2219 out_features: matrix.out_features,
2220 in_features: local_in,
2221 };
2222 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2223 for (sum, value) in reduced.iter_mut().zip(&partial) {
2224 *sum += *value;
2225 }
2226 rank_partials.push(partial);
2227 }
2228 Ok(RowParallelResult {
2229 reduced,
2230 rank_partials,
2231 })
2232 }
2233
2234 pub fn upload_row_parallel(
2235 &self,
2236 matrix: E4m3BlockMatrix<'_>,
2237 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2238 matrix.validate()?;
2239 let tp = self.ranks.len();
2240 validate_row_shape(matrix, tp)?;
2241 let local_in = matrix.in_features / tp;
2242 let mut ranks = Vec::with_capacity(tp);
2243 for (rank_index, engine) in self.ranks.iter().enumerate() {
2244 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2245 ranks.push(upload_rank(
2246 engine,
2247 E4m3BlockMatrix {
2248 codes: &codes,
2249 scales: &scales,
2250 out_features: matrix.out_features,
2251 in_features: local_in,
2252 },
2253 )?);
2254 }
2255 Ok(ResidentRowParallel {
2256 ranks,
2257 out_features: matrix.out_features,
2258 in_features: matrix.in_features,
2259 })
2260 }
2261
2262 pub fn row_parallel_resident(
2263 &self,
2264 matrix: &ResidentRowParallel,
2265 activations: &[f32],
2266 tokens: usize,
2267 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2268 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2269 validate_activations(activations, tokens, matrix.in_features)?;
2270 let tp = self.ranks.len();
2271 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2272 let mut rank_partials = Vec::with_capacity(tp);
2273 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2274 let local_activations =
2275 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2276 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2277 for (sum, value) in reduced.iter_mut().zip(&partial) {
2278 *sum += *value;
2279 }
2280 rank_partials.push(partial);
2281 }
2282 Ok(RowParallelResult {
2283 reduced,
2284 rank_partials,
2285 })
2286 }
2287
2288 pub fn upload_bf16_column_parallel(
2289 &self,
2290 matrix: Bf16Matrix<'_>,
2291 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2292 self.upload_bf16_column_parallel_inner(matrix, None, false)
2293 }
2294
2295 pub fn upload_step_bf16_column_parallel(
2297 &self,
2298 matrix: Bf16Matrix<'_>,
2299 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2300 self.upload_step_bf16_column_parallel_inner(matrix, false)
2301 }
2302
2303 pub fn upload_step_bf16_column_parallel_f32_mirror(
2308 &self,
2309 matrix: Bf16Matrix<'_>,
2310 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2311 self.upload_step_bf16_column_parallel_inner(matrix, true)
2312 }
2313
2314 fn upload_step_bf16_column_parallel_inner(
2315 &self,
2316 matrix: Bf16Matrix<'_>,
2317 f32_mirror: bool,
2318 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2319 let canonical_chunk_rows =
2320 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2321 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2322 }
2323
2324 fn upload_bf16_column_parallel_inner(
2325 &self,
2326 matrix: Bf16Matrix<'_>,
2327 canonical_chunk_rows: Option<usize>,
2328 f32_mirror: bool,
2329 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2330 matrix.validate()?;
2331 let tp = self.ranks.len();
2332 if matrix.out_features % tp != 0 {
2333 return Err(format!(
2334 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2335 matrix.out_features
2336 )
2337 .into());
2338 }
2339 let mut ranks = Vec::with_capacity(tp);
2340 for (rank, engine) in self.ranks.iter().enumerate() {
2341 ranks.push(upload_bf16_rank(
2342 engine,
2343 bf16_column_shard(matrix, tp, rank)?,
2344 f32_mirror,
2345 )?);
2346 }
2347 Ok(ResidentBf16ColumnParallel {
2348 ranks,
2349 out_features: matrix.out_features,
2350 in_features: matrix.in_features,
2351 canonical_chunk_rows,
2352 })
2353 }
2354
2355 pub fn bf16_column_parallel_resident(
2356 &self,
2357 matrix: &ResidentBf16ColumnParallel,
2358 activations: &[f32],
2359 tokens: usize,
2360 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2361 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2362 validate_activations(activations, tokens, matrix.in_features)?;
2363 let local_out = matrix.out_features / self.ranks.len();
2364 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2365 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2366 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2367 let output = run_resident_bf16_rank(
2368 engine,
2369 shard,
2370 activations,
2371 tokens,
2372 matrix.canonical_chunk_rows,
2373 )?;
2374 for token in 0..tokens {
2375 let src = &output[token * local_out..(token + 1) * local_out];
2376 let dst_start = token * matrix.out_features + rank * local_out;
2377 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2378 }
2379 rank_outputs.push(output);
2380 }
2381 Ok(ColumnParallelResult {
2382 gathered,
2383 rank_outputs,
2384 })
2385 }
2386
2387 pub fn bf16_column_parallel_resident_native(
2394 &self,
2395 matrix: &ResidentBf16ColumnParallel,
2396 activations: &[f32],
2397 tokens: usize,
2398 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2399 let rank_outputs =
2400 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2401 let local_out = matrix.out_features / self.ranks.len();
2402 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2403 }
2404
2405 pub fn root_shares_ctx(&self, e: &Engine) -> bool {
2410 self.ranks
2411 .first()
2412 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2413 }
2414
2415 pub fn bf16_column_parallel_resident_native_device(
2428 &self,
2429 matrix: &ResidentBf16ColumnParallel,
2430 root_activation: &CudaSlice<f32>,
2431 tokens: usize,
2432 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2433 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2434 matrix,
2435 root_activation,
2436 tokens,
2437 )?;
2438 let local_out = matrix.out_features / self.ranks.len();
2439 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2440 let root = &self.ranks[0];
2441 let _main = root.gpu.enter_main()?;
2442 root.stream().synchronize()?;
2443 Ok(gathered)
2444 }
2445
2446 pub fn bf16_column_parallel_resident_device_shards_from_root(
2451 &self,
2452 matrix: &ResidentBf16ColumnParallel,
2453 root_activation: &CudaSlice<f32>,
2454 tokens: usize,
2455 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2456 if self.ranks.len() > 1 && !self.native_p2p {
2457 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2458 }
2459 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2460 let values = tokens
2461 .checked_mul(matrix.in_features)
2462 .ok_or("device BF16 column activation size overflow")?;
2463 let root = &self.ranks[0];
2464 if tokens == 0
2465 || root_activation.len() < values
2466 || root_activation.ordinal() != root.ctx().ordinal()
2467 {
2468 return Err("device BF16 column root activation geometry mismatch".into());
2469 }
2470
2471 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2472 let root_input = {
2473 let _main = root.gpu.enter_main()?;
2474 let mut root_input = root.uninit(values)?;
2475 root.stream()
2476 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2477 root_input
2478 };
2479 {
2483 let _main = root.gpu.enter_main()?;
2484 root.stream().synchronize()?;
2485 }
2486 rank_inputs.push(root_input);
2487 for engine in &self.ranks[1..] {
2488 let peer_input = {
2489 let _main = engine.gpu.enter_main()?;
2490 let mut peer_input = engine.uninit(values)?;
2491 engine
2492 .stream()
2493 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2494 peer_input
2495 };
2496 rank_inputs.push(peer_input);
2497 }
2498
2499 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2500 for rank in 0..self.ranks.len() {
2501 rank_outputs.push(run_resident_bf16_rank_device(
2502 &self.ranks[rank],
2503 &matrix.ranks[rank],
2504 &rank_inputs[rank],
2505 tokens,
2506 matrix.canonical_chunk_rows,
2507 self.bulk_p2p,
2508 )?);
2509 }
2510 Ok(rank_outputs)
2511 }
2512
2513 pub fn bf16_column_parallel_resident_device_shards(
2520 &self,
2521 matrix: &ResidentBf16ColumnParallel,
2522 activations: &[f32],
2523 tokens: usize,
2524 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2525 if self.ranks.len() > 1 && !self.native_p2p {
2526 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2527 }
2528 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2529 validate_activations(activations, tokens, matrix.in_features)?;
2530
2531 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2532 let root_input = {
2533 let root = &self.ranks[0];
2534 let _main = root.gpu.enter_main()?;
2535 root.htod(activations)?
2536 };
2537 {
2543 let root = &self.ranks[0];
2544 let _main = root.gpu.enter_main()?;
2545 root.stream().synchronize()?;
2546 }
2547 rank_inputs.push(root_input);
2548 for engine in &self.ranks[1..] {
2549 let peer_input = {
2550 let _main = engine.gpu.enter_main()?;
2551 let mut peer_input = engine.uninit(activations.len())?;
2552 engine
2553 .stream()
2554 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2555 peer_input
2556 };
2557 rank_inputs.push(peer_input);
2558 }
2559
2560 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2561 for rank in 0..self.ranks.len() {
2562 rank_outputs.push(run_resident_bf16_rank_device(
2563 &self.ranks[rank],
2564 &matrix.ranks[rank],
2565 &rank_inputs[rank],
2566 tokens,
2567 matrix.canonical_chunk_rows,
2568 self.bulk_p2p,
2569 )?);
2570 }
2571 Ok(rank_outputs)
2572 }
2573
2574 pub fn allocate_replicated_device_rows(
2578 &self,
2579 tokens: usize,
2580 width: usize,
2581 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2582 if self.ranks.len() > 1 && !self.native_p2p {
2583 return Err("replicated device rows require native P2P ranks".into());
2584 }
2585 let values = tokens
2586 .checked_mul(width)
2587 .ok_or("replicated device row size overflow")?;
2588 let rank_lengths = vec![values; self.ranks.len()];
2589 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2590 let mut ranks = Vec::with_capacity(self.ranks.len());
2591 for engine in &self.ranks {
2592 let _main = engine.gpu.enter_main()?;
2593 ranks.push(engine.uninit(values)?);
2594 }
2595 Ok(ResidentReplicatedDeviceRows {
2596 ranks,
2597 tokens,
2598 width,
2599 })
2600 }
2601
2602 pub fn refresh_replicated_device_rows_from_root(
2604 &self,
2605 rows: &mut ResidentReplicatedDeviceRows,
2606 source: &CudaSlice<f32>,
2607 ) -> Result<(), Box<dyn std::error::Error>> {
2608 if self.ranks.len() > 1 && !self.native_p2p {
2609 return Err("replicated device rows require native P2P ranks".into());
2610 }
2611 validate_replicated_device_rows(&self.ranks, rows)?;
2612 let root = self
2613 .ranks
2614 .first()
2615 .ok_or("replicated rows have no root rank")?;
2616 let values = replicated_device_row_source_values(
2617 rows.tokens,
2618 rows.width,
2619 source.len(),
2620 source.ordinal(),
2621 root.ctx().ordinal(),
2622 )?;
2623 let (root_rows, peer_rows) = rows
2624 .ranks
2625 .split_first_mut()
2626 .ok_or("replicated rows have no root allocation")?;
2627 {
2628 let _main = root.gpu.enter_main()?;
2629 let mut destination = root_rows.slice_mut(0..values);
2630 root.stream()
2631 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2632 root.stream().synchronize()?;
2633 }
2634 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2635 let _main = engine.gpu.enter_main()?;
2636 let mut destination = peer_rows.slice_mut(0..values);
2637 engine
2638 .stream()
2639 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2640 }
2641 Ok(())
2642 }
2643
2644 pub fn upload_replicated_device_rows(
2646 &self,
2647 rows: &[f32],
2648 tokens: usize,
2649 width: usize,
2650 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2651 if self.ranks.len() > 1 && !self.native_p2p {
2652 return Err("replicated device rows require native P2P ranks".into());
2653 }
2654 validate_activations(rows, tokens, width)?;
2655 let root = self
2656 .ranks
2657 .first()
2658 .ok_or("replicated rows have no root rank")?;
2659 let root_rows = {
2660 let _main = root.gpu.enter_main()?;
2661 root.htod(rows)?
2662 };
2663 {
2664 let _main = root.gpu.enter_main()?;
2665 root.stream().synchronize()?;
2666 }
2667 let mut ranks = Vec::with_capacity(self.ranks.len());
2668 ranks.push(root_rows);
2669 for engine in self.ranks.iter().skip(1) {
2670 let _main = engine.gpu.enter_main()?;
2671 let mut peer_rows = engine.uninit(rows.len())?;
2672 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2673 ranks.push(peer_rows);
2674 }
2675 Ok(ResidentReplicatedDeviceRows {
2676 ranks,
2677 tokens,
2678 width,
2679 })
2680 }
2681
2682 pub fn bf16_column_parallel_resident_replicated_device_shards(
2684 &self,
2685 matrix: &ResidentBf16ColumnParallel,
2686 activations: &ResidentReplicatedDeviceRows,
2687 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2688 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2689 validate_replicated_device_rows(&self.ranks, activations)?;
2690 if activations.width != matrix.in_features {
2691 return Err(format!(
2692 "replicated BF16 column input width {} != matrix width {}",
2693 activations.width, matrix.in_features
2694 )
2695 .into());
2696 }
2697 let mut outputs = Vec::with_capacity(self.ranks.len());
2698 for rank in 0..self.ranks.len() {
2699 outputs.push(run_resident_bf16_rank_device(
2700 &self.ranks[rank],
2701 &matrix.ranks[rank],
2702 &activations.ranks[rank],
2703 activations.tokens,
2704 matrix.canonical_chunk_rows,
2705 self.bulk_p2p,
2706 )?);
2707 }
2708 Ok(outputs)
2709 }
2710
2711 #[allow(clippy::too_many_arguments)]
2713 pub fn upload_sigmoid_topk_router(
2714 &self,
2715 weight: Bf16Matrix<'_>,
2716 correction_bias: &[f32],
2717 active: Option<&[bool]>,
2718 experts_per_token: usize,
2719 scaling_factor: f32,
2720 route_norm: bool,
2721 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2722 weight.validate()?;
2723 if correction_bias.len() != weight.out_features
2724 || experts_per_token == 0
2725 || experts_per_token > weight.out_features
2726 || !correction_bias.iter().all(|value| value.is_finite())
2727 || !scaling_factor.is_finite()
2728 || scaling_factor <= 0.0
2729 {
2730 return Err(format!(
2731 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2732 weight.out_features,
2733 weight.in_features,
2734 correction_bias.len(),
2735 experts_per_token,
2736 )
2737 .into());
2738 }
2739 let active_row = active
2740 .map(|mask| {
2741 if mask.len() != weight.out_features {
2742 return Err(format!(
2743 "sigmoid router active mask {} != experts {}",
2744 mask.len(),
2745 weight.out_features
2746 ));
2747 }
2748 Ok(mask
2749 .iter()
2750 .map(|&enabled| u8::from(enabled))
2751 .collect::<Vec<_>>())
2752 })
2753 .transpose()?
2754 .unwrap_or_else(|| vec![1; weight.out_features]);
2755 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2756 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2757
2758 let root = self
2759 .ranks
2760 .first()
2761 .ok_or("sigmoid router runtime has no root rank")?;
2762 let _main = root.gpu.enter_main()?;
2763 let bf16 = root.htod_bytes(weight.bytes)?;
2764 let weight_f32 = root.bf16_to_f32(
2765 &bf16.slice(0..bf16.len()),
2766 weight.out_features * weight.in_features,
2767 )?;
2768 Ok(ResidentSigmoidTopKRouter {
2769 weight: weight_f32,
2770 correction_bias: root.htod(correction_bias)?,
2771 active: root.htod_bytes(&active_row)?,
2772 root_device: root.ctx().ordinal(),
2773 input_width: weight.in_features,
2774 expert_count: weight.out_features,
2775 experts_per_token,
2776 active_count,
2777 scaling_factor,
2778 route_norm,
2779 })
2780 }
2781
2782 pub fn sigmoid_topk_replicated_device_rows_host(
2787 &self,
2788 router: &ResidentSigmoidTopKRouter,
2789 input: &ResidentReplicatedDeviceRows,
2790 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2791 validate_replicated_device_rows(&self.ranks, input)?;
2792 if input.width != router.input_width {
2793 return Err(format!(
2794 "sigmoid router input width {} != resident width {}",
2795 input.width, router.input_width
2796 )
2797 .into());
2798 }
2799 let root = self
2800 .ranks
2801 .first()
2802 .ok_or("sigmoid router runtime has no root rank")?;
2803 let _main = root.gpu.enter_main()?;
2804 if root.ctx().ordinal() != router.root_device
2805 || router.weight.ordinal() != router.root_device
2806 || router.correction_bias.ordinal() != router.root_device
2807 || router.active.ordinal() != router.root_device
2808 {
2809 return Err("sigmoid router root residency changed".into());
2810 }
2811 let logits = root.router_gemv(
2812 &router.weight,
2813 &input.ranks[0],
2814 router.input_width,
2815 router.expert_count,
2816 input.tokens,
2817 )?;
2818 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2819 &logits,
2820 input.tokens,
2821 router.expert_count,
2822 router.experts_per_token,
2823 router.active_count,
2824 &router.correction_bias,
2825 &router.active,
2826 router.scaling_factor,
2827 router.route_norm,
2828 )?;
2829 Ok(SigmoidTopKHostOutput {
2830 logits: root.dtoh(&logits)?,
2831 selected,
2832 weights,
2833 })
2834 }
2835
2836 pub fn upload_replicated_bf16_swiglu(
2838 &self,
2839 gate: Bf16Matrix<'_>,
2840 up: Bf16Matrix<'_>,
2841 down: Bf16Matrix<'_>,
2842 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2843 gate.validate()?;
2844 up.validate()?;
2845 down.validate()?;
2846 if gate.in_features != up.in_features
2847 || gate.out_features != up.out_features
2848 || down.in_features != gate.out_features
2849 || down.out_features != gate.in_features
2850 {
2851 return Err(format!(
2852 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2853 gate.out_features,
2854 gate.in_features,
2855 up.out_features,
2856 up.in_features,
2857 down.out_features,
2858 down.in_features,
2859 )
2860 .into());
2861 }
2862 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2863 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2864 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2865 for engine in &self.ranks {
2866 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2867 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2868 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2869 }
2870 Ok(ResidentReplicatedBf16SwiGlu {
2871 gate: gate_ranks,
2872 up: up_ranks,
2873 down: down_ranks,
2874 input_width: gate.in_features,
2875 intermediate_width: gate.out_features,
2876 })
2877 }
2878
2879 pub fn replicated_bf16_swiglu_resident_device(
2881 &self,
2882 mlp: &ResidentReplicatedBf16SwiGlu,
2883 input: &ResidentReplicatedDeviceRows,
2884 activation_limit: Option<f32>,
2885 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2886 validate_step_expert_activation_limit(activation_limit)?;
2887 validate_replicated_device_rows(&self.ranks, input)?;
2888 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2889 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2890 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2891 if input.width != mlp.input_width
2892 || mlp.gate.len() != self.ranks.len()
2893 || mlp.up.len() != self.ranks.len()
2894 || mlp.down.len() != self.ranks.len()
2895 {
2896 return Err("replicated BF16 SwiGLU residency or input width changed".into());
2897 }
2898
2899 let mut outputs = Vec::with_capacity(self.ranks.len());
2900 for rank in 0..self.ranks.len() {
2901 let engine = &self.ranks[rank];
2902 let gate = run_resident_bf16_rank_device(
2903 engine,
2904 &mlp.gate[rank],
2905 &input.ranks[rank],
2906 input.tokens,
2907 None,
2908 self.bulk_p2p,
2909 )?;
2910 let up = run_resident_bf16_rank_device(
2911 engine,
2912 &mlp.up[rank],
2913 &input.ranks[rank],
2914 input.tokens,
2915 None,
2916 self.bulk_p2p,
2917 )?;
2918 let _main = engine.gpu.enter_main()?;
2919 let values = input
2920 .tokens
2921 .checked_mul(mlp.intermediate_width)
2922 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
2923 let mut activation = engine.uninit(values)?;
2924 if let Some(limit) = activation_limit {
2925 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
2926 } else {
2927 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
2928 }
2929 outputs.push(run_resident_bf16_rank_device(
2930 engine,
2931 &mlp.down[rank],
2932 &activation,
2933 input.tokens,
2934 None,
2935 self.bulk_p2p,
2936 )?);
2937 }
2938 Ok(ResidentReplicatedDeviceRows {
2939 ranks: outputs,
2940 tokens: input.tokens,
2941 width: mlp.input_width,
2942 })
2943 }
2944
2945 pub fn rms_norm_replicated_device_rows(
2947 &self,
2948 input: &ResidentReplicatedDeviceRows,
2949 weight: &[f32],
2950 eps: f32,
2951 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2952 validate_replicated_device_rows(&self.ranks, input)?;
2953 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
2954 return Err(format!(
2955 "replicated RMS norm weight/eps {}/{} != width {}",
2956 weight.len(),
2957 eps,
2958 input.width
2959 )
2960 .into());
2961 }
2962 let mut ranks = Vec::with_capacity(self.ranks.len());
2963 for (rank, engine) in self.ranks.iter().enumerate() {
2964 let _main = engine.gpu.enter_main()?;
2965 let weight = engine.htod(weight)?;
2966 let mut output = engine.uninit(input.tokens * input.width)?;
2967 engine.rms_norm(
2968 &input.ranks[rank],
2969 &weight,
2970 &mut output,
2971 input.width,
2972 input.tokens,
2973 eps,
2974 )?;
2975 ranks.push(output);
2976 }
2977 Ok(ResidentReplicatedDeviceRows {
2978 ranks,
2979 tokens: input.tokens,
2980 width: input.width,
2981 })
2982 }
2983
2984 pub fn add_rms_norm_replicated_device_rows(
2986 &self,
2987 input: &ResidentReplicatedDeviceRows,
2988 update: &ResidentReplicatedDeviceRows,
2989 weight: &[f32],
2990 eps: f32,
2991 ) -> Result<
2992 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
2993 Box<dyn std::error::Error>,
2994 > {
2995 validate_replicated_device_rows(&self.ranks, input)?;
2996 validate_replicated_device_rows(&self.ranks, update)?;
2997 if input.tokens != update.tokens
2998 || input.width != update.width
2999 || weight.len() != input.width
3000 || !eps.is_finite()
3001 || eps <= 0.0
3002 {
3003 return Err(format!(
3004 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
3005 input.tokens,
3006 input.width,
3007 update.tokens,
3008 update.width,
3009 weight.len(),
3010 )
3011 .into());
3012 }
3013 let values = input.tokens * input.width;
3014 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
3015 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
3016 for (rank, engine) in self.ranks.iter().enumerate() {
3017 let _main = engine.gpu.enter_main()?;
3018 let weight = engine.htod(weight)?;
3019 let mut residual = engine.uninit(values)?;
3020 let mut normalized = engine.uninit(values)?;
3021 engine.add_rms_norm(
3022 &input.ranks[rank],
3023 &update.ranks[rank],
3024 &weight,
3025 &mut residual,
3026 &mut normalized,
3027 input.width,
3028 input.tokens,
3029 eps,
3030 )?;
3031 residual_ranks.push(residual);
3032 normalized_ranks.push(normalized);
3033 }
3034 Ok((
3035 ResidentReplicatedDeviceRows {
3036 ranks: residual_ranks,
3037 tokens: input.tokens,
3038 width: input.width,
3039 },
3040 ResidentReplicatedDeviceRows {
3041 ranks: normalized_ranks,
3042 tokens: input.tokens,
3043 width: input.width,
3044 },
3045 ))
3046 }
3047
3048 pub fn collect_replicated_device_rows(
3049 &self,
3050 rows: &ResidentReplicatedDeviceRows,
3051 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3052 validate_replicated_device_rows(&self.ranks, rows)?;
3053 let mut outputs = Vec::with_capacity(self.ranks.len());
3054 for (rank, engine) in self.ranks.iter().enumerate() {
3055 let _main = engine.gpu.enter_main()?;
3056 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3057 }
3058 Ok(outputs)
3059 }
3060
3061 pub fn upload_bf16_row_parallel(
3062 &self,
3063 matrix: Bf16Matrix<'_>,
3064 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3065 matrix.validate()?;
3066 let tp = self.ranks.len();
3067 if matrix.in_features % tp != 0 {
3068 return Err(format!(
3069 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3070 matrix.in_features
3071 )
3072 .into());
3073 }
3074 let mut ranks = Vec::with_capacity(tp);
3075 for (rank, engine) in self.ranks.iter().enumerate() {
3076 let shard = bf16_row_shard(matrix, tp, rank)?;
3077 ranks.push(upload_bf16_rank(
3078 engine,
3079 Bf16Matrix {
3080 bytes: &shard,
3081 out_features: matrix.out_features,
3082 in_features: matrix.in_features / tp,
3083 },
3084 false,
3085 )?);
3086 }
3087 Ok(ResidentBf16RowParallel {
3088 ranks,
3089 out_features: matrix.out_features,
3090 in_features: matrix.in_features,
3091 })
3092 }
3093
3094 pub fn bf16_row_parallel_resident(
3095 &self,
3096 matrix: &ResidentBf16RowParallel,
3097 activations: &[f32],
3098 tokens: usize,
3099 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3100 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3101 validate_activations(activations, tokens, matrix.in_features)?;
3102 let tp = self.ranks.len();
3103 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3104 let mut rank_partials = Vec::with_capacity(tp);
3105 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3106 let local_activations =
3107 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3108 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3109 for (sum, value) in reduced.iter_mut().zip(&partial) {
3110 *sum += value;
3111 }
3112 rank_partials.push(partial);
3113 }
3114 Ok(RowParallelResult {
3115 reduced,
3116 rank_partials,
3117 })
3118 }
3119
3120 pub fn upload_step_bf16_row_parallel(
3122 &self,
3123 matrix: Bf16Matrix<'_>,
3124 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3125 self.upload_step_bf16_row_parallel_inner(matrix, false)
3126 }
3127
3128 pub fn upload_step_bf16_row_parallel_f32_mirror(
3129 &self,
3130 matrix: Bf16Matrix<'_>,
3131 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3132 self.upload_step_bf16_row_parallel_inner(matrix, true)
3133 }
3134
3135 fn upload_step_bf16_row_parallel_inner(
3136 &self,
3137 matrix: Bf16Matrix<'_>,
3138 f32_mirror: bool,
3139 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3140 matrix.validate()?;
3141 let tp = self.ranks.len();
3142 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3143 let local_in = matrix.in_features / tp;
3144 let blocks_per_rank = local_in / canonical_chunk_cols;
3145 let mut ranks = Vec::with_capacity(tp);
3146 for (rank, engine) in self.ranks.iter().enumerate() {
3147 let mut blocks = Vec::with_capacity(blocks_per_rank);
3148 for block in 0..blocks_per_rank {
3149 let global_block = rank * blocks_per_rank + block;
3150 let col_start = global_block * canonical_chunk_cols;
3151 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3152 blocks.push(upload_bf16_rank(
3153 engine,
3154 Bf16Matrix {
3155 bytes: &bytes,
3156 out_features: matrix.out_features,
3157 in_features: canonical_chunk_cols,
3158 },
3159 f32_mirror,
3160 )?);
3161 }
3162 ranks.push(blocks);
3163 }
3164 Ok(ResidentStepBf16RowParallel {
3165 ranks,
3166 out_features: matrix.out_features,
3167 in_features: matrix.in_features,
3168 canonical_chunk_cols,
3169 })
3170 }
3171
3172 pub fn step_bf16_row_parallel_resident(
3177 &self,
3178 matrix: &ResidentStepBf16RowParallel,
3179 activations: &[f32],
3180 tokens: usize,
3181 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3182 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3183 validate_activations(activations, tokens, matrix.in_features)?;
3184 let root = &self.ranks[0];
3185 let output_len = tokens
3186 .checked_mul(matrix.out_features)
3187 .ok_or("Step BF16 row output size overflow")?;
3188 let mut reduced = {
3189 let _main = root.gpu.enter_main()?;
3190 root.htod(&vec![0.0f32; output_len])?
3191 };
3192 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3193 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3194 for (block, resident) in blocks.iter().enumerate() {
3195 let global_block = rank * blocks_per_rank + block;
3196 let input = activation_shard(
3197 activations,
3198 tokens,
3199 matrix.in_features,
3200 PRODUCT_MAX_CARDS,
3201 global_block,
3202 );
3203 let partial =
3204 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3205 let next = {
3206 let _main = root.gpu.enter_main()?;
3207 let partial = root.htod(&partial)?;
3208 let mut next = root.uninit(output_len)?;
3209 root.add(&reduced, &partial, &mut next, output_len)?;
3210 next
3211 };
3212 reduced = next;
3213 }
3214 }
3215 let _main = root.gpu.enter_main()?;
3216 root.dtoh(&reduced)
3217 }
3218
3219 pub fn step_bf16_row_parallel_resident_native(
3225 &self,
3226 matrix: &ResidentStepBf16RowParallel,
3227 activations: &[f32],
3228 tokens: usize,
3229 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3230 if self.ranks.len() > 1 && !self.native_p2p {
3231 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3232 }
3233 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3234 validate_activations(activations, tokens, matrix.in_features)?;
3235 let root = &self.ranks[0];
3236 let root_input = {
3237 let _main = root.gpu.enter_main()?;
3238 root.htod(activations)?
3239 };
3240 {
3243 let _main = root.gpu.enter_main()?;
3244 root.stream().synchronize()?;
3245 }
3246 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3247 let _main = root.gpu.enter_main()?;
3248 root.dtoh(&reduced)
3249 }
3250
3251 pub fn step_bf16_row_parallel_resident_native_device(
3259 &self,
3260 matrix: &ResidentStepBf16RowParallel,
3261 root_activation: &CudaSlice<f32>,
3262 tokens: usize,
3263 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3264 if self.ranks.len() > 1 && !self.native_p2p {
3265 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3266 }
3267 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3268 let values = tokens
3269 .checked_mul(matrix.in_features)
3270 .ok_or("device Step BF16 row activation size overflow")?;
3271 let root = &self.ranks[0];
3272 if tokens == 0
3273 || root_activation.len() < values
3274 || root_activation.ordinal() != root.ctx().ordinal()
3275 {
3276 return Err("device Step BF16 row root activation geometry mismatch".into());
3277 }
3278 let root_input = {
3279 let _main = root.gpu.enter_main()?;
3280 let mut root_input = root.uninit(values)?;
3281 root.stream()
3282 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3283 root.stream().synchronize()?; root_input
3285 };
3286 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3287 let _main = root.gpu.enter_main()?;
3288 root.stream().synchronize()?;
3289 Ok(reduced)
3290 }
3291
3292 fn step_bf16_row_native_reduce_from_root(
3297 &self,
3298 matrix: &ResidentStepBf16RowParallel,
3299 root_input: &CudaSlice<f32>,
3300 tokens: usize,
3301 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3302 let root = &self.ranks[0];
3303 let output_len = tokens
3304 .checked_mul(matrix.out_features)
3305 .ok_or("native Step BF16 row output size overflow")?;
3306 let mut reduced = {
3307 let _main = root.gpu.enter_main()?;
3308 root.htod(&vec![0.0f32; output_len])?
3309 };
3310 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3311 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3312 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3313 let mut remote_partial_keepalive = Vec::new();
3314 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3315 for (block, resident) in blocks.iter().enumerate() {
3316 let global_block = rank * blocks_per_rank + block;
3317 let col_start = global_block * matrix.canonical_chunk_cols;
3318 let block_len = tokens
3319 .checked_mul(matrix.canonical_chunk_cols)
3320 .ok_or("native Step BF16 row block size overflow")?;
3321 let block_input = if self.bulk_p2p {
3322 let root_packed = {
3323 let _main = root.gpu.enter_main()?;
3324 let mut root_packed = root.uninit(block_len)?;
3325 root.copy_rows_strided(
3326 &root_input,
3327 &mut root_packed,
3328 matrix.canonical_chunk_cols,
3329 tokens,
3330 matrix.in_features,
3331 col_start,
3332 )?;
3333 root_packed
3334 };
3335 if rank == 0 {
3336 root_packed
3337 } else {
3338 {
3341 let _main = root.gpu.enter_main()?;
3342 root.stream().synchronize()?;
3343 }
3344 let engine = &self.ranks[rank];
3345 let _main = engine.gpu.enter_main()?;
3346 let mut block_input = engine.uninit(block_len)?;
3347 engine
3348 .stream()
3349 .memcpy_dtod(&root_packed, &mut block_input)?;
3350 root_packed_keepalive.push(root_packed);
3351 block_input
3352 }
3353 } else {
3354 let engine = &self.ranks[rank];
3355 let _main = engine.gpu.enter_main()?;
3356 let mut block_input = engine.uninit(block_len)?;
3357 for token in 0..tokens {
3358 let source_start = token * matrix.in_features + col_start;
3359 let source = root_input
3360 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3361 let destination_start = token * matrix.canonical_chunk_cols;
3362 let mut destination = block_input.slice_mut(
3363 destination_start..destination_start + matrix.canonical_chunk_cols,
3364 );
3365 engine.stream().memcpy_dtod(&source, &mut destination)?;
3366 }
3367 block_input
3368 };
3369 let partial = run_resident_bf16_rank_device(
3370 &self.ranks[rank],
3371 resident,
3372 &block_input,
3373 tokens,
3374 None,
3375 self.bulk_p2p,
3376 )?;
3377 block_input_keepalive.push(block_input);
3378 let root_partial = if rank == 0 {
3379 partial
3380 } else {
3381 {
3384 let engine = &self.ranks[rank];
3385 let _main = engine.gpu.enter_main()?;
3386 engine.stream().synchronize()?;
3387 }
3388 let _main = root.gpu.enter_main()?;
3389 let mut peer_partial = root.uninit(output_len)?;
3390 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3391 remote_partial_keepalive.push(partial);
3392 peer_partial
3393 };
3394 let next = {
3395 let _main = root.gpu.enter_main()?;
3396 let mut next = root.uninit(output_len)?;
3397 root.add(&reduced, &root_partial, &mut next, output_len)?;
3398 next
3399 };
3400 reduced = next;
3401 }
3402 }
3403 {
3404 let _main = root.gpu.enter_main()?;
3405 root.stream().synchronize()?;
3406 }
3407 drop(remote_partial_keepalive);
3408 drop(root_packed_keepalive);
3409 drop(block_input_keepalive);
3410 Ok(reduced)
3411 }
3412
3413 pub fn step_bf16_row_parallel_resident_root_device(
3416 &self,
3417 matrix: &ResidentStepBf16RowParallel,
3418 rank_activations: &[CudaSlice<f32>],
3419 tokens: usize,
3420 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3421 if self.ranks.len() > 1 && !self.native_p2p {
3422 return Err(
3423 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3424 );
3425 }
3426 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3427 let local_width = matrix.in_features / self.ranks.len();
3428 let shard_len = tokens
3429 .checked_mul(local_width)
3430 .ok_or("device Step BF16 row shard size overflow")?;
3431 if tokens == 0
3432 || rank_activations.len() != self.ranks.len()
3433 || rank_activations
3434 .iter()
3435 .zip(&self.ranks)
3436 .any(|(rows, engine)| {
3437 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3438 })
3439 {
3440 return Err("device Step BF16 row activation shard geometry changed".into());
3441 }
3442
3443 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3444 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3445 let mut partials = Vec::with_capacity(self.ranks.len());
3446 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3447 if blocks.len() != blocks_per_rank {
3448 return Err(format!(
3449 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3450 blocks.len()
3451 )
3452 .into());
3453 }
3454 let engine = &self.ranks[rank];
3455 let _main = engine.gpu.enter_main()?;
3456 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3457 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3458 for (block, resident) in blocks.iter().enumerate() {
3459 let block_len = tokens
3460 .checked_mul(matrix.canonical_chunk_cols)
3461 .ok_or("device Step BF16 row block size overflow")?;
3462 let mut block_input = engine.uninit(block_len)?;
3463 let local_col_start = block * matrix.canonical_chunk_cols;
3464 if self.bulk_p2p {
3465 engine.copy_rows_strided(
3466 &rank_activations[rank],
3467 &mut block_input,
3468 matrix.canonical_chunk_cols,
3469 tokens,
3470 local_width,
3471 local_col_start,
3472 )?;
3473 } else {
3474 for token in 0..tokens {
3475 let source_start = token * local_width + local_col_start;
3476 let source = rank_activations[rank]
3477 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3478 let destination_start = token * matrix.canonical_chunk_cols;
3479 let mut destination = block_input.slice_mut(
3480 destination_start..destination_start + matrix.canonical_chunk_cols,
3481 );
3482 engine.stream().memcpy_dtod(&source, &mut destination)?;
3483 }
3484 }
3485 let partial = run_resident_bf16_rank_device(
3486 engine,
3487 resident,
3488 &block_input,
3489 tokens,
3490 None,
3491 self.bulk_p2p,
3492 )?;
3493 rank_inputs.push(block_input);
3494 rank_partials.push(partial);
3495 }
3496 block_inputs.push(rank_inputs);
3497 partials.push(rank_partials);
3498 }
3499 for engine in self.ranks.iter().skip(1) {
3500 let _main = engine.gpu.enter_main()?;
3501 engine.stream().synchronize()?;
3502 }
3503
3504 let output_len = tokens
3505 .checked_mul(matrix.out_features)
3506 .ok_or("device Step BF16 row output size overflow")?;
3507 let root = &self.ranks[0];
3508 let _main = root.gpu.enter_main()?;
3509 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3510 let mut remote_partials = Vec::new();
3511 for (rank, rank_partials) in partials.into_iter().enumerate() {
3512 for partial in rank_partials {
3513 let root_partial = if rank == 0 {
3514 partial
3515 } else {
3516 let mut peer_partial = root.uninit(output_len)?;
3517 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3518 remote_partials.push(partial);
3519 peer_partial
3520 };
3521 let mut next = root.uninit(output_len)?;
3522 root.add(&reduced, &root_partial, &mut next, output_len)?;
3523 reduced = next;
3524 }
3525 }
3526 root.stream().synchronize()?;
3527 drop(remote_partials);
3528 drop(block_inputs);
3529 Ok(reduced)
3530 }
3531
3532 pub fn step_bf16_row_parallel_resident_replicated_device(
3534 &self,
3535 matrix: &ResidentStepBf16RowParallel,
3536 rank_activations: &[CudaSlice<f32>],
3537 tokens: usize,
3538 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3539 let reduced =
3540 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3541 let output_len = tokens
3542 .checked_mul(matrix.out_features)
3543 .ok_or("device Step BF16 row output size overflow")?;
3544 let mut ranks = Vec::with_capacity(self.ranks.len());
3545 ranks.push(reduced);
3546 for engine in self.ranks.iter().skip(1) {
3547 let _main = engine.gpu.enter_main()?;
3548 let mut peer_output = engine.uninit(output_len)?;
3549 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3550 ranks.push(peer_output);
3551 }
3552 Ok(ResidentReplicatedDeviceRows {
3553 ranks,
3554 tokens,
3555 width: matrix.out_features,
3556 })
3557 }
3558
3559 pub fn upload_expert(
3560 &self,
3561 gate: E4m3BlockMatrix<'_>,
3562 up: E4m3BlockMatrix<'_>,
3563 down: E4m3BlockMatrix<'_>,
3564 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3565 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3566 return Err("TP expert gate/up dimensions differ".into());
3567 }
3568 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3569 return Err(format!(
3570 "TP expert down {}x{} does not invert gate/up {}x{}",
3571 down.out_features, down.in_features, gate.out_features, gate.in_features
3572 )
3573 .into());
3574 }
3575 Ok(ResidentTpExpert {
3576 gate: self.upload_column_parallel(gate)?,
3577 up: self.upload_column_parallel(up)?,
3578 down: self.upload_row_parallel(down)?,
3579 input_width: gate.in_features,
3580 expert_width: gate.out_features,
3581 })
3582 }
3583
3584 pub fn run_expert(
3585 &self,
3586 expert: &ResidentTpExpert,
3587 input: &[f32],
3588 tokens: usize,
3589 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3590 validate_activations(input, tokens, expert.input_width)?;
3591 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3592 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3593 let activated: Vec<f32> = gate
3594 .gathered
3595 .iter()
3596 .zip(&up.gathered)
3597 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3598 .collect();
3599 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3600 Ok(self
3601 .row_parallel_resident(&expert.down, &activated, tokens)?
3602 .reduced)
3603 }
3604
3605 pub fn upload_expert_parallel(
3606 &self,
3607 gate: E4m3ExpertBank<'_>,
3608 up: E4m3ExpertBank<'_>,
3609 down: E4m3ExpertBank<'_>,
3610 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3611 gate.validate()?;
3612 up.validate()?;
3613 down.validate()?;
3614 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3615 return Err("EP gate/up/down expert counts differ".into());
3616 }
3617 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3618 return Err("EP gate/up dimensions differ".into());
3619 }
3620 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3621 return Err(format!(
3622 "EP down {}x{} does not invert gate/up {}x{}",
3623 down.out_features, down.in_features, gate.out_features, gate.in_features
3624 )
3625 .into());
3626 }
3627 if gate.expert_count % self.ranks.len() != 0 {
3628 return Err(format!(
3629 "EP expert count {} is not divisible by {} ranks",
3630 gate.expert_count,
3631 self.ranks.len()
3632 )
3633 .into());
3634 }
3635
3636 let per_rank = gate.expert_count / self.ranks.len();
3637 let mut ranks = Vec::with_capacity(self.ranks.len());
3638 for (rank, engine) in self.ranks.iter().enumerate() {
3639 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3640 ranks.push(ResidentEpRank {
3641 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3642 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3643 down: upload_expert_bank_rank(engine, down, expert_range)?,
3644 });
3645 }
3646 Ok(ResidentExpertParallel {
3647 ranks,
3648 expert_count: gate.expert_count,
3649 input_width: gate.in_features,
3650 expert_width: gate.out_features,
3651 })
3652 }
3653
3654 #[allow(clippy::too_many_arguments)]
3660 pub fn prepare_step_grouped_fp8_gate(
3661 &self,
3662 gate: E4m3ExpertBank<'_>,
3663 up: E4m3ExpertBank<'_>,
3664 down: E4m3ExpertBank<'_>,
3665 input: &[f32],
3666 tokens: usize,
3667 selected: &[usize],
3668 activation_limit: Option<f32>,
3669 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3670 gate.validate()?;
3671 up.validate()?;
3672 down.validate()?;
3673 validate_step_expert_activation_limit(activation_limit)?;
3674 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3675 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3676 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3677 {
3678 return Err(format!(
3679 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3680 got gate/up/down={}/{}/{}",
3681 gate.expert_count, up.expert_count, down.expert_count,
3682 )
3683 .into());
3684 }
3685 if gate.in_features != up.in_features
3686 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3687 || up.out_features != STEP_GROUPED_FP8_WIDTH
3688 || down.in_features != STEP_GROUPED_FP8_WIDTH
3689 || down.out_features != gate.in_features
3690 {
3691 return Err(format!(
3692 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3693 gate.out_features,
3694 gate.in_features,
3695 up.out_features,
3696 up.in_features,
3697 down.out_features,
3698 down.in_features,
3699 )
3700 .into());
3701 }
3702 validate_activations(input, tokens, gate.in_features)?;
3703 let pairs = tokens
3704 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3705 .ok_or("official Step grouped FP8 route count overflow")?;
3706 if selected.len() != pairs {
3707 return Err(format!(
3708 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3709 ({pairs})",
3710 selected.len()
3711 )
3712 .into());
3713 }
3714 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3715 let mut unique = routes.to_vec();
3716 unique.sort_unstable();
3717 unique.dedup();
3718 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3719 return Err(format!(
3720 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3721 {routes:?}"
3722 )
3723 .into());
3724 }
3725 }
3726
3727 let engine = self
3728 .ranks
3729 .first()
3730 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3731 let _main = engine.gpu.enter_main()?;
3732 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3733 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3734 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3735 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3736 let input = engine.htod(input)?;
3737 let route_csr = ExpertCsr::from_token_routes(
3738 STEP_GROUPED_FP8_EXPERTS,
3739 tokens,
3740 STEP_GROUPED_FP8_TOP_K,
3741 selected,
3742 )?
3743 .upload(engine)?;
3744 let pair_rows = (0..pairs).collect::<Vec<_>>();
3745 let down_csr =
3746 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3747 .upload(engine)?;
3748 let gate_workspace =
3749 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3750 let up_workspace =
3751 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3752 let down_workspace =
3753 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3754 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3755 Ok(PreparedStepGroupedFp8Gate {
3756 device: engine.ctx().ordinal(),
3757 gate,
3758 up,
3759 down,
3760 input,
3761 route_csr,
3762 down_csr,
3763 gate_workspace,
3764 up_workspace,
3765 down_workspace,
3766 activation,
3767 activation_limit,
3768 tokens,
3769 pairs,
3770 })
3771 }
3772
3773 pub fn run_step_grouped_fp8_gate(
3775 &self,
3776 plan: &mut PreparedStepGroupedFp8Gate,
3777 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3778 let engine = self
3779 .ranks
3780 .first()
3781 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3782 if engine.ctx().ordinal() != plan.device {
3783 return Err(format!(
3784 "official Step grouped FP8 plan device {} != rank-zero device {}",
3785 plan.device,
3786 engine.ctx().ordinal()
3787 )
3788 .into());
3789 }
3790 let _main = engine.gpu.enter_main()?;
3791
3792 plan.gate_workspace.quantize(engine, &plan.input)?;
3793 plan.gate_workspace.project(
3794 engine,
3795 &plan.gate.codes,
3796 &plan.gate.scales,
3797 &plan.route_csr,
3798 plan.gate.code_stride,
3799 plan.gate.scale_stride,
3800 1.0,
3801 )?;
3802 plan.up_workspace.quantize(engine, &plan.input)?;
3803 plan.up_workspace.project(
3804 engine,
3805 &plan.up.codes,
3806 &plan.up.scales,
3807 &plan.route_csr,
3808 plan.up.code_stride,
3809 plan.up.scale_stride,
3810 1.0,
3811 )?;
3812 if let Some(limit) = plan.activation_limit {
3813 engine.silu_clamped_mul_host_expf(
3814 plan.gate_workspace.output(),
3815 plan.up_workspace.output(),
3816 limit,
3817 &mut plan.activation,
3818 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3819 )?;
3820 } else {
3821 engine.silu_mul_host_expf(
3822 plan.gate_workspace.output(),
3823 plan.up_workspace.output(),
3824 &mut plan.activation,
3825 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3826 )?;
3827 }
3828 plan.down_workspace.quantize(engine, &plan.activation)?;
3829 plan.down_workspace.project(
3830 engine,
3831 &plan.down.codes,
3832 &plan.down.scales,
3833 &plan.down_csr,
3834 plan.down.code_stride,
3835 plan.down.scale_stride,
3836 1.0,
3837 )?;
3838
3839 Ok(StepGroupedFp8ProjectionOutput {
3840 gate: engine.dtoh(plan.gate_workspace.output())?,
3841 up: engine.dtoh(plan.up_workspace.output())?,
3842 down: engine.dtoh(plan.down_workspace.output())?,
3843 })
3844 }
3845
3846 pub fn prepare_step_grouped_expert_parallel_gate(
3847 &self,
3848 experts: &ResidentExpertParallel,
3849 input: &[f32],
3850 tokens: usize,
3851 selected: &[usize],
3852 activation_limit: Option<f32>,
3853 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3854 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3855 experts,
3856 input,
3857 tokens,
3858 selected,
3859 activation_limit,
3860 tokens,
3861 )
3862 }
3863
3864 #[allow(clippy::too_many_arguments)]
3865 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3866 &self,
3867 experts: &ResidentExpertParallel,
3868 input: &[f32],
3869 tokens: usize,
3870 selected: &[usize],
3871 activation_limit: Option<f32>,
3872 max_tokens: usize,
3873 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3874 if !self.native_p2p || !self.ep_device_arithmetic {
3875 return Err(
3876 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3877 );
3878 }
3879 validate_step_expert_activation_limit(activation_limit)?;
3880 validate_ep_residency(&self.ranks, experts)?;
3881 validate_activations(input, tokens, experts.input_width)?;
3882 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3883 return Err(format!(
3884 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3885 )
3886 .into());
3887 }
3888 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3889 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3890 {
3891 return Err(format!(
3892 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3893 STEP_GROUPED_FP8_EXPERTS,
3894 STEP_GROUPED_FP8_WIDTH,
3895 experts.expert_count,
3896 experts.expert_width,
3897 )
3898 .into());
3899 }
3900 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3901 let max_pairs = max_tokens
3902 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3903 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
3904 let input_capacity = max_tokens
3905 .checked_mul(experts.input_width)
3906 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
3907
3908 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3909 for engine in &self.ranks {
3910 let _main = engine.gpu.enter_main()?;
3911 rank_inputs.push(engine.uninit(input_capacity)?);
3912 }
3913
3914 let mut owners = Vec::with_capacity(self.ranks.len());
3915 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
3916 if rank.gate.expert_range != rank.up.expert_range
3917 || rank.gate.expert_range != rank.down.expert_range
3918 {
3919 return Err(format!(
3920 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
3921 owner_rank
3922 )
3923 .into());
3924 }
3925 let local_experts = rank.gate.expert_range.len();
3926 let engine = &self.ranks[owner_rank];
3927 let _main = engine.gpu.enter_main()?;
3928 let route_csr =
3929 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
3930 let down_csr =
3931 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
3932 let gate_workspace = Fp8GroupedWorkspace::new(
3933 engine,
3934 experts.input_width,
3935 experts.expert_width,
3936 max_tokens,
3937 max_pairs,
3938 )?;
3939 let up_workspace = Fp8GroupedWorkspace::new(
3940 engine,
3941 experts.input_width,
3942 experts.expert_width,
3943 max_tokens,
3944 max_pairs,
3945 )?;
3946 let down_workspace = Fp8GroupedWorkspace::new(
3947 engine,
3948 experts.expert_width,
3949 experts.input_width,
3950 max_pairs,
3951 max_pairs,
3952 )?;
3953 let activation = engine.uninit(
3954 max_pairs
3955 .checked_mul(experts.expert_width)
3956 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
3957 )?;
3958 owners.push(PreparedStepGroupedExpertOwner {
3959 rank: owner_rank,
3960 global_pairs: Vec::new(),
3961 route_csr,
3962 down_csr,
3963 gate_workspace,
3964 up_workspace,
3965 down_workspace,
3966 activation,
3967 });
3968 }
3969
3970 let mut plan = PreparedStepGroupedExpertParallelGate {
3971 rank_inputs,
3972 owners,
3973 activation_limit,
3974 tokens: 0,
3975 pairs: 0,
3976 max_tokens,
3977 max_pairs,
3978 input_width: experts.input_width,
3979 expert_width: experts.expert_width,
3980 generation: 0,
3981 executed_generation: None,
3982 ready: false,
3983 };
3984 self.refresh_step_grouped_expert_parallel_gate(
3985 experts, &mut plan, input, tokens, selected,
3986 )?;
3987 Ok(plan)
3988 }
3989
3990 fn prepare_step_grouped_expert_parallel_refresh(
3991 &self,
3992 experts: &ResidentExpertParallel,
3993 plan: &PreparedStepGroupedExpertParallelGate,
3994 tokens: usize,
3995 selected: &[usize],
3996 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
3997 {
3998 validate_ep_residency(&self.ranks, experts)?;
3999 if plan.rank_inputs.len() != self.ranks.len()
4000 || plan.owners.len() != self.ranks.len()
4001 || plan.input_width != experts.input_width
4002 || plan.expert_width != experts.expert_width
4003 || tokens > plan.max_tokens
4004 {
4005 return Err(format!(
4006 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
4007 input={}/{} expert={}/{} tokens={}/{}",
4008 plan.rank_inputs.len(),
4009 self.ranks.len(),
4010 plan.owners.len(),
4011 self.ranks.len(),
4012 plan.input_width,
4013 experts.input_width,
4014 plan.expert_width,
4015 experts.expert_width,
4016 tokens,
4017 plan.max_tokens,
4018 )
4019 .into());
4020 }
4021 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
4022 if pairs > plan.max_pairs {
4023 return Err(format!(
4024 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
4025 plan.max_pairs
4026 )
4027 .into());
4028 }
4029 let next_generation = plan
4030 .generation
4031 .checked_add(1)
4032 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4033 let owner_routes = partition_expert_owner_routes(
4034 experts.expert_count,
4035 self.ranks.len(),
4036 tokens,
4037 STEP_GROUPED_FP8_TOP_K,
4038 selected,
4039 )?;
4040 let mut schedules = Vec::with_capacity(self.ranks.len());
4041 for routes in owner_routes {
4042 if routes.selected.is_empty() {
4043 schedules.push(None);
4044 continue;
4045 }
4046 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4047 let local_pairs = routes.selected.len();
4048 let route_csr = ExpertCsr::from_pair_rows(
4049 local_experts,
4050 tokens,
4051 &routes.selected,
4052 &routes.token_rows,
4053 )?;
4054 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4055 let down_csr = ExpertCsr::from_pair_rows(
4056 local_experts,
4057 local_pairs,
4058 &routes.selected,
4059 &down_rows,
4060 )?;
4061 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4062 global_pairs: routes.global_pairs,
4063 route_csr,
4064 down_csr,
4065 }));
4066 }
4067 Ok((pairs, next_generation, schedules))
4068 }
4069
4070 fn commit_step_grouped_expert_parallel_refresh(
4071 &self,
4072 plan: &mut PreparedStepGroupedExpertParallelGate,
4073 tokens: usize,
4074 pairs: usize,
4075 next_generation: u64,
4076 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4077 ) -> Result<(), Box<dyn std::error::Error>> {
4078 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4079 let engine = &self.ranks[owner.rank];
4080 let _main = engine.gpu.enter_main()?;
4081 if let Some(schedule) = schedule {
4082 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4083 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4084 owner.global_pairs = schedule.global_pairs;
4085 } else {
4086 owner.route_csr.clear();
4087 owner.down_csr.clear();
4088 owner.global_pairs.clear();
4089 }
4090 }
4091 plan.tokens = tokens;
4092 plan.pairs = pairs;
4093 plan.generation = next_generation;
4094 plan.ready = true;
4095 Ok(())
4096 }
4097
4098 pub fn refresh_step_grouped_expert_parallel_gate(
4099 &self,
4100 experts: &ResidentExpertParallel,
4101 plan: &mut PreparedStepGroupedExpertParallelGate,
4102 input: &[f32],
4103 tokens: usize,
4104 selected: &[usize],
4105 ) -> Result<(), Box<dyn std::error::Error>> {
4106 validate_activations(input, tokens, experts.input_width)?;
4107 let (pairs, next_generation, schedules) =
4108 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4109
4110 plan.ready = false;
4111 plan.executed_generation = None;
4112 {
4113 let root = &self.ranks[0];
4114 let _main = root.gpu.enter_main()?;
4115 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4116 root.stream().memcpy_htod(input, &mut destination)?;
4117 root.stream().synchronize()?;
4118 }
4119 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4120 let root_input = &root_inputs[0];
4121 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4122 let engine = &self.ranks[rank + 1];
4123 let _main = engine.gpu.enter_main()?;
4124 let mut destination = peer_input.slice_mut(0..input.len());
4125 engine
4126 .stream()
4127 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4128 }
4129 self.commit_step_grouped_expert_parallel_refresh(
4130 plan,
4131 tokens,
4132 pairs,
4133 next_generation,
4134 schedules,
4135 )
4136 }
4137
4138 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4143 &self,
4144 experts: &ResidentExpertParallel,
4145 plan: &mut PreparedStepGroupedExpertParallelGate,
4146 input: &CudaSlice<f32>,
4147 tokens: usize,
4148 selected: &[usize],
4149 ) -> Result<(), Box<dyn std::error::Error>> {
4150 let input_values = tokens
4151 .checked_mul(experts.input_width)
4152 .ok_or("Step owner-grouped FP8 input size overflow")?;
4153 let root = self
4154 .ranks
4155 .first()
4156 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4157 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4158 return Err(format!(
4159 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4160 device {}",
4161 input.len(),
4162 input.ordinal(),
4163 input_values,
4164 root.ctx().ordinal(),
4165 )
4166 .into());
4167 }
4168 let (pairs, next_generation, schedules) =
4169 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4170
4171 plan.ready = false;
4172 plan.executed_generation = None;
4173 {
4174 let _main = root.gpu.enter_main()?;
4175 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4176 root.stream()
4177 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4178 root.stream().synchronize()?;
4179 }
4180 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4181 let root_input = &root_inputs[0];
4182 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4183 let engine = &self.ranks[rank + 1];
4184 let _main = engine.gpu.enter_main()?;
4185 let mut destination = peer_input.slice_mut(0..input_values);
4186 engine
4187 .stream()
4188 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4189 }
4190 self.commit_step_grouped_expert_parallel_refresh(
4191 plan,
4192 tokens,
4193 pairs,
4194 next_generation,
4195 schedules,
4196 )
4197 }
4198
4199 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4204 &self,
4205 experts: &ResidentExpertParallel,
4206 plan: &mut PreparedStepGroupedExpertParallelGate,
4207 input: &ResidentReplicatedDeviceRows,
4208 ) -> Result<(), Box<dyn std::error::Error>> {
4209 validate_ep_residency(&self.ranks, experts)?;
4210 validate_replicated_device_rows(&self.ranks, input)?;
4211 if !plan.ready
4212 || input.tokens != plan.tokens
4213 || input.width != plan.input_width
4214 || input.tokens > plan.max_tokens
4215 || plan.rank_inputs.len() != self.ranks.len()
4216 || plan.owners.len() != self.ranks.len()
4217 || plan.input_width != experts.input_width
4218 || plan.expert_width != experts.expert_width
4219 {
4220 return Err("Step owner-grouped replicated input geometry changed".into());
4221 }
4222 let values = input
4223 .tokens
4224 .checked_mul(input.width)
4225 .ok_or("Step owner-grouped replicated input size overflow")?;
4226 let next_generation = plan
4227 .generation
4228 .checked_add(1)
4229 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4230 plan.ready = false;
4231 plan.executed_generation = None;
4232 for (rank, engine) in self.ranks.iter().enumerate() {
4233 let _main = engine.gpu.enter_main()?;
4234 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4235 engine
4236 .stream()
4237 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4238 }
4239 plan.generation = next_generation;
4240 plan.ready = true;
4241 Ok(())
4242 }
4243
4244 pub fn execute_step_grouped_expert_parallel_gate(
4245 &self,
4246 experts: &ResidentExpertParallel,
4247 plan: &mut PreparedStepGroupedExpertParallelGate,
4248 ) -> Result<(), Box<dyn std::error::Error>> {
4249 validate_ep_residency(&self.ranks, experts)?;
4250 if !plan.ready
4251 || plan.rank_inputs.len() != self.ranks.len()
4252 || plan.owners.len() != self.ranks.len()
4253 || plan.input_width != experts.input_width
4254 || plan.expert_width != experts.expert_width
4255 {
4256 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4257 }
4258 plan.executed_generation = None;
4259
4260 for owner in &mut plan.owners {
4261 if owner.global_pairs.is_empty() {
4262 continue;
4263 }
4264 let engine = &self.ranks[owner.rank];
4265 let bank = &experts.ranks[owner.rank];
4266 let _main = engine.gpu.enter_main()?;
4267 let local_pairs = owner.global_pairs.len();
4268 owner.gate_workspace.quantize_for_shape(
4269 engine,
4270 &plan.rank_inputs[owner.rank],
4271 plan.tokens,
4272 local_pairs,
4273 )?;
4274 owner.gate_workspace.project(
4275 engine,
4276 &bank.gate.codes,
4277 &bank.gate.scales,
4278 &owner.route_csr,
4279 bank.gate.code_stride,
4280 bank.gate.scale_stride,
4281 1.0,
4282 )?;
4283 owner.up_workspace.quantize_for_shape(
4284 engine,
4285 &plan.rank_inputs[owner.rank],
4286 plan.tokens,
4287 local_pairs,
4288 )?;
4289 owner.up_workspace.project(
4290 engine,
4291 &bank.up.codes,
4292 &bank.up.scales,
4293 &owner.route_csr,
4294 bank.up.code_stride,
4295 bank.up.scale_stride,
4296 1.0,
4297 )?;
4298 }
4299 for owner in &mut plan.owners {
4300 if owner.global_pairs.is_empty() {
4301 continue;
4302 }
4303 let engine = &self.ranks[owner.rank];
4304 let _main = engine.gpu.enter_main()?;
4305 let values = owner.global_pairs.len() * plan.expert_width;
4306 if let Some(limit) = plan.activation_limit {
4307 engine.silu_clamped_mul_host_expf(
4308 owner.gate_workspace.output(),
4309 owner.up_workspace.output(),
4310 limit,
4311 &mut owner.activation,
4312 values,
4313 )?;
4314 } else {
4315 engine.silu_mul_host_expf(
4316 owner.gate_workspace.output(),
4317 owner.up_workspace.output(),
4318 &mut owner.activation,
4319 values,
4320 )?;
4321 }
4322 }
4323 for owner in &mut plan.owners {
4324 if owner.global_pairs.is_empty() {
4325 continue;
4326 }
4327 let engine = &self.ranks[owner.rank];
4328 let bank = &experts.ranks[owner.rank];
4329 let _main = engine.gpu.enter_main()?;
4330 let local_pairs = owner.global_pairs.len();
4331 owner.down_workspace.quantize_for_shape(
4332 engine,
4333 &owner.activation,
4334 local_pairs,
4335 local_pairs,
4336 )?;
4337 owner.down_workspace.project(
4338 engine,
4339 &bank.down.codes,
4340 &bank.down.scales,
4341 &owner.down_csr,
4342 bank.down.code_stride,
4343 bank.down.scale_stride,
4344 1.0,
4345 )?;
4346 }
4347 plan.executed_generation = Some(plan.generation);
4348 Ok(())
4349 }
4350
4351 pub fn collect_step_grouped_expert_parallel_gate(
4352 &self,
4353 plan: &PreparedStepGroupedExpertParallelGate,
4354 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4355 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4356 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4357 }
4358 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4359 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4360 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4361 for owner in &plan.owners {
4362 if owner.global_pairs.is_empty() {
4363 continue;
4364 }
4365 let engine = &self.ranks[owner.rank];
4366 let _main = engine.gpu.enter_main()?;
4367 let owner_gate = engine.dtoh_view(
4368 &owner
4369 .gate_workspace
4370 .output()
4371 .slice(0..owner.gate_workspace.output_len()),
4372 )?;
4373 let owner_up = engine.dtoh_view(
4374 &owner
4375 .up_workspace
4376 .output()
4377 .slice(0..owner.up_workspace.output_len()),
4378 )?;
4379 let owner_down = engine.dtoh_view(
4380 &owner
4381 .down_workspace
4382 .output()
4383 .slice(0..owner.down_workspace.output_len()),
4384 )?;
4385 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4386 let local_expert = local_pair * plan.expert_width;
4387 let global_expert = global_pair * plan.expert_width;
4388 gate[global_expert..global_expert + plan.expert_width]
4389 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4390 up[global_expert..global_expert + plan.expert_width]
4391 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4392
4393 let local_hidden = local_pair * plan.input_width;
4394 let global_hidden = global_pair * plan.input_width;
4395 down[global_hidden..global_hidden + plan.input_width]
4396 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4397 }
4398 }
4399 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4400 }
4401
4402 pub fn run_step_grouped_expert_parallel_gate(
4403 &self,
4404 experts: &ResidentExpertParallel,
4405 plan: &mut PreparedStepGroupedExpertParallelGate,
4406 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4407 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4408 self.collect_step_grouped_expert_parallel_gate(plan)
4409 }
4410
4411 pub fn prepare_step_grouped_expert_parallel_combine(
4412 &self,
4413 plan: &PreparedStepGroupedExpertParallelGate,
4414 route_weights: &[f32],
4415 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4416 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4417 return Err(
4418 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4419 );
4420 }
4421 let owner_pairs = plan
4422 .owners
4423 .iter()
4424 .map(|owner| owner.global_pairs.as_slice())
4425 .collect::<Vec<_>>();
4426 let shape = validate_weighted_route_combine(
4427 plan.input_width,
4428 STEP_GROUPED_FP8_TOP_K,
4429 plan.max_tokens,
4430 plan.tokens,
4431 &owner_pairs,
4432 route_weights,
4433 )?;
4434 if shape.max_pairs != plan.max_pairs {
4435 return Err(format!(
4436 "Step owner-grouped combine capacity {} != projection capacity {}",
4437 shape.max_pairs, plan.max_pairs
4438 )
4439 .into());
4440 }
4441 let root = self
4442 .ranks
4443 .first()
4444 .ok_or("Step owner-grouped combine has no root rank")?;
4445 let slot_values = shape
4446 .max_pairs
4447 .checked_mul(plan.input_width)
4448 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4449 let output_values = plan
4450 .max_tokens
4451 .checked_mul(plan.input_width)
4452 .ok_or("Step owner-grouped combine output capacity overflow")?;
4453 let (root_device, owners, peer_staging, slots, weights, output) = {
4454 let _main = root.gpu.enter_main()?;
4455 let mut owners = Vec::with_capacity(plan.owners.len());
4456 for _ in &plan.owners {
4457 owners.push(PreparedPeerWeightedRouteOwner {
4458 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4459 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4460 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4461 active_pairs: 0,
4462 });
4463 }
4464 (
4465 root.ctx().ordinal(),
4466 owners,
4467 root.uninit(slot_values)?,
4468 root.uninit(slot_values)?,
4469 root.uninit(shape.max_pairs)?,
4470 root.uninit(output_values)?,
4471 )
4472 };
4473 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4474 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4475 for engine in self.ranks.iter().skip(1) {
4476 let _main = engine.gpu.enter_main()?;
4477 peer_devices.push(engine.ctx().ordinal());
4478 peer_outputs.push(engine.uninit(output_values)?);
4479 }
4480 let mut combine = PreparedPeerWeightedRouteCombine {
4481 root_device,
4482 owners,
4483 peer_staging,
4484 slots,
4485 weights,
4486 output,
4487 peer_devices,
4488 peer_outputs,
4489 width: plan.input_width,
4490 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4491 max_tokens: plan.max_tokens,
4492 max_pairs: shape.max_pairs,
4493 tokens: 0,
4494 pairs: 0,
4495 projection_generation: 0,
4496 output_generation: None,
4497 broadcast_generation: None,
4498 ready: false,
4499 };
4500 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4501 Ok(combine)
4502 }
4503
4504 pub fn refresh_step_grouped_expert_parallel_combine(
4505 &self,
4506 plan: &PreparedStepGroupedExpertParallelGate,
4507 combine: &mut PreparedPeerWeightedRouteCombine,
4508 route_weights: &[f32],
4509 ) -> Result<(), Box<dyn std::error::Error>> {
4510 let output_capacity = combine
4511 .max_tokens
4512 .checked_mul(combine.width)
4513 .ok_or("Step owner-grouped combine output capacity overflow")?;
4514 if !plan.ready
4515 || combine.owners.len() != plan.owners.len()
4516 || combine.peer_devices.len() + 1 != self.ranks.len()
4517 || combine.peer_outputs.len() + 1 != self.ranks.len()
4518 || combine.width != plan.input_width
4519 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4520 || combine.max_tokens != plan.max_tokens
4521 || combine.max_pairs != plan.max_pairs
4522 || combine.output.len() < output_capacity
4523 || combine
4524 .peer_outputs
4525 .iter()
4526 .any(|output| output.len() < output_capacity)
4527 {
4528 return Err("Step owner-grouped combine/projection geometry changed".into());
4529 }
4530 if self
4531 .ranks
4532 .iter()
4533 .skip(1)
4534 .zip(&combine.peer_devices)
4535 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4536 {
4537 return Err("Step owner-grouped combine peer devices changed".into());
4538 }
4539 let owner_pairs = plan
4540 .owners
4541 .iter()
4542 .map(|owner| owner.global_pairs.as_slice())
4543 .collect::<Vec<_>>();
4544 let shape = validate_weighted_route_combine(
4545 combine.width,
4546 combine.experts_per_token,
4547 combine.max_tokens,
4548 plan.tokens,
4549 &owner_pairs,
4550 route_weights,
4551 )?;
4552 if shape.max_pairs != combine.max_pairs {
4553 return Err("Step owner-grouped combine capacity changed during refresh".into());
4554 }
4555 let metadata = owner_pairs
4556 .iter()
4557 .map(|pairs| {
4558 let token_rows = pairs
4559 .iter()
4560 .map(|&pair| (pair / combine.experts_per_token) as i32)
4561 .collect::<Vec<_>>();
4562 let slots = pairs
4563 .iter()
4564 .map(|&pair| (pair % combine.experts_per_token) as i32)
4565 .collect::<Vec<_>>();
4566 let weights = pairs
4567 .iter()
4568 .map(|&pair| route_weights[pair])
4569 .collect::<Vec<_>>();
4570 (token_rows, slots, weights)
4571 })
4572 .collect::<Vec<_>>();
4573
4574 combine.ready = false;
4575 combine.output_generation = None;
4576 combine.broadcast_generation = None;
4577 let root = self
4578 .ranks
4579 .first()
4580 .ok_or("Step owner-grouped combine has no root rank")?;
4581 let _main = root.gpu.enter_main()?;
4582 if root.ctx().ordinal() != combine.root_device {
4583 return Err(format!(
4584 "Step owner-grouped combine root device changed {} != {}",
4585 root.ctx().ordinal(),
4586 combine.root_device
4587 )
4588 .into());
4589 }
4590 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4591 if token_rows.is_empty() {
4592 owner.active_pairs = 0;
4593 continue;
4594 }
4595 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4596 root.htod_i32_into(&mut owner.slots, &slots)?;
4597 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4598 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4599 owner.active_pairs = token_rows.len();
4600 }
4601 combine.tokens = plan.tokens;
4602 combine.pairs = shape.pairs;
4603 combine.projection_generation = plan.generation;
4604 combine.ready = true;
4605 Ok(())
4606 }
4607
4608 pub fn execute_step_grouped_expert_parallel_combine(
4609 &self,
4610 plan: &PreparedStepGroupedExpertParallelGate,
4611 combine: &mut PreparedPeerWeightedRouteCombine,
4612 ) -> Result<(), Box<dyn std::error::Error>> {
4613 if !plan.ready
4614 || plan.executed_generation != Some(plan.generation)
4615 || !combine.ready
4616 || combine.tokens != plan.tokens
4617 || combine.pairs != plan.pairs
4618 || combine.width != plan.input_width
4619 || combine.owners.len() != plan.owners.len()
4620 || combine.projection_generation != plan.generation
4621 {
4622 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4623 }
4624 combine.output_generation = None;
4625 combine.broadcast_generation = None;
4626 for owner in &plan.owners {
4627 if owner.rank == 0 || owner.global_pairs.is_empty() {
4628 continue;
4629 }
4630 let engine = &self.ranks[owner.rank];
4631 let _main = engine.gpu.enter_main()?;
4632 engine.stream().synchronize()?;
4633 }
4634 let root = self
4635 .ranks
4636 .first()
4637 .ok_or("Step owner-grouped combine has no root rank")?;
4638 let _main = root.gpu.enter_main()?;
4639 if root.ctx().ordinal() != combine.root_device {
4640 return Err("Step owner-grouped combine is not resident on the root device".into());
4641 }
4642 for (index, owner) in plan.owners.iter().enumerate() {
4643 let metadata = &combine.owners[index];
4644 if owner.global_pairs.len() != metadata.active_pairs {
4645 return Err(format!(
4646 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4647 owner.global_pairs.len(),
4648 metadata.active_pairs
4649 )
4650 .into());
4651 }
4652 if metadata.active_pairs == 0 {
4653 continue;
4654 }
4655 let values = metadata
4656 .active_pairs
4657 .checked_mul(combine.width)
4658 .ok_or("Step owner-grouped combine peer value count overflow")?;
4659 if owner.rank == 0 {
4660 root.scatter_slot(
4661 owner.down_workspace.output(),
4662 &metadata.token_rows,
4663 &metadata.slots,
4664 &metadata.weights,
4665 &mut combine.slots,
4666 &mut combine.weights,
4667 combine.width,
4668 combine.experts_per_token,
4669 metadata.active_pairs,
4670 )?;
4671 } else {
4672 let source = owner.down_workspace.output().slice(0..values);
4673 let mut destination = combine.peer_staging.slice_mut(0..values);
4674 root.stream().memcpy_dtod(&source, &mut destination)?;
4675 root.scatter_slot(
4676 &combine.peer_staging,
4677 &metadata.token_rows,
4678 &metadata.slots,
4679 &metadata.weights,
4680 &mut combine.slots,
4681 &mut combine.weights,
4682 combine.width,
4683 combine.experts_per_token,
4684 metadata.active_pairs,
4685 )?;
4686 }
4687 }
4688 root.reduce_slots_host(
4689 &combine.slots,
4690 &combine.weights,
4691 &mut combine.output,
4692 combine.width,
4693 combine.experts_per_token,
4694 combine.tokens,
4695 )?;
4696 combine.output_generation = Some(plan.generation);
4697 Ok(())
4698 }
4699
4700 pub fn collect_step_grouped_expert_parallel_combine(
4701 &self,
4702 plan: &PreparedStepGroupedExpertParallelGate,
4703 combine: &PreparedPeerWeightedRouteCombine,
4704 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4705 if !plan.ready
4706 || combine.output_generation != Some(plan.generation)
4707 || combine.projection_generation != plan.generation
4708 {
4709 return Err("Step owner-grouped combine output is stale or has not executed".into());
4710 }
4711 let root = self
4712 .ranks
4713 .first()
4714 .ok_or("Step owner-grouped combine has no root rank")?;
4715 let _main = root.gpu.enter_main()?;
4716 if root.ctx().ordinal() != combine.root_device {
4717 return Err("Step owner-grouped combine is not resident on the root device".into());
4718 }
4719 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4720 }
4721
4722 pub fn copy_step_grouped_expert_parallel_combine_root(
4727 &self,
4728 plan: &PreparedStepGroupedExpertParallelGate,
4729 combine: &PreparedPeerWeightedRouteCombine,
4730 destination: &Engine,
4731 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4732 if !plan.ready
4733 || combine.output_generation != Some(plan.generation)
4734 || combine.projection_generation != plan.generation
4735 {
4736 return Err("Step owner-grouped combine output is stale or has not executed".into());
4737 }
4738 let root = self
4739 .ranks
4740 .first()
4741 .ok_or("Step owner-grouped combine has no root rank")?;
4742 if root.ctx().ordinal() != combine.root_device
4743 || destination.ctx().ordinal() != combine.root_device
4744 {
4745 return Err(format!(
4746 "Step owner-grouped combine root/destination devices {}/{} != {}",
4747 root.ctx().ordinal(),
4748 destination.ctx().ordinal(),
4749 combine.root_device,
4750 )
4751 .into());
4752 }
4753 let values = combine
4754 .tokens
4755 .checked_mul(combine.width)
4756 .ok_or("Step owner-grouped combine copy size overflow")?;
4757 {
4758 let _main = root.gpu.enter_main()?;
4759 root.stream().synchronize()?;
4760 }
4761 let _main = destination.gpu.enter_main()?;
4762 let mut output = destination.uninit(values)?;
4763 destination
4764 .stream()
4765 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4766 Ok(output)
4767 }
4768
4769 pub fn broadcast_step_grouped_expert_parallel_combine(
4770 &self,
4771 plan: &PreparedStepGroupedExpertParallelGate,
4772 combine: &mut PreparedPeerWeightedRouteCombine,
4773 ) -> Result<(), Box<dyn std::error::Error>> {
4774 if !plan.ready
4775 || combine.output_generation != Some(plan.generation)
4776 || combine.projection_generation != plan.generation
4777 || combine.peer_devices.len() + 1 != self.ranks.len()
4778 || combine.peer_outputs.len() + 1 != self.ranks.len()
4779 {
4780 return Err("Step owner-grouped combine output cannot be broadcast".into());
4781 }
4782 combine.broadcast_generation = None;
4783 let values = combine
4784 .tokens
4785 .checked_mul(combine.width)
4786 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4787 {
4788 let root = self
4789 .ranks
4790 .first()
4791 .ok_or("Step owner-grouped combine has no root rank")?;
4792 let _main = root.gpu.enter_main()?;
4793 if root.ctx().ordinal() != combine.root_device {
4794 return Err("Step owner-grouped combine root device changed".into());
4795 }
4796 root.stream().synchronize()?;
4797 }
4798 let source = &combine.output;
4799 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4800 let engine = &self.ranks[index + 1];
4801 let _main = engine.gpu.enter_main()?;
4802 if engine.ctx().ordinal() != combine.peer_devices[index] {
4803 return Err(format!(
4804 "Step owner-grouped combine peer {} device changed",
4805 index + 1
4806 )
4807 .into());
4808 }
4809 let mut destination = destination_buffer.slice_mut(0..values);
4810 engine
4811 .stream()
4812 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4813 }
4814 combine.broadcast_generation = Some(plan.generation);
4815 Ok(())
4816 }
4817
4818 pub fn collect_step_grouped_expert_parallel_broadcast(
4819 &self,
4820 plan: &PreparedStepGroupedExpertParallelGate,
4821 combine: &PreparedPeerWeightedRouteCombine,
4822 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4823 if !plan.ready
4824 || combine.output_generation != Some(plan.generation)
4825 || combine.broadcast_generation != Some(plan.generation)
4826 || combine.peer_outputs.len() + 1 != self.ranks.len()
4827 {
4828 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4829 }
4830 let values = combine
4831 .tokens
4832 .checked_mul(combine.width)
4833 .ok_or("Step owner-grouped combine collection size overflow")?;
4834 let mut outputs = Vec::with_capacity(self.ranks.len());
4835 {
4836 let root = &self.ranks[0];
4837 let _main = root.gpu.enter_main()?;
4838 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4839 }
4840 for (index, output) in combine.peer_outputs.iter().enumerate() {
4841 let engine = &self.ranks[index + 1];
4842 let _main = engine.gpu.enter_main()?;
4843 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4844 }
4845 Ok(outputs)
4846 }
4847
4848 pub fn finish_step_grouped_expert_parallel_layer(
4850 &self,
4851 plan: &PreparedStepGroupedExpertParallelGate,
4852 combine: &PreparedPeerWeightedRouteCombine,
4853 shared: &ResidentReplicatedDeviceRows,
4854 residual: &ResidentReplicatedDeviceRows,
4855 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4856 validate_replicated_device_rows(&self.ranks, shared)?;
4857 validate_replicated_device_rows(&self.ranks, residual)?;
4858 if !plan.ready
4859 || plan.executed_generation != Some(plan.generation)
4860 || combine.output_generation != Some(plan.generation)
4861 || combine.broadcast_generation != Some(plan.generation)
4862 || combine.projection_generation != plan.generation
4863 || combine.peer_outputs.len() + 1 != self.ranks.len()
4864 || shared.tokens != combine.tokens
4865 || residual.tokens != combine.tokens
4866 || shared.width != combine.width
4867 || residual.width != combine.width
4868 {
4869 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4870 }
4871 let values = combine
4872 .tokens
4873 .checked_mul(combine.width)
4874 .ok_or("Step full-layer output size overflow")?;
4875 let mut ranks = Vec::with_capacity(self.ranks.len());
4876 for rank in 0..self.ranks.len() {
4877 let engine = &self.ranks[rank];
4878 let _main = engine.gpu.enter_main()?;
4879 let routed = if rank == 0 {
4880 &combine.output
4881 } else {
4882 &combine.peer_outputs[rank - 1]
4883 };
4884 let mut ffn = engine.uninit(values)?;
4885 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4886 let mut output = engine.uninit(values)?;
4887 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4888 ranks.push(output);
4889 }
4890 Ok(ResidentReplicatedDeviceRows {
4891 ranks,
4892 tokens: combine.tokens,
4893 width: combine.width,
4894 })
4895 }
4896
4897 pub fn run_step_grouped_expert_parallel_combine(
4898 &self,
4899 plan: &PreparedStepGroupedExpertParallelGate,
4900 combine: &mut PreparedPeerWeightedRouteCombine,
4901 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4902 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
4903 self.collect_step_grouped_expert_parallel_combine(plan, combine)
4904 }
4905
4906 pub fn upload_tensor_parallel(
4907 &self,
4908 gate: E4m3ExpertBank<'_>,
4909 up: E4m3ExpertBank<'_>,
4910 down: E4m3ExpertBank<'_>,
4911 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
4912 gate.validate()?;
4913 up.validate()?;
4914 down.validate()?;
4915 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4916 return Err("TP gate/up/down expert counts differ".into());
4917 }
4918 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4919 return Err("TP gate/up dimensions differ".into());
4920 }
4921 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4922 return Err(format!(
4923 "TP down {}x{} does not invert gate/up {}x{}",
4924 down.out_features, down.in_features, gate.out_features, gate.in_features
4925 )
4926 .into());
4927 }
4928 let tp = self.ranks.len();
4929 validate_column_bank_shape(gate, tp)?;
4930 validate_column_bank_shape(up, tp)?;
4931 validate_row_bank_shape(down, tp)?;
4932
4933 let mut gate_ranks = Vec::with_capacity(tp);
4934 let mut up_ranks = Vec::with_capacity(tp);
4935 let mut down_ranks = Vec::with_capacity(tp);
4936 for (rank, engine) in self.ranks.iter().enumerate() {
4937 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
4938 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
4939 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
4940 }
4941 Ok(ResidentTensorParallel {
4942 bank: ResidentTpExpertBank {
4943 gate: gate_ranks,
4944 up: up_ranks,
4945 down: down_ranks,
4946 expert_count: gate.expert_count,
4947 input_width: gate.in_features,
4948 expert_width: gate.out_features,
4949 },
4950 })
4951 }
4952
4953 pub fn run_tensor_parallel_routes(
4954 &self,
4955 experts: &ResidentTensorParallel,
4956 input: &[f32],
4957 tokens: usize,
4958 selected: &[usize],
4959 route_weights: &[f32],
4960 experts_per_token: usize,
4961 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4962 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
4963 validate_activations(input, tokens, experts.bank.input_width)?;
4964 let pairs = tokens
4965 .checked_mul(experts_per_token)
4966 .ok_or("TP route count overflow")?;
4967 if selected.len() != pairs || route_weights.len() != pairs {
4968 return Err(format!(
4969 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
4970 {experts_per_token} ({pairs})",
4971 selected.len(),
4972 route_weights.len(),
4973 )
4974 .into());
4975 }
4976 if !route_weights.iter().all(|weight| weight.is_finite()) {
4977 return Err("TP route weights contain a non-finite value".into());
4978 }
4979
4980 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
4981 for token in 0..tokens {
4982 let input_row =
4983 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
4984 for slot in 0..experts_per_token {
4985 let pair = token * experts_per_token + slot;
4986 let expert = selected[pair];
4987 if expert >= experts.bank.expert_count {
4988 return Err(format!(
4989 "TP selected expert {expert} outside 0..{}",
4990 experts.bank.expert_count
4991 )
4992 .into());
4993 }
4994 let down = if self.native_p2p {
4995 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
4996 } else {
4997 let gate =
4998 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
4999 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
5000 let activated: Vec<f32> = gate
5001 .iter()
5002 .zip(&up)
5003 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5004 .collect();
5005 debug_assert_eq!(activated.len(), experts.bank.expert_width);
5006 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
5007 };
5008 let weight = route_weights[pair];
5009 for (sum, value) in output
5010 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
5011 .iter_mut()
5012 .zip(down)
5013 {
5014 *sum += weight * value;
5015 }
5016 }
5017 }
5018 Ok(output)
5019 }
5020
5021 fn run_column_bank_expert(
5022 &self,
5023 ranks: &[ResidentE4m3ExpertBankRank],
5024 expert: usize,
5025 input: &[f32],
5026 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5027 let local_out = ranks
5028 .first()
5029 .ok_or("TP column bank has no ranks")?
5030 .out_features;
5031 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5032 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5033 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5034 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5035 }
5036 Ok(gathered)
5037 }
5038
5039 fn run_row_bank_expert(
5040 &self,
5041 ranks: &[ResidentE4m3ExpertBankRank],
5042 expert: usize,
5043 input: &[f32],
5044 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5045 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5046 if input.len() != local_in * ranks.len() {
5047 return Err(format!(
5048 "TP row input {} != {} ranks x {local_in}",
5049 input.len(),
5050 ranks.len()
5051 )
5052 .into());
5053 }
5054 let out_features = ranks[0].out_features;
5055 let mut reduced = vec![0.0f32; out_features];
5056 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5057 let blocks = bank
5058 .k_blocks
5059 .ok_or("TP row bank is not packed in native K-block order")?;
5060 if blocks * FP8_BLOCK != local_in {
5061 return Err(format!(
5062 "TP row bank has {blocks} blocks but local input width is {local_in}"
5063 )
5064 .into());
5065 }
5066 for block in 0..blocks {
5067 let global_start = rank * local_in + block * FP8_BLOCK;
5068 let partial = run_resident_bank_expert_block(
5069 engine,
5070 bank,
5071 expert,
5072 block,
5073 &input[global_start..global_start + FP8_BLOCK],
5074 )?;
5075 for (sum, value) in reduced.iter_mut().zip(partial) {
5076 *sum += value;
5077 }
5078 }
5079 }
5080 Ok(reduced)
5081 }
5082
5083 fn run_tensor_parallel_expert_native(
5084 &self,
5085 bank: &ResidentTpExpertBank,
5086 expert: usize,
5087 input: &[f32],
5088 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5089 if !self.native_p2p || self.ranks.len() < 2 {
5090 return Err("native TP expert execution requires at least two P2P ranks".into());
5091 }
5092 let local_out = bank
5093 .gate
5094 .first()
5095 .ok_or("native TP gate bank has no ranks")?
5096 .out_features;
5097 if local_out * self.ranks.len() != bank.expert_width {
5098 return Err(format!(
5099 "native TP gate shards {}x{local_out} != expert width {}",
5100 self.ranks.len(),
5101 bank.expert_width
5102 )
5103 .into());
5104 }
5105
5106 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5109 let root_input = {
5110 let root = &self.ranks[0];
5111 let _main = root.gpu.enter_main()?;
5112 root.htod(input)?
5113 };
5114 rank_inputs.push(root_input);
5115 for engine in &self.ranks[1..] {
5116 let peer_input = {
5117 let _main = engine.gpu.enter_main()?;
5118 let mut peer_input = engine.uninit(input.len())?;
5119 engine
5120 .stream()
5121 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5122 peer_input
5123 };
5124 rank_inputs.push(peer_input);
5125 }
5126
5127 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5128 let mut up_shards = Vec::with_capacity(self.ranks.len());
5129 for rank in 0..self.ranks.len() {
5130 gate_shards.push(run_resident_bank_expert_device(
5131 &self.ranks[rank],
5132 &bank.gate[rank],
5133 expert,
5134 &rank_inputs[rank],
5135 1,
5136 )?);
5137 up_shards.push(run_resident_bank_expert_device(
5138 &self.ranks[rank],
5139 &bank.up[rank],
5140 expert,
5141 &rank_inputs[rank],
5142 1,
5143 )?);
5144 }
5145
5146 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5150 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5151 let activated = gate
5152 .iter()
5153 .zip(&up)
5154 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5155 .collect::<Vec<_>>();
5156 debug_assert_eq!(activated.len(), bank.expert_width);
5157
5158 let root_activated = {
5159 let root = &self.ranks[0];
5160 let _main = root.gpu.enter_main()?;
5161 root.htod(&activated)?
5162 };
5163 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5164 for (rank, engine) in self.ranks.iter().enumerate() {
5165 let start = rank * local_out;
5166 let source = root_activated.slice(start..start + local_out);
5167 let local = {
5168 let _main = engine.gpu.enter_main()?;
5169 let mut local = engine.uninit(local_out)?;
5170 engine.stream().memcpy_dtod(&source, &mut local)?;
5171 local
5172 };
5173 rank_activated.push(local);
5174 }
5175
5176 let out_features = bank
5177 .down
5178 .first()
5179 .ok_or("native TP down bank has no ranks")?
5180 .out_features;
5181 let mut reduced = {
5182 let root = &self.ranks[0];
5183 let _main = root.gpu.enter_main()?;
5184 root.htod(&vec![0.0f32; out_features])?
5185 };
5186 let mut remote_partial_keepalive = Vec::new();
5187 for rank in 0..self.ranks.len() {
5188 let down = &bank.down[rank];
5189 let blocks = down
5190 .k_blocks
5191 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5192 if blocks * FP8_BLOCK != local_out {
5193 return Err(format!(
5194 "native TP rank {rank} has {blocks} blocks but local activation width is \
5195 {local_out}"
5196 )
5197 .into());
5198 }
5199 for block in 0..blocks {
5200 let start = block * FP8_BLOCK;
5201 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5202 let partial = run_resident_bank_expert_block_device(
5203 &self.ranks[rank],
5204 down,
5205 expert,
5206 block,
5207 &input_block,
5208 )?;
5209 let root_partial = if rank == 0 {
5210 partial
5211 } else {
5212 let root = &self.ranks[0];
5213 let _main = root.gpu.enter_main()?;
5214 let mut peer_partial = root.uninit(out_features)?;
5215 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5216 remote_partial_keepalive.push(partial);
5217 peer_partial
5218 };
5219 let next = {
5220 let root = &self.ranks[0];
5221 let _main = root.gpu.enter_main()?;
5222 let mut next = root.uninit(out_features)?;
5223 root.add(&reduced, &root_partial, &mut next, out_features)?;
5224 next
5225 };
5226 reduced = next;
5227 }
5228 }
5229 let output = {
5230 let root = &self.ranks[0];
5231 let _main = root.gpu.enter_main()?;
5232 root.dtoh(&reduced)?
5233 };
5234 drop(remote_partial_keepalive);
5235 Ok(output)
5236 }
5237
5238 pub fn gather_native_column_shards_device(
5240 &self,
5241 shards: &[CudaSlice<f32>],
5242 tokens: usize,
5243 local_out: usize,
5244 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5245 let shard_len = tokens
5246 .checked_mul(local_out)
5247 .ok_or("native TP gather shard size overflow")?;
5248 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5249 return Err("native TP gather shard geometry mismatch".into());
5250 }
5251 for engine in &self.ranks[1..] {
5255 let _main = engine.gpu.enter_main()?;
5256 engine.stream().synchronize()?;
5257 }
5258 let root = &self.ranks[0];
5259 let _main = root.gpu.enter_main()?;
5260 let global_out = shards
5261 .len()
5262 .checked_mul(local_out)
5263 .ok_or("native TP gather output width overflow")?;
5264 let gathered_len = tokens
5265 .checked_mul(global_out)
5266 .ok_or("native TP gather output size overflow")?;
5267 let mut gathered = root.uninit(gathered_len)?;
5268 if self.bulk_p2p {
5269 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5270 if shards.len() > 1 {
5271 let mut staging = root.uninit(shard_len)?;
5272 for (rank, shard) in shards.iter().enumerate().skip(1) {
5273 root.stream().memcpy_dtod(shard, &mut staging)?;
5274 root.place_rows_strided(
5275 &staging,
5276 &mut gathered,
5277 local_out,
5278 tokens,
5279 global_out,
5280 rank * local_out,
5281 )?;
5282 }
5283 }
5284 } else {
5285 for token in 0..tokens {
5286 for (rank, shard) in shards.iter().enumerate() {
5287 let source = shard.slice(token * local_out..(token + 1) * local_out);
5288 let start = token * global_out + rank * local_out;
5289 let mut destination = gathered.slice_mut(start..start + local_out);
5290 root.stream().memcpy_dtod(&source, &mut destination)?;
5291 }
5292 }
5293 }
5294 Ok(gathered)
5295 }
5296
5297 pub fn gather_native_column_shards(
5298 &self,
5299 shards: &[CudaSlice<f32>],
5300 tokens: usize,
5301 local_out: usize,
5302 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5303 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5304 let root = &self.ranks[0];
5305 let _main = root.gpu.enter_main()?;
5306 root.dtoh(&gathered)
5307 }
5308
5309 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5310 &self.decode_v2
5311 }
5312
5313 pub(crate) fn decode_v2_ensure(
5322 &self,
5323 e: &Engine,
5324 q_m: &ResidentBf16ColumnParallel,
5325 k_m: &ResidentBf16ColumnParallel,
5326 v_m: &ResidentBf16ColumnParallel,
5327 o_m: &ResidentStepBf16RowParallel,
5328 heads: usize,
5329 ) -> Result<usize, Box<dyn std::error::Error>> {
5330 if self.ranks.len() > 1 && !self.native_p2p {
5331 return Err("step TP decode v2 requires native P2P ranks".into());
5332 }
5333 let ranks = self.ranks.len();
5334 let fused_door = step_tp_qkv_fused_enabled()?;
5338 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5339 ResidentBf16Weight::F32(_) => true,
5340 ResidentBf16Weight::Bf16(_) => fused_door,
5341 };
5342 for matrix in [q_m, k_m, v_m] {
5343 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5344 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5345 return Err("step TP decode v2 QKV geometry mismatch".into());
5346 }
5347 for rank in &matrix.ranks {
5348 if !arm_ok(&rank.weight) {
5349 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5350 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5351 .into());
5352 }
5353 }
5354 }
5355 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5356 for blocks in &o_m.ranks {
5357 for block in blocks {
5358 if !arm_ok(&block.weight) {
5359 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5360 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5361 .into());
5362 }
5363 }
5364 }
5365 if v_m.out_features != k_m.out_features
5366 || o_m.in_features != q_m.out_features
5367 || heads == 0
5368 || heads % ranks != 0
5369 {
5370 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5371 }
5372 let local_q_dim = q_m.out_features / ranks;
5373 let local_kv_dim = k_m.out_features / ranks;
5374 let o_out = o_m.out_features;
5375 let o_block_cols = o_m.canonical_chunk_cols;
5376 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5377 if blocks_per_rank == 0
5378 || o_m
5379 .ranks
5380 .iter()
5381 .any(|blocks| blocks.len() != blocks_per_rank)
5382 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5383 {
5384 return Err("step TP decode v2 O canonical block grid mismatch".into());
5385 }
5386
5387 let mut guard = self
5388 .decode_v2
5389 .lock()
5390 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5391 if let Some(index) = guard.iter().position(|ws| {
5392 ws.local_q_dim == local_q_dim
5393 && ws.local_kv_dim == local_kv_dim
5394 && ws.heads == heads
5395 && ws.o_out == o_out
5396 && ws.o_block_cols == o_block_cols
5397 && ws.blocks_per_rank == blocks_per_rank
5398 && ws.e_device == e.ctx().ordinal()
5399 && ws.q.len() == ranks
5400 }) {
5401 return Ok(index);
5402 }
5403
5404 let mut q_raw = Vec::with_capacity(ranks);
5405 let mut k_raw = Vec::with_capacity(ranks);
5406 let mut v_raw = Vec::with_capacity(ranks);
5407 let mut q = Vec::with_capacity(ranks);
5408 let mut k = Vec::with_capacity(ranks);
5409 let mut pos = Vec::with_capacity(ranks);
5410 let mut gate = Vec::with_capacity(ranks);
5411 let mut attn_out = Vec::with_capacity(ranks);
5412 let mut gated = Vec::with_capacity(ranks);
5413 let mut fuse_ctr = Vec::with_capacity(ranks);
5414 let mut o_partials = Vec::with_capacity(ranks);
5415 let mut ev_rank = Vec::with_capacity(ranks);
5416 let direct_join = oproj_direct_on();
5417 for (rank, engine) in self.ranks.iter().enumerate() {
5418 let _main = engine.gpu.enter_main()?;
5419 q_raw.push(engine.uninit(local_q_dim)?);
5420 k_raw.push(engine.uninit(local_kv_dim)?);
5421 v_raw.push(engine.uninit(local_kv_dim)?);
5422 q.push(engine.uninit(local_q_dim)?);
5423 k.push(engine.uninit(local_kv_dim)?);
5424 pos.push(engine.htod_i32(&[0])?);
5425 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5426 gate.push(engine.uninit(heads / ranks)?);
5427 attn_out.push(engine.uninit(local_q_dim)?);
5428 gated.push(engine.uninit(local_q_dim)?);
5429 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5430 for _ in 0..blocks_per_rank {
5431 if direct_join && rank != 0 {
5434 let root = &self.ranks[0];
5435 let _root_main = root.gpu.enter_main()?;
5436 rank_partials.push(root.uninit(o_out)?);
5437 } else {
5438 rank_partials.push(engine.uninit(o_out)?);
5439 }
5440 }
5441 o_partials.push(rank_partials);
5442 ev_rank.push(engine.ctx().new_event(None)?);
5443 }
5444 let root = &self.ranks[0];
5445 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5446 let _main = root.gpu.enter_main()?;
5447 (
5448 root.uninit(o_out)?,
5449 root.uninit(o_out)?,
5450 root.uninit(o_out)?,
5451 root.htod(&vec![0.0f32; o_out])?,
5452 root.uninit(ranks * local_kv_dim)?,
5453 root.uninit(ranks * local_kv_dim)?,
5454 root.ctx().new_event(None)?,
5455 root.ctx().new_event(None)?,
5456 )
5457 };
5458 let (gate_e, ev_entry) = {
5459 let _main = e.gpu.enter_main()?;
5460 (e.uninit(heads)?, e.ctx().new_event(None)?)
5461 };
5462 let raw_attn_in = Vec::new();
5463 let raw_pos = Vec::new();
5464 guard.push(StepTpDecodeV2Ws {
5465 tcol_q: Vec::new(),
5466 tcol_k: Vec::new(),
5467 tcol_v: Vec::new(),
5468 tcol_g: Vec::new(),
5469 tcol_in: Vec::new(),
5470 tcol_cap: 0,
5471 w8_aq: Vec::new(),
5472 w8_ad: Vec::new(),
5473 w8_in: 0,
5474 w8o_aq: Vec::new(),
5475 w8o_ad: Vec::new(),
5476 w8o_in: 0,
5477 fa2_q: Vec::new(),
5478 fa2_gate: Vec::new(),
5479 fa2_gated: Vec::new(),
5480 fa2_cap: 0,
5481 rope_k_t: Vec::new(),
5482 rope_ctr_t: Vec::new(),
5483 rope_pos_t: Vec::new(),
5484 rows_tabs: Vec::new(),
5485 tcol_gated: Vec::new(),
5486 tcol_opart: Vec::new(),
5487 tcol_opeer: None,
5488 tcol_omix: None,
5489 tcol_ocap: 0,
5490 q_raw,
5491 k_raw,
5492 v_raw,
5493 q,
5494 k,
5495 pos,
5496 fuse_ctr,
5497 gate,
5498 attn_out,
5499 gated,
5500 o_partials,
5501 ev_rank,
5502 peer_partial,
5503 reduce_a,
5504 reduce_b,
5505 zeros,
5506 k_shadow,
5507 v_shadow,
5508 ev_refresh,
5509 ev_oproj,
5510 gate_e,
5511 attn_in: Vec::new(),
5512 h_stage: None,
5513 pos_stage: None,
5514 raw_h_stage: 0,
5515 raw_pos_stage: 0,
5516 raw_attn_in,
5517 raw_pos,
5518 raw_o_partial1: 0,
5519 raw_peer_partial: 0,
5520 raw_k1: 0,
5521 raw_v1: 0,
5522 raw_k_shadow: 0,
5523 raw_v_shadow: 0,
5524 raw_mixed_stage_e: 0,
5525 raw_reduce_a: 0,
5526 raw_shadow_stage_e: (0, 0),
5527 ev_entry,
5528 e_device: e.ctx().ordinal(),
5529 local_q_dim,
5530 local_kv_dim,
5531 heads,
5532 o_out,
5533 o_block_cols,
5534 blocks_per_rank,
5535 });
5536 eprintln!(
5537 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5538 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5539 residency=persistent ordering=evented performance_claim=false"
5540 );
5541 Ok(guard.len() - 1)
5542 }
5543
5544 #[allow(clippy::too_many_arguments)]
5552 #[allow(clippy::too_many_arguments)]
5557 pub fn decode_v2_input_qkv_tcol(
5558 &self,
5559 ws_index: usize,
5560 e: &Engine,
5561 h_t: &CudaSlice<f32>,
5562 t: usize,
5563 q_m: &ResidentBf16ColumnParallel,
5564 k_m: &ResidentBf16ColumnParallel,
5565 v_m: &ResidentBf16ColumnParallel,
5566 gate_shards: Option<StepTpGateShards<'_>>,
5567 ) -> Result<(), Box<dyn std::error::Error>> {
5568 let ranks = self.ranks.len();
5569 let mut guard = self
5570 .decode_v2
5571 .lock()
5572 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5573 let ws = guard
5574 .get_mut(ws_index)
5575 .ok_or("step TP decode v2 workspace index out of range")?;
5576 let in_f = q_m.in_features;
5577 if h_t.len() < t * in_f || t == 0 || t > 32 {
5578 return Err("decode_v2_input_qkv_tcol geometry".into());
5579 }
5580 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5582 ws.tcol_q.clear();
5583 ws.tcol_k.clear();
5584 ws.tcol_v.clear();
5585 ws.tcol_g.clear();
5586 ws.tcol_in.clear();
5587 for engine in &self.ranks {
5588 let _m = engine.gpu.enter_main()?;
5589 ws.tcol_q.push(engine.uninit(32 * ws.local_q_dim)?);
5590 ws.tcol_k.push(engine.uninit(32 * ws.local_kv_dim)?);
5591 ws.tcol_v.push(engine.uninit(32 * ws.local_kv_dim)?);
5592 ws.tcol_g
5593 .push(engine.uninit(32 * (ws.heads / ranks).max(1))?);
5594 ws.tcol_in.push(engine.uninit(32 * in_f)?);
5595 }
5596 ws.tcol_cap = 32;
5597 }
5598 use cudarc::driver::DevicePtr;
5600 let raw_src = {
5601 let _main = e.gpu.enter_main()?;
5602 let stream = e.stream();
5603 let (p, _g) = h_t.device_ptr(&stream);
5604 ws.ev_entry.record(&stream)?;
5605 p as u64
5606 };
5607 for rank in 0..ranks {
5608 let engine = &self.ranks[rank];
5609 let _main = engine.gpu.enter_main()?;
5610 engine.stream().wait(&ws.ev_entry)?;
5611 let raw_dst = {
5612 let stream = engine.stream();
5613 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5614 p as u64
5615 };
5616 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5617 let out_g = match &gate_shards {
5618 Some(_) => ws.heads / ranks,
5619 None => 0,
5620 };
5621 match (
5622 &q_m.ranks[rank].weight,
5623 &k_m.ranks[rank].weight,
5624 &v_m.ranks[rank].weight,
5625 ) {
5626 (
5627 ResidentBf16Weight::Bf16(wq),
5628 ResidentBf16Weight::Bf16(wk),
5629 ResidentBf16Weight::Bf16(wv),
5630 ) => {
5631 let wg = match &gate_shards {
5632 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5633 Some(StepTpGateShards::F32(_)) => {
5634 return Err(
5635 "tcol verify: gate shard class does not match bf16 QKV".into()
5636 );
5637 }
5638 None => wq,
5639 };
5640 let StepTpDecodeV2Ws {
5641 tcol_q,
5642 tcol_k,
5643 tcol_v,
5644 tcol_g,
5645 tcol_in,
5646 local_q_dim,
5647 local_kv_dim,
5648 ..
5649 } = &mut *ws;
5650 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5653 let refk = *REFK
5654 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5655 if refk {
5656 let lq = *local_q_dim;
5657 let lkv = *local_kv_dim;
5658 let mut hrow = engine.uninit(in_f)?;
5659 let mut qr = engine.uninit(lq)?;
5660 let mut kr = engine.uninit(lkv)?;
5661 let mut vr = engine.uninit(lkv)?;
5662 let mut gr = engine.uninit(out_g.max(1))?;
5663 for c in 0..t {
5664 {
5665 let mut dst = hrow.slice_mut(0..in_f);
5666 engine.stream().memcpy_dtod(
5667 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5668 &mut dst,
5669 )?;
5670 }
5671 engine.matvec_bf16_qkvg_into(
5672 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5673 lq, lkv, out_g,
5674 )?;
5675 let stream = engine.stream();
5676 {
5677 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5678 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5679 }
5680 {
5681 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5682 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5683 }
5684 {
5685 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5686 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5687 }
5688 if out_g > 0 {
5689 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5690 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5691 }
5692 }
5693 } else {
5694 engine.matvec_bf16_qkvg_tcol_into(
5695 wq,
5696 wk,
5697 wv,
5698 wg,
5699 &tcol_in[rank],
5700 &mut tcol_q[rank],
5701 &mut tcol_k[rank],
5702 &mut tcol_v[rank],
5703 &mut tcol_g[rank],
5704 in_f,
5705 *local_q_dim,
5706 *local_kv_dim,
5707 out_g,
5708 t,
5709 )?;
5710 }
5711 }
5712 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5713 }
5714 }
5715 Ok(())
5716 }
5717
5718 pub(crate) fn decode_v2_oproj_tcol_eligible(
5722 &self,
5723 ws: &StepTpDecodeV2Ws,
5724 o_m: &ResidentStepBf16RowParallel,
5725 ) -> bool {
5726 self.ranks.len() == 2
5727 && ws.blocks_per_rank == 4
5728 && step_tp_qkv_fused_enabled().unwrap_or(false)
5729 && no_local_shadow_on()
5730 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5731 && o_m
5732 .ranks
5733 .iter()
5734 .flatten()
5735 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5736 }
5737
5738 pub(crate) fn decode_v2_stash_fa2(
5743 &self,
5744 ws: &mut StepTpDecodeV2Ws,
5745 e: &Engine,
5746 col: usize,
5747 ) -> Result<(), Box<dyn std::error::Error>> {
5748 let ranks = self.ranks.len();
5749 if col >= 32 {
5750 return Err("decode_v2_stash_fa2 column out of range".into());
5751 }
5752 let lq = ws.local_q_dim;
5753 let lg = (ws.heads / ranks).max(1);
5754 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks {
5755 ws.fa2_q.clear();
5756 ws.fa2_gate.clear();
5757 ws.fa2_gated.clear();
5758 ws.rope_k_t.clear();
5759 ws.rope_ctr_t.clear();
5760 ws.rope_pos_t.clear();
5761 for engine in &self.ranks {
5762 let _m = engine.gpu.enter_main()?;
5763 ws.fa2_q.push(engine.uninit(32 * lq)?);
5764 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5765 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5766 ws.rope_k_t.push(engine.uninit(32 * ws.local_kv_dim)?);
5767 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5768 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5769 }
5770 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5771 ws.fa2_cap = 32;
5772 }
5773 for rank in 0..ranks {
5774 let engine = &self.ranks[rank];
5775 let _main = engine.gpu.enter_main()?;
5776 {
5777 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5778 engine
5779 .stream()
5780 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5781 }
5782 {
5783 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5784 engine
5785 .stream()
5786 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5787 }
5788 ws.ev_rank[rank].record(&engine.stream())?;
5789 }
5790 {
5791 let _main = e.gpu.enter_main()?;
5792 for ev in ws.ev_rank.iter() {
5793 e.stream().wait(ev)?;
5794 }
5795 }
5796 Ok(())
5797 }
5798
5799 #[allow(clippy::too_many_arguments)]
5806 pub(crate) fn decode_v2_spec_fa2_join(
5807 &self,
5808 ws_index: usize,
5809 e: &Engine,
5810 o_m: &ResidentStepBf16RowParallel,
5811 kv: &ResidentTpKvCache,
5812 head_dim: usize,
5813 window: usize,
5814 bucket_max: usize,
5815 scale: f32,
5816 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5817 let ranks = self.ranks.len();
5818 static ONCE: std::sync::Once = std::sync::Once::new();
5820 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
5821 {
5822 let mut guard = self
5823 .decode_v2
5824 .lock()
5825 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5826 let ws = guard
5827 .get_mut(ws_index)
5828 .ok_or("step TP decode v2 workspace index out of range")?;
5829 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
5830 return Err("spec fa2 join without stashed columns".into());
5831 }
5832 let lq = ws.local_q_dim;
5833 let local_heads = (ws.heads / ranks).max(1);
5834 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
5835 let capacity = kv.physical_capacity();
5836 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
5837 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
5839 ws.tcol_gated.clear();
5840 ws.tcol_opart.clear();
5841 for engine in &self.ranks {
5842 let _m = engine.gpu.enter_main()?;
5843 ws.tcol_gated.push(engine.uninit(32 * lq)?);
5844 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
5845 }
5846 let root = &self.ranks[0];
5847 let _m = root.gpu.enter_main()?;
5848 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
5849 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
5850 ws.tcol_ocap = 32;
5851 }
5852 for rank in 0..ranks {
5853 let engine = &self.ranks[rank];
5854 let _main = engine.gpu.enter_main()?;
5855 let rank_cache = kv
5856 .rank(rank)
5857 .ok_or("spec fa2 join lost its KV cache rank")?;
5858 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
5859 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
5860 {
5861 let StepTpDecodeV2Ws {
5862 fa2_q,
5863 fa2_gate,
5864 fa2_gated,
5865 ..
5866 } = &mut *ws;
5867 engine.fa_decode_dcw2(
5868 &fa2_q[rank],
5869 &k_ring,
5870 &v_ring,
5871 &mut fa2_gated[rank],
5872 head_dim,
5873 local_heads,
5874 local_kv_heads,
5875 rank_cache.len_d(),
5876 rank_cache.base_d(),
5877 window,
5878 bucket_max,
5879 scale,
5880 k_tok_bytes,
5881 v_tok_bytes,
5882 &fa2_gate[rank],
5883 )?;
5884 }
5885 let StepTpDecodeV2Ws {
5888 fa2_gated,
5889 tcol_gated,
5890 ..
5891 } = &mut *ws;
5892 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
5893 engine
5894 .stream()
5895 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
5896 }
5897 }
5898 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
5899 }
5900
5901 #[allow(clippy::too_many_arguments)]
5911 pub(crate) fn decode_v2_rope_fa_rows(
5912 &self,
5913 ws_index: usize,
5914 e: &Engine,
5915 o_m: &ResidentStepBf16RowParallel,
5916 session_parts: &[Vec<[u64; 4]>],
5917 tab_keys: &[u64],
5918 positions: &[i32],
5919 stage_pos: bool,
5920 same_session: bool,
5921 q_norms: &[CudaSlice<f32>],
5922 k_norms: &[CudaSlice<f32>],
5923 rope_freqs: &[Option<&crate::CudaSlice<f32>>],
5924 t: usize,
5925 head_dim: usize,
5926 n_rot: usize,
5927 window: usize,
5928 max_ns: usize,
5929 scale: f32,
5930 k_tok_bytes: usize,
5931 v_tok_bytes: usize,
5932 eps: f32,
5933 rope_base: f32,
5934 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
5935 use cudarc::driver::DevicePtr;
5936 let ranks = self.ranks.len();
5937 if session_parts.len() != ranks || tab_keys.len() != ranks || positions.len() < t {
5938 return Err("rope fa rows geometry".into());
5939 }
5940 {
5941 let mut guard = self
5942 .decode_v2
5943 .lock()
5944 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5945 let ws = guard
5946 .get_mut(ws_index)
5947 .ok_or("step TP decode v2 workspace index out of range")?;
5948 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5949 return Err("rope fa rows without tcol slabs".into());
5950 }
5951 let lq = ws.local_q_dim;
5952 let lkv = ws.local_kv_dim;
5953 let lg = (ws.heads / ranks).max(1);
5954 let local_heads = (ws.heads / ranks).max(1);
5955 let local_kv_heads = (lkv / head_dim).max(1);
5956 if ws.fa2_cap < 32 || ws.fa2_q.len() != ranks {
5958 ws.fa2_q.clear();
5959 ws.fa2_gate.clear();
5960 ws.fa2_gated.clear();
5961 ws.rope_k_t.clear();
5962 ws.rope_ctr_t.clear();
5963 ws.rope_pos_t.clear();
5964 for engine in &self.ranks {
5965 let _m = engine.gpu.enter_main()?;
5966 ws.fa2_q.push(engine.uninit(32 * lq)?);
5967 ws.fa2_gate.push(engine.uninit(32 * lg)?);
5968 ws.fa2_gated.push(engine.uninit(32 * lq)?);
5969 ws.rope_k_t.push(engine.uninit(32 * lkv)?);
5970 ws.rope_ctr_t.push(engine.stream().clone_htod(&[0u32; 32])?);
5971 ws.rope_pos_t.push(engine.htod_i32(&[0i32; 32])?);
5972 }
5973 ws.rows_tabs = (0..ranks).map(|_| Default::default()).collect();
5974 ws.fa2_cap = 32;
5975 }
5976 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
5977 ws.tcol_gated.clear();
5978 ws.tcol_opart.clear();
5979 for engine in &self.ranks {
5980 let _m = engine.gpu.enter_main()?;
5981 ws.tcol_gated.push(engine.uninit(32 * lq)?);
5982 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
5983 }
5984 let root = &self.ranks[0];
5985 let _m = root.gpu.enter_main()?;
5986 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
5987 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
5988 ws.tcol_ocap = 32;
5989 }
5990 for rank in 0..ranks {
5991 let engine = &self.ranks[rank];
5992 let _main = engine.gpu.enter_main()?;
5993 if stage_pos {
5994 let host: Vec<i32> = positions[..t].to_vec();
5995 let mut view = ws.rope_pos_t[rank].slice_mut(0..t);
5996 engine.stream().memcpy_htod(&host, &mut view)?;
5997 }
5998 if !ws.rows_tabs[rank].contains_key(&tab_keys[rank]) {
6001 let ctr_base = {
6002 let s = engine.stream();
6003 let (p, _g) = ws.rope_ctr_t[rank].device_ptr(&s);
6004 p as u64
6005 };
6006 let mut host = Vec::with_capacity(t * 6);
6007 for (r, parts) in session_parts[rank].iter().enumerate().take(t) {
6008 host.extend_from_slice(&[
6009 parts[0],
6010 parts[1],
6011 parts[2],
6012 parts[3],
6013 if same_session {
6014 ctr_base
6015 } else {
6016 ctr_base + (r as u64) * 4
6017 },
6018 if same_session {
6019 (t - 1 - r) as u64
6020 } else {
6021 0u64
6022 },
6023 ]);
6024 }
6025 let tab = engine.stream().clone_htod(&host)?;
6026 ws.rows_tabs[rank].insert(tab_keys[rank], tab);
6027 }
6028 let StepTpDecodeV2Ws {
6029 tcol_q,
6030 tcol_k,
6031 tcol_v,
6032 tcol_g,
6033 fa2_q,
6034 fa2_gated,
6035 rope_k_t,
6036 rope_pos_t,
6037 rows_tabs,
6038 ..
6039 } = &mut *ws;
6040 let tab = rows_tabs[rank]
6041 .get(&tab_keys[rank])
6042 .expect("inserted above");
6043 engine.qk_norm_rope_append_inc_dcw_rows(
6044 &tcol_q[rank],
6045 &tcol_k[rank],
6046 &tcol_v[rank],
6047 &q_norms[rank],
6048 &k_norms[rank],
6049 &mut fa2_q[rank],
6050 &mut rope_k_t[rank],
6051 tab,
6052 &rope_pos_t[rank],
6053 same_session,
6054 t,
6055 lkv,
6056 lkv,
6057 k_tok_bytes,
6058 v_tok_bytes,
6059 head_dim,
6060 n_rot,
6061 local_heads,
6062 local_kv_heads,
6063 eps,
6064 rope_base,
6065 1.0,
6066 rope_freqs[rank],
6067 )?;
6068 engine.fa_decode_dcw_rows(
6069 &fa2_q[rank],
6070 tab,
6071 &mut fa2_gated[rank],
6072 t,
6073 head_dim,
6074 local_heads,
6075 local_kv_heads,
6076 window,
6077 max_ns,
6078 scale,
6079 k_tok_bytes,
6080 v_tok_bytes,
6081 &tcol_g[rank],
6082 )?;
6083 let StepTpDecodeV2Ws {
6084 fa2_gated,
6085 tcol_gated,
6086 ..
6087 } = &mut *ws;
6088 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6089 engine
6090 .stream()
6091 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6092 }
6093 }
6094 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6095 }
6096
6097 #[allow(clippy::too_many_arguments)]
6104 pub(crate) fn decode_v2_fa_rows_join(
6105 &self,
6106 ws_index: usize,
6107 e: &Engine,
6108 o_m: &ResidentStepBf16RowParallel,
6109 tabs: &[&crate::CudaSlice<u64>],
6110 t: usize,
6111 head_dim: usize,
6112 window: usize,
6113 max_ns: usize,
6114 scale: f32,
6115 k_tok_bytes: usize,
6116 v_tok_bytes: usize,
6117 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
6118 let ranks = self.ranks.len();
6119 if tabs.len() != ranks {
6120 return Err("fa rows join needs one table per rank".into());
6121 }
6122 {
6123 let mut guard = self
6124 .decode_v2
6125 .lock()
6126 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6127 let ws = guard
6128 .get_mut(ws_index)
6129 .ok_or("step TP decode v2 workspace index out of range")?;
6130 if ws.fa2_cap < t || ws.fa2_q.len() != ranks {
6131 return Err("fa rows join without stashed rows".into());
6132 }
6133 let lq = ws.local_q_dim;
6134 let local_heads = (ws.heads / ranks).max(1);
6135 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
6136 if ws.tcol_ocap < t || ws.tcol_gated.len() != ranks {
6137 ws.tcol_gated.clear();
6138 ws.tcol_opart.clear();
6139 for engine in &self.ranks {
6140 let _m = engine.gpu.enter_main()?;
6141 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6142 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6143 }
6144 let root = &self.ranks[0];
6145 let _m = root.gpu.enter_main()?;
6146 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6147 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6148 ws.tcol_ocap = 32;
6149 }
6150 for rank in 0..ranks {
6151 let engine = &self.ranks[rank];
6152 let _main = engine.gpu.enter_main()?;
6153 {
6154 let StepTpDecodeV2Ws {
6155 fa2_q,
6156 fa2_gate,
6157 fa2_gated,
6158 ..
6159 } = &mut *ws;
6160 engine.fa_decode_dcw_rows(
6161 &fa2_q[rank],
6162 tabs[rank],
6163 &mut fa2_gated[rank],
6164 t,
6165 head_dim,
6166 local_heads,
6167 local_kv_heads,
6168 window,
6169 max_ns,
6170 scale,
6171 k_tok_bytes,
6172 v_tok_bytes,
6173 &fa2_gate[rank],
6174 )?;
6175 }
6176 let StepTpDecodeV2Ws {
6177 fa2_gated,
6178 tcol_gated,
6179 ..
6180 } = &mut *ws;
6181 let mut dst = tcol_gated[rank].slice_mut(0..t * lq);
6182 engine
6183 .stream()
6184 .memcpy_dtod(&fa2_gated[rank].slice(0..t * lq), &mut dst)?;
6185 }
6186 }
6187 self.decode_v2_oproj_tcol(ws_index, e, o_m, t)
6188 }
6189
6190 pub(crate) fn decode_v2_stash_gated(
6195 &self,
6196 ws: &mut StepTpDecodeV2Ws,
6197 e: &Engine,
6198 col: usize,
6199 ) -> Result<(), Box<dyn std::error::Error>> {
6200 let ranks = self.ranks.len();
6201 if col >= 8 {
6202 return Err("decode_v2_stash_gated column out of range".into());
6203 }
6204 let lq = ws.local_q_dim;
6205 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
6206 ws.tcol_gated.clear();
6207 ws.tcol_opart.clear();
6208 for engine in &self.ranks {
6209 let _m = engine.gpu.enter_main()?;
6210 ws.tcol_gated.push(engine.uninit(32 * lq)?);
6211 ws.tcol_opart.push(engine.uninit(32 * ws.o_out)?);
6212 }
6213 let root = &self.ranks[0];
6214 let _m = root.gpu.enter_main()?;
6215 ws.tcol_opeer = Some(root.uninit(32 * ws.o_out)?);
6216 ws.tcol_omix = Some(root.uninit(32 * ws.o_out)?);
6217 ws.tcol_ocap = 32;
6218 }
6219 for rank in 0..ranks {
6220 let engine = &self.ranks[rank];
6221 let _main = engine.gpu.enter_main()?;
6222 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
6223 engine
6224 .stream()
6225 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
6226 ws.ev_rank[rank].record(&engine.stream())?;
6230 }
6231 {
6232 let _main = e.gpu.enter_main()?;
6233 for ev in ws.ev_rank.iter() {
6234 e.stream().wait(ev)?;
6235 }
6236 }
6237 Ok(())
6238 }
6239
6240 pub(crate) fn decode_v2_oproj_tcol(
6246 &self,
6247 ws_index: usize,
6248 e: &Engine,
6249 o_m: &ResidentStepBf16RowParallel,
6250 t: usize,
6251 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6252 let ranks = self.ranks.len();
6253 let mut guard = self
6254 .decode_v2
6255 .lock()
6256 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
6257 let ws = guard
6258 .get_mut(ws_index)
6259 .ok_or("step TP decode v2 workspace index out of range")?;
6260 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 32 || ws.tcol_ocap < t {
6261 return Err("decode_v2_oproj_tcol geometry".into());
6262 }
6263 for rank in 0..ranks {
6264 let engine = &self.ranks[rank];
6265 let _main = engine.gpu.enter_main()?;
6266 let mut weights = Vec::with_capacity(4);
6267 for block in 0..4 {
6268 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6269 return Err("tcol o_proj requires bf16-resident O blocks".into());
6270 };
6271 weights.push(weight);
6272 }
6273 {
6274 let StepTpDecodeV2Ws {
6275 tcol_gated,
6276 tcol_opart,
6277 local_q_dim,
6278 o_block_cols,
6279 o_out,
6280 ..
6281 } = &mut *ws;
6282 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6285 let refk = *REFK
6286 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
6287 if refk {
6288 let lq = *local_q_dim;
6289 let mut xr = engine.uninit(lq)?;
6290 let mut yr = engine.uninit(*o_out)?;
6291 for c in 0..t {
6292 {
6293 let mut dst = xr.slice_mut(0..lq);
6294 engine.stream().memcpy_dtod(
6295 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
6296 &mut dst,
6297 )?;
6298 }
6299 engine.matvec_bf16_b4_into(
6300 [weights[0], weights[1], weights[2], weights[3]],
6301 &xr,
6302 &mut yr,
6303 *o_block_cols,
6304 *o_out,
6305 )?;
6306 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
6307 engine
6308 .stream()
6309 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
6310 }
6311 } else {
6312 engine.matvec_bf16_b4_tcol_into(
6313 [weights[0], weights[1], weights[2], weights[3]],
6314 &tcol_gated[rank],
6315 &mut tcol_opart[rank],
6316 *o_block_cols,
6317 *o_out,
6318 t,
6319 )?;
6320 }
6321 }
6322 if rank != 0 {
6323 ws.ev_rank[rank].record(&engine.stream())?;
6324 }
6325 }
6326 let root = &self.ranks[0];
6327 {
6328 let _main = root.gpu.enter_main()?;
6329 for ev in ws.ev_rank.iter().skip(1) {
6330 root.stream().wait(ev)?;
6331 }
6332 {
6333 let StepTpDecodeV2Ws {
6334 tcol_opart,
6335 tcol_opeer,
6336 tcol_omix,
6337 o_out,
6338 ..
6339 } = &mut *ws;
6340 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6341 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6342 {
6343 let mut dst = opeer.slice_mut(0..t * *o_out);
6344 root.stream()
6345 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6346 }
6347 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6350 }
6351 ws.ev_oproj.record(&root.stream())?;
6352 }
6353 let _main = e.gpu.enter_main()?;
6354 e.stream().wait(&ws.ev_oproj)?;
6355 let mut out = e.uninit(t * ws.o_out)?;
6356 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6357 e.stream().memcpy_dtod(
6358 &omix.slice(0..t * ws.o_out),
6359 &mut out.slice_mut(0..t * ws.o_out),
6360 )?;
6361 Ok(out)
6362 }
6363
6364 pub(crate) fn decode_v2_input_qkv(
6365 &self,
6366 ws: &mut StepTpDecodeV2Ws,
6367 e: &Engine,
6368 h: &CudaSlice<f32>,
6369 pos_d: &CudaSlice<i32>,
6370 gate_raw: Option<&CudaSlice<f32>>,
6371 gate_shards: Option<StepTpGateShards<'_>>,
6372 decode_input: &mut ResidentReplicatedDeviceRows,
6373 q_m: &ResidentBf16ColumnParallel,
6374 k_m: &ResidentBf16ColumnParallel,
6375 v_m: &ResidentBf16ColumnParallel,
6376 q_norm: &[CudaSlice<f32>],
6377 k_norm: &[CudaSlice<f32>],
6378 head_dim: usize,
6379 n_rot: usize,
6380 rope_base: f32,
6381 rope_freqs: &[Option<&CudaSlice<f32>>],
6382 rms_eps: f32,
6383 defer_norm_rope: bool,
6384 tcol_col: Option<usize>,
6385 ) -> Result<(), Box<dyn std::error::Error>> {
6386 let ranks = self.ranks.len();
6387 validate_replicated_device_rows(&self.ranks, decode_input)?;
6388 if decode_input.tokens != 1
6389 || decode_input.width != q_m.in_features
6390 || pos_d.len() != 1
6391 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6392 || gate_raw.is_none() != gate_shards.is_some()
6393 || gate_shards.as_ref().is_some_and(|shards| match shards {
6394 StepTpGateShards::F32(shards) => shards.len() != ranks,
6395 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6396 })
6397 || q_norm.len() != ranks
6398 || k_norm.len() != ranks
6399 || rope_freqs.len() != ranks
6400 || e.ctx().ordinal() != ws.e_device
6401 {
6402 return Err("step TP decode v2 input geometry mismatch".into());
6403 }
6404
6405 let qkv_fused = step_tp_qkv_fused_enabled()?;
6406 if gate_shards.is_some() && !qkv_fused {
6407 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6408 }
6409 let values = decode_input.width;
6410 if h.len() != values {
6411 return Err(format!(
6412 "step TP decode v2 hidden width {} != replicated width {values}",
6413 h.len()
6414 )
6415 .into());
6416 }
6417
6418 if qkv_fused {
6419 if ws.h_stage.is_none() {
6423 use cudarc::driver::DevicePtr;
6424 let _main = e.gpu.enter_main()?;
6425 let h_stage = e.uninit(values)?;
6426 let pos_stage = e.htod_i32(&[0])?;
6427 {
6428 let stream = e.stream();
6429 let (hp, _g0) = h_stage.device_ptr(&stream);
6430 let (pp, _g1) = pos_stage.device_ptr(&stream);
6431 ws.raw_h_stage = hp as u64;
6432 ws.raw_pos_stage = pp as u64;
6433 }
6434 ws.h_stage = Some(h_stage);
6435 ws.pos_stage = Some(pos_stage);
6436 for rank in 0..ranks {
6437 use cudarc::driver::DevicePtr;
6438 let engine = &self.ranks[rank];
6439 let _rmain = engine.gpu.enter_main()?;
6440 let attn_in = engine.uninit(values)?;
6441 let (dp, pp) = {
6442 let stream = engine.stream();
6443 let (dp, _g2) = attn_in.device_ptr(&stream);
6444 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6445 (dp as u64, pp as u64)
6446 };
6447 ws.raw_attn_in.push(dp);
6448 ws.raw_pos.push(pp);
6449 ws.attn_in.push(attn_in);
6450 }
6451 {
6452 use cudarc::driver::DevicePtr;
6453 let root = &self.ranks[0];
6454 let _rmain = root.gpu.enter_main()?;
6455 let stream = root.stream();
6456 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6457 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6458 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6459 ws.raw_peer_partial = a as u64;
6460 ws.raw_k_shadow = b as u64;
6461 ws.raw_v_shadow = c as u64;
6462 }
6463 {
6464 use cudarc::driver::DevicePtr;
6465 let rank1 = &self.ranks[1];
6466 let _rmain = rank1.gpu.enter_main()?;
6467 let stream = rank1.stream();
6468 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6469 let (b, _g) = ws.k[1].device_ptr(&stream);
6470 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6471 ws.raw_o_partial1 = a as u64;
6472 ws.raw_k1 = b as u64;
6473 ws.raw_v1 = c as u64;
6474 }
6475 }
6476 {
6477 let _main = e.gpu.enter_main()?;
6478 {
6479 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6482 let mut dst = h_stage.slice_mut(0..values);
6483 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6484 }
6485 {
6486 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6487 let mut dst = pos_stage.slice_mut(0..1);
6488 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6489 }
6490 ws.ev_entry.record(&e.stream())?;
6491 }
6492 for rank in 0..ranks {
6493 let engine = &self.ranks[rank];
6494 let _main = engine.gpu.enter_main()?;
6495 engine.stream().wait(&ws.ev_entry)?;
6496 }
6497 } else {
6498 {
6500 let _main = e.gpu.enter_main()?;
6501 if let Some(gate_raw) = gate_raw {
6502 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6503 e.stream()
6504 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6505 }
6506 ws.ev_entry.record(&e.stream())?;
6507 }
6508 {
6509 let root = &self.ranks[0];
6510 let _main = root.gpu.enter_main()?;
6511 root.stream().wait(&ws.ev_entry)?;
6512 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6513 root.stream()
6514 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6515 ws.ev_refresh.record(&root.stream())?;
6516 }
6517 for rank in 1..ranks {
6518 let engine = &self.ranks[rank];
6519 let _main = engine.gpu.enter_main()?;
6520 engine.stream().wait(&ws.ev_refresh)?;
6521 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6522 let mut destination = peer_rows[0].slice_mut(0..values);
6523 engine
6524 .stream()
6525 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6526 }
6527 }
6528 for rank in 0..ranks {
6529 self.decode_v2_input_qkv_rank(
6530 ws,
6531 pos_d,
6532 decode_input,
6533 q_m,
6534 k_m,
6535 v_m,
6536 q_norm,
6537 k_norm,
6538 head_dim,
6539 n_rot,
6540 rope_base,
6541 rope_freqs,
6542 rms_eps,
6543 gate_shards.as_ref(),
6544 qkv_fused,
6545 defer_norm_rope,
6546 rank,
6547 tcol_col,
6548 )?;
6549 }
6550 Ok(())
6551 }
6552
6553 #[allow(clippy::too_many_arguments)]
6556 pub(crate) fn decode_v2_input_qkv_rank(
6557 &self,
6558 ws: &mut StepTpDecodeV2Ws,
6559 pos_d: &CudaSlice<i32>,
6560 decode_input: &mut ResidentReplicatedDeviceRows,
6561 q_m: &ResidentBf16ColumnParallel,
6562 k_m: &ResidentBf16ColumnParallel,
6563 v_m: &ResidentBf16ColumnParallel,
6564 q_norm: &[CudaSlice<f32>],
6565 k_norm: &[CudaSlice<f32>],
6566 head_dim: usize,
6567 n_rot: usize,
6568 rope_base: f32,
6569 rope_freqs: &[Option<&CudaSlice<f32>>],
6570 rms_eps: f32,
6571 gate_shards: Option<&StepTpGateShards<'_>>,
6572 qkv_fused: bool,
6573 defer_norm_rope: bool,
6574 rank: usize,
6575 tcol_col: Option<usize>,
6576 ) -> Result<(), Box<dyn std::error::Error>> {
6577 let ranks = self.ranks.len();
6578 let local_heads = ws.local_q_dim / head_dim;
6579 let local_kv_heads = ws.local_kv_dim / head_dim;
6580 let engine = &self.ranks[rank];
6581 let _main = engine.gpu.enter_main()?;
6582 let ws_e_device = ws.e_device;
6583 if qkv_fused && tcol_col.is_some() {
6588 let c = tcol_col.expect("checked");
6589 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6590 return Err("tcol select without precompute".into());
6591 }
6592 if engine.ctx().ordinal() != ws_e_device {
6596 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6597 }
6598 let StepTpDecodeV2Ws {
6599 tcol_q,
6600 tcol_k,
6601 tcol_v,
6602 tcol_g,
6603 q_raw,
6604 k_raw,
6605 v_raw,
6606 gate,
6607 local_q_dim,
6608 local_kv_dim,
6609 heads,
6610 ..
6611 } = &mut *ws;
6612 let lg = *heads / ranks;
6613 let stream = engine.stream();
6614 {
6615 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6616 stream.memcpy_dtod(
6617 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6618 &mut dst,
6619 )?;
6620 }
6621 {
6622 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6623 stream.memcpy_dtod(
6624 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6625 &mut dst,
6626 )?;
6627 }
6628 {
6629 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6630 stream.memcpy_dtod(
6631 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6632 &mut dst,
6633 )?;
6634 }
6635 if lg > 0 {
6636 let mut dst = gate[rank].slice_mut(0..lg);
6637 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6638 }
6639 if !defer_norm_rope {
6640 } else {
6644 return Ok(());
6645 }
6646 }
6647 if qkv_fused {
6648 let same_dev = engine.ctx().ordinal() == ws.e_device;
6653 if !same_dev {
6654 raw_copy_bytes(
6655 ws.raw_attn_in[rank],
6656 ws.raw_h_stage,
6657 q_m.in_features * 4,
6658 engine,
6659 )?;
6660 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6661 }
6662 let StepTpDecodeV2Ws {
6663 q_raw,
6664 k_raw,
6665 v_raw,
6666 gate,
6667 gate_e,
6668 attn_in,
6669 h_stage,
6670 heads,
6671 local_q_dim,
6672 local_kv_dim,
6673 w8_aq,
6674 w8_ad,
6675 w8_in,
6676 ..
6677 } = &mut *ws;
6678 let input_ref: &CudaSlice<f32> = if same_dev {
6679 h_stage
6680 .as_ref()
6681 .ok_or("step TP decode v2 stage not armed")?
6682 } else {
6683 &attn_in[rank]
6684 };
6685 match (
6686 &q_m.ranks[rank].weight,
6687 &k_m.ranks[rank].weight,
6688 &v_m.ranks[rank].weight,
6689 ) {
6690 (
6691 ResidentBf16Weight::F32(wq),
6692 ResidentBf16Weight::F32(wk),
6693 ResidentBf16Weight::F32(wv),
6694 ) => {
6695 let (wg, out_g) = match &gate_shards {
6696 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6697 Some(StepTpGateShards::Bf16(_)) => {
6698 return Err("step TP decode v2 gate shard class does not \
6699 match the F32 projections"
6700 .into());
6701 }
6702 None => (&*gate_e, 0),
6704 };
6705 engine.matvec_f32_qkv_into(
6706 wq,
6707 wk,
6708 wv,
6709 wg,
6710 input_ref,
6711 &mut q_raw[rank],
6712 &mut k_raw[rank],
6713 &mut v_raw[rank],
6714 &mut gate[rank],
6715 q_m.in_features,
6716 *local_q_dim,
6717 *local_kv_dim,
6718 out_g,
6719 )?;
6720 }
6721 (
6722 ResidentBf16Weight::Bf16(wq),
6723 ResidentBf16Weight::Bf16(wk),
6724 ResidentBf16Weight::Bf16(wv),
6725 ) => {
6726 let (wg, out_g) = match &gate_shards {
6727 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6728 Some(StepTpGateShards::F32(_)) => {
6729 return Err("step TP decode v2 gate shard class does not \
6730 match the bf16 projections"
6731 .into());
6732 }
6733 None => (wq, 0),
6734 };
6735 let in_f = q_m.in_features;
6742 let q8_ready = crate::step_tp_w8_on()
6743 && q_m.ranks[rank].q8.is_some()
6744 && k_m.ranks[rank].q8.is_some()
6745 && v_m.ranks[rank].q8.is_some();
6746 if q8_ready {
6747 if *w8_in != in_f || w8_aq.len() != ranks {
6748 w8_aq.clear();
6749 w8_ad.clear();
6750 for e_rank in &self.ranks {
6751 let _m = e_rank.gpu.enter_main()?;
6752 w8_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
6753 w8_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
6754 }
6755 *w8_in = in_f;
6756 }
6757 engine.quantize_q8_1_into(
6758 input_ref,
6759 1,
6760 in_f,
6761 &mut w8_aq[rank],
6762 &mut w8_ad[rank],
6763 )?;
6764 engine.qmatvec_q8_0_qkv_rp_into(
6769 q_m.ranks[rank].q8.as_ref().unwrap(),
6770 k_m.ranks[rank].q8.as_ref().unwrap(),
6771 v_m.ranks[rank].q8.as_ref().unwrap(),
6772 &w8_aq[rank],
6773 &w8_ad[rank],
6774 &mut q_raw[rank],
6775 &mut k_raw[rank],
6776 &mut v_raw[rank],
6777 in_f,
6778 *local_q_dim,
6779 *local_kv_dim,
6780 )?;
6781 if out_g > 0 {
6782 engine.matvec_bf16_into(wg, input_ref, &mut gate[rank], in_f, out_g)?;
6783 }
6784 } else {
6785 engine.matvec_bf16_qkvg_into(
6786 wq,
6787 wk,
6788 wv,
6789 wg,
6790 input_ref,
6791 &mut q_raw[rank],
6792 &mut k_raw[rank],
6793 &mut v_raw[rank],
6794 &mut gate[rank],
6795 q_m.in_features,
6796 *local_q_dim,
6797 *local_kv_dim,
6798 out_g,
6799 )?;
6800 }
6801 }
6802 _ => {
6803 return Err("step TP decode v2 QKV projections mix residency classes".into());
6804 }
6805 }
6806 } else {
6807 for (matrix, local_out, raw) in [
6808 (q_m, ws.local_q_dim, &mut ws.q_raw),
6809 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6810 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6811 ] {
6812 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6813 return Err("step TP decode v2 lost its F32 projection residency".into());
6814 };
6815 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6816 engine.linear_f32_resident_canonical_rows_t1_into(
6817 &decode_input.ranks[rank],
6818 values_w,
6819 &mut raw[rank],
6820 matrix.in_features,
6821 local_out,
6822 chunk_rows,
6823 )?;
6824 }
6825 }
6826 if qkv_fused && defer_norm_rope {
6827 } else if qkv_fused {
6829 let StepTpDecodeV2Ws {
6832 q_raw,
6833 k_raw,
6834 q,
6835 k,
6836 pos,
6837 pos_stage,
6838 ..
6839 } = &mut *ws;
6840 let same_dev = engine.ctx().ordinal() == ws_e_device;
6841 let pos_ref: &CudaSlice<i32> = if same_dev {
6842 pos_stage
6843 .as_ref()
6844 .ok_or("step TP decode v2 pos stage not armed")?
6845 } else {
6846 &pos[rank]
6847 };
6848 engine.qk_norm_rope_into(
6849 &q_raw[rank],
6850 &k_raw[rank],
6851 &q_norm[rank],
6852 &k_norm[rank],
6853 &mut q[rank],
6854 &mut k[rank],
6855 pos_ref,
6856 head_dim,
6857 n_rot,
6858 local_heads,
6859 local_kv_heads,
6860 rms_eps,
6861 rope_base,
6862 1.0,
6863 rope_freqs[rank],
6864 )?;
6865 } else {
6866 engine.rms_norm(
6867 &ws.q_raw[rank],
6868 &q_norm[rank],
6869 &mut ws.q[rank],
6870 head_dim,
6871 local_heads,
6872 rms_eps,
6873 )?;
6874 engine.rms_norm(
6875 &ws.k_raw[rank],
6876 &k_norm[rank],
6877 &mut ws.k[rank],
6878 head_dim,
6879 local_kv_heads,
6880 rms_eps,
6881 )?;
6882 {
6883 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6884 engine
6885 .stream()
6886 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6887 }
6888 engine.rope_neox2(
6889 &mut ws.q[rank],
6890 &mut ws.k[rank],
6891 &ws.pos[rank],
6892 head_dim,
6893 n_rot,
6894 local_heads,
6895 local_kv_heads,
6896 1,
6897 rope_base,
6898 1.0,
6899 rope_freqs[rank],
6900 )?;
6901 }
6902 if gate_shards.is_none() {
6903 let gate_start = rank * (ws.heads / ranks);
6904 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6905 engine.stream().memcpy_dtod(
6906 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6907 &mut gate_dst,
6908 )?;
6909 }
6910 Ok(())
6911 }
6912
6913 pub(crate) fn decode_v2_finish_rank_partial(
6917 &self,
6918 ws: &mut StepTpDecodeV2Ws,
6919 o_m: &ResidentStepBf16RowParallel,
6920 o_fused: bool,
6921 rank: usize,
6922 ) -> Result<(), Box<dyn std::error::Error>> {
6923 let engine = &self.ranks[rank];
6924 let _main = engine.gpu.enter_main()?;
6925 if o_fused {
6926 let StepTpDecodeV2Ws {
6927 gated,
6928 o_partials,
6929 o_block_cols,
6930 o_out,
6931 w8o_aq,
6932 w8o_ad,
6933 w8o_in,
6934 ..
6935 } = &mut *ws;
6936 let all_f32 = o_m.ranks[rank]
6937 .iter()
6938 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6939 if all_f32 {
6940 let mut weights = Vec::with_capacity(4);
6941 for block in 0..4 {
6942 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6943 unreachable!("all_f32 checked above");
6944 };
6945 weights.push(weight);
6946 }
6947 engine.matvec_f32_b4_into(
6948 [weights[0], weights[1], weights[2], weights[3]],
6949 &gated[rank],
6950 &mut o_partials[rank][0],
6951 *o_block_cols,
6952 *o_out,
6953 )?;
6954 } else if crate::step_tp_w8_on() && (0..4).all(|b| o_m.ranks[rank][b].q8.is_some()) {
6955 let in_f = 4 * *o_block_cols;
6960 if *w8o_in != in_f || w8o_aq.len() != self.ranks.len() {
6961 w8o_aq.clear();
6962 w8o_ad.clear();
6963 for e_rank in &self.ranks {
6964 let _m = e_rank.gpu.enter_main()?;
6965 w8o_aq.push(e_rank.alloc_uninit::<i8>(in_f)?);
6966 w8o_ad.push(e_rank.alloc_uninit::<f32>(in_f / 32)?);
6967 }
6968 *w8o_in = in_f;
6969 }
6970 engine.quantize_q8_1_into(
6971 &gated[rank],
6972 1,
6973 in_f,
6974 &mut w8o_aq[rank],
6975 &mut w8o_ad[rank],
6976 )?;
6977 engine.qmatvec_q8_0_b4_rp_into(
6978 [
6979 o_m.ranks[rank][0].q8.as_ref().unwrap(),
6980 o_m.ranks[rank][1].q8.as_ref().unwrap(),
6981 o_m.ranks[rank][2].q8.as_ref().unwrap(),
6982 o_m.ranks[rank][3].q8.as_ref().unwrap(),
6983 ],
6984 &w8o_aq[rank],
6985 &w8o_ad[rank],
6986 &mut o_partials[rank][0],
6987 *o_block_cols,
6988 *o_out,
6989 )?;
6990 } else {
6991 let mut weights = Vec::with_capacity(4);
6992 for block in 0..4 {
6993 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6994 return Err("step TP decode v2 O projections mix residency classes".into());
6995 };
6996 weights.push(weight);
6997 }
6998 engine.matvec_bf16_b4_into(
6999 [weights[0], weights[1], weights[2], weights[3]],
7000 &gated[rank],
7001 &mut o_partials[rank][0],
7002 *o_block_cols,
7003 *o_out,
7004 )?;
7005 }
7006 } else {
7007 for block in 0..ws.blocks_per_rank {
7008 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
7009 return Err("step TP decode v2 lost its F32 O residency".into());
7010 };
7011 let x =
7012 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
7013 let w = weight.slice(0..weight.len());
7014 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
7015 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
7016 }
7017 }
7018 Ok(())
7019 }
7020
7021 pub(crate) fn decode_v2_finish(
7029 &self,
7030 ws: &mut StepTpDecodeV2Ws,
7031 e: &Engine,
7032 o_m: &ResidentStepBf16RowParallel,
7033 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7034 let ranks = self.ranks.len();
7035 if e.ctx().ordinal() != ws.e_device {
7036 return Err("step TP decode v2 finish engine changed".into());
7037 }
7038 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
7043
7044 for rank in 0..ranks {
7047 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
7048 if rank == 0 {
7049 continue;
7052 }
7053 let engine = &self.ranks[rank];
7054 let _main = engine.gpu.enter_main()?;
7055 ws.ev_rank[rank].record(&engine.stream())?;
7056 }
7057
7058 let root = &self.ranks[0];
7060 #[allow(unused_assignments)]
7061 let mut final_in_a = false;
7062 {
7063 let _main = root.gpu.enter_main()?;
7064 for ev in ws.ev_rank.iter().skip(1) {
7065 root.stream().wait(ev)?;
7066 }
7067 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
7068 ws.ev_oproj.record(&root.stream())?;
7074 let _main = e.gpu.enter_main()?;
7075 e.stream().wait(&ws.ev_oproj)?;
7076 let mut output = e.uninit(ws.o_out)?;
7077 if oproj_tail_on() && oproj_tail_eligible() {
7078 use cudarc::driver::DevicePtr;
7081 let stream = e.stream();
7082 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
7083 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
7084 set_oproj_tail((p0 as u64, p1 as u64));
7085 return Ok(output);
7086 }
7087 e.add(
7088 &ws.o_partials[0][0],
7089 &ws.o_partials[1][0],
7090 &mut output,
7091 ws.o_out,
7092 )?;
7093 return Ok(output);
7094 }
7095 if o_fused {
7096 self.decode_v2_finish_root_fused(ws)?;
7097 ws.ev_oproj.record(&root.stream())?;
7098 let _main = e.gpu.enter_main()?;
7099 e.stream().wait(&ws.ev_oproj)?;
7100 let mut output = e.uninit(ws.o_out)?;
7101 e.stream().memcpy_dtod(
7102 &ws.reduce_a.slice(0..ws.o_out),
7103 &mut output.slice_mut(0..ws.o_out),
7104 )?;
7105 return Ok(output);
7106 }
7107 let mut first = true;
7108 let mut current_is_a = false;
7109 for rank in 0..ranks {
7110 for block in 0..ws.blocks_per_rank {
7111 let use_peer = rank != 0;
7112 if use_peer {
7113 root.stream()
7114 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
7115 }
7116 match (first, current_is_a, use_peer) {
7118 (true, _, true) => {
7119 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7120 }
7121 (true, _, false) => root.add(
7122 &ws.zeros,
7123 &ws.o_partials[0][block],
7124 &mut ws.reduce_a,
7125 ws.o_out,
7126 )?,
7127 (false, true, true) => {
7128 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
7129 }
7130 (false, true, false) => root.add(
7131 &ws.reduce_a,
7132 &ws.o_partials[0][block],
7133 &mut ws.reduce_b,
7134 ws.o_out,
7135 )?,
7136 (false, false, true) => {
7137 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
7138 }
7139 (false, false, false) => root.add(
7140 &ws.reduce_b,
7141 &ws.o_partials[0][block],
7142 &mut ws.reduce_a,
7143 ws.o_out,
7144 )?,
7145 }
7146 current_is_a = first || !current_is_a;
7147 first = false;
7148 }
7149 }
7150 final_in_a = current_is_a;
7151
7152 for rank in 0..ranks {
7153 let start = rank * ws.local_kv_dim;
7154 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
7155 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
7156 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
7157 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
7158 }
7159 ws.ev_oproj.record(&root.stream())?;
7160 }
7161
7162 let _main = e.gpu.enter_main()?;
7166 e.stream().wait(&ws.ev_oproj)?;
7167 let mut output = e.uninit(ws.o_out)?;
7168 let source = if final_in_a {
7169 &ws.reduce_a
7170 } else {
7171 &ws.reduce_b
7172 };
7173 e.stream().memcpy_dtod(
7174 &source.slice(0..ws.o_out),
7175 &mut output.slice_mut(0..ws.o_out),
7176 )?;
7177 Ok(output)
7178 }
7179
7180 pub fn run_routed_experts(
7181 &self,
7182 experts: &ResidentExpertParallel,
7183 input: &[f32],
7184 tokens: usize,
7185 selected: &[usize],
7186 route_weights: &[f32],
7187 experts_per_token: usize,
7188 activation_limit: Option<f32>,
7189 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7190 validate_step_expert_activation_limit(activation_limit)?;
7191 validate_ep_residency(&self.ranks, experts)?;
7192 validate_activations(input, tokens, experts.input_width)?;
7193 let pairs = tokens
7194 .checked_mul(experts_per_token)
7195 .ok_or("EP route count overflow")?;
7196 if selected.len() != pairs || route_weights.len() != pairs {
7197 return Err(format!(
7198 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
7199 {experts_per_token} ({pairs})",
7200 selected.len(),
7201 route_weights.len(),
7202 )
7203 .into());
7204 }
7205 if !route_weights.iter().all(|weight| weight.is_finite()) {
7206 return Err("EP route weights contain a non-finite value".into());
7207 }
7208 if self.native_p2p {
7209 return self.run_routed_experts_native(
7210 experts,
7211 input,
7212 tokens,
7213 selected,
7214 route_weights,
7215 experts_per_token,
7216 activation_limit,
7217 );
7218 }
7219
7220 let mut output = vec![0.0f32; tokens * experts.input_width];
7221 let per_rank = experts.expert_count / experts.ranks.len();
7222 for token in 0..tokens {
7223 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7224 for slot in 0..experts_per_token {
7225 let pair = token * experts_per_token + slot;
7226 let expert = selected[pair];
7227 if expert >= experts.expert_count {
7228 return Err(format!(
7229 "EP selected expert {expert} outside 0..{}",
7230 experts.expert_count
7231 )
7232 .into());
7233 }
7234 let owner = expert / per_rank;
7235 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7236 let rank = &experts.ranks[owner];
7237 let engine = &self.ranks[owner];
7238 let gate =
7239 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
7240 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
7241 let activated: Vec<f32> = gate
7242 .iter()
7243 .zip(&up)
7244 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7245 .collect();
7246 debug_assert_eq!(activated.len(), experts.expert_width);
7247 let down =
7248 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
7249 let weight = route_weights[pair];
7250 for (sum, value) in output
7251 [token * experts.input_width..(token + 1) * experts.input_width]
7252 .iter_mut()
7253 .zip(down)
7254 {
7255 *sum += weight * value;
7256 }
7257 }
7258 }
7259 Ok(output)
7260 }
7261
7262 fn run_routed_experts_native(
7263 &self,
7264 experts: &ResidentExpertParallel,
7265 input: &[f32],
7266 tokens: usize,
7267 selected: &[usize],
7268 route_weights: &[f32],
7269 experts_per_token: usize,
7270 activation_limit: Option<f32>,
7271 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7272 if !self.native_p2p || self.ranks.len() < 2 {
7273 return Err("native EP execution requires at least two P2P ranks".into());
7274 }
7275 if self.ep_device_arithmetic {
7276 return self.run_routed_experts_native_device(
7277 experts,
7278 input,
7279 tokens,
7280 selected,
7281 route_weights,
7282 experts_per_token,
7283 activation_limit,
7284 );
7285 }
7286 let mut output = vec![0.0f32; tokens * experts.input_width];
7287 let per_rank = experts.expert_count / experts.ranks.len();
7288 for token in 0..tokens {
7289 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7290 let mut rank_inputs = (0..self.ranks.len())
7291 .map(|_| None)
7292 .collect::<Vec<Option<CudaSlice<f32>>>>();
7293 rank_inputs[0] = Some({
7294 let root = &self.ranks[0];
7295 let _main = root.gpu.enter_main()?;
7296 root.htod(input_row)?
7297 });
7298
7299 for slot in 0..experts_per_token {
7300 let pair = token * experts_per_token + slot;
7301 let expert = selected[pair];
7302 if expert >= experts.expert_count {
7303 return Err(format!(
7304 "EP selected expert {expert} outside 0..{}",
7305 experts.expert_count
7306 )
7307 .into());
7308 }
7309 let owner = expert / per_rank;
7310 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7311 if rank_inputs[owner].is_none() {
7312 let peer_input = {
7313 let root_input = rank_inputs[0]
7314 .as_ref()
7315 .ok_or("native EP lost its root input")?;
7316 let engine = &self.ranks[owner];
7317 let _main = engine.gpu.enter_main()?;
7318 let mut peer_input = engine.uninit(experts.input_width)?;
7319 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7320 peer_input
7321 };
7322 rank_inputs[owner] = Some(peer_input);
7323 }
7324
7325 let rank = &experts.ranks[owner];
7326 let engine = &self.ranks[owner];
7327 let owner_input = rank_inputs[owner]
7328 .as_ref()
7329 .ok_or("native EP owner input is absent after dispatch")?;
7330 let gate = run_resident_bank_expert_device(
7331 engine,
7332 &rank.gate,
7333 local_expert,
7334 owner_input,
7335 1,
7336 )?;
7337 let up = run_resident_bank_expert_device(
7338 engine,
7339 &rank.up,
7340 local_expert,
7341 owner_input,
7342 1,
7343 )?;
7344 let (gate, up) = {
7345 let _main = engine.gpu.enter_main()?;
7346 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
7347 };
7348 let activated = gate
7349 .iter()
7350 .zip(&up)
7351 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
7352 .collect::<Vec<_>>();
7353 debug_assert_eq!(activated.len(), experts.expert_width);
7354 let activated = {
7355 let _main = engine.gpu.enter_main()?;
7356 engine.htod(&activated)?
7357 };
7358 let down = run_resident_bank_expert_device(
7359 engine,
7360 &rank.down,
7361 local_expert,
7362 &activated,
7363 1,
7364 )?;
7365 let down = if owner == 0 {
7366 let _main = engine.gpu.enter_main()?;
7367 engine.dtoh(&down)?
7368 } else {
7369 let root = &self.ranks[0];
7370 let _main = root.gpu.enter_main()?;
7371 let mut root_down = root.uninit(experts.input_width)?;
7372 root.stream().memcpy_dtod(&down, &mut root_down)?;
7373 root.dtoh(&root_down)?
7374 };
7375 let weight = route_weights[pair];
7376 for (sum, value) in output
7377 [token * experts.input_width..(token + 1) * experts.input_width]
7378 .iter_mut()
7379 .zip(down)
7380 {
7381 *sum += weight * value;
7382 }
7383 }
7384 }
7385 Ok(output)
7386 }
7387
7388 fn run_routed_experts_native_device(
7389 &self,
7390 experts: &ResidentExpertParallel,
7391 input: &[f32],
7392 tokens: usize,
7393 selected: &[usize],
7394 route_weights: &[f32],
7395 experts_per_token: usize,
7396 activation_limit: Option<f32>,
7397 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7398 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
7399 return Err(
7400 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
7401 );
7402 }
7403 let mut output = Vec::with_capacity(tokens * experts.input_width);
7404 let per_rank = experts.expert_count / experts.ranks.len();
7405 let root = &self.ranks[0];
7406 for token in 0..tokens {
7407 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
7408 let mut rank_inputs = (0..self.ranks.len())
7409 .map(|_| None)
7410 .collect::<Vec<Option<CudaSlice<f32>>>>();
7411 rank_inputs[0] = Some({
7412 let _main = root.gpu.enter_main()?;
7413 root.htod(input_row)?
7414 });
7415 let mut root_output = {
7416 let _main = root.gpu.enter_main()?;
7417 root.zeros(experts.input_width)?
7418 };
7419 let mut remote_down_keepalive = Vec::new();
7420
7421 for slot in 0..experts_per_token {
7422 let pair = token * experts_per_token + slot;
7423 let expert = selected[pair];
7424 if expert >= experts.expert_count {
7425 return Err(format!(
7426 "EP selected expert {expert} outside 0..{}",
7427 experts.expert_count
7428 )
7429 .into());
7430 }
7431 let owner = expert / per_rank;
7432 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7433 if rank_inputs[owner].is_none() {
7434 let peer_input = {
7435 let root_input = rank_inputs[0]
7436 .as_ref()
7437 .ok_or("native EP lost its root input")?;
7438 let engine = &self.ranks[owner];
7439 let _main = engine.gpu.enter_main()?;
7440 let mut peer_input = engine.uninit(experts.input_width)?;
7441 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7442 peer_input
7443 };
7444 rank_inputs[owner] = Some(peer_input);
7445 }
7446
7447 let rank = &experts.ranks[owner];
7448 let engine = &self.ranks[owner];
7449 let owner_input = rank_inputs[owner]
7450 .as_ref()
7451 .ok_or("native EP owner input is absent after dispatch")?;
7452 let gate = run_resident_bank_expert_device(
7453 engine,
7454 &rank.gate,
7455 local_expert,
7456 owner_input,
7457 1,
7458 )?;
7459 let up = run_resident_bank_expert_device(
7460 engine,
7461 &rank.up,
7462 local_expert,
7463 owner_input,
7464 1,
7465 )?;
7466 let activated = {
7467 let _main = engine.gpu.enter_main()?;
7468 let mut activated = engine.uninit(experts.expert_width)?;
7469 if let Some(limit) = activation_limit {
7470 engine.silu_clamped_mul_host_expf(
7471 &gate,
7472 &up,
7473 limit,
7474 &mut activated,
7475 experts.expert_width,
7476 )?;
7477 } else {
7478 engine.silu_mul_host_expf(
7479 &gate,
7480 &up,
7481 &mut activated,
7482 experts.expert_width,
7483 )?;
7484 }
7485 activated
7486 };
7487 let down = run_resident_bank_expert_device(
7488 engine,
7489 &rank.down,
7490 local_expert,
7491 &activated,
7492 1,
7493 )?;
7494 let root_down = if owner == 0 {
7495 down
7496 } else {
7497 let _main = root.gpu.enter_main()?;
7498 let mut root_down = root.uninit(experts.input_width)?;
7499 root.stream().memcpy_dtod(&down, &mut root_down)?;
7500 remote_down_keepalive.push(down);
7504 root_down
7505 };
7506 let _main = root.gpu.enter_main()?;
7507 let mut destination = root_output.slice_mut(0..experts.input_width);
7508 root.axpy_host_into(
7509 &root_down.slice(0..root_down.len()),
7510 route_weights[pair],
7511 &mut destination,
7512 experts.input_width,
7513 )?;
7514 }
7515
7516 let _main = root.gpu.enter_main()?;
7517 let root_output = root.dtoh(&root_output)?;
7518 drop(remote_down_keepalive);
7519 output.extend(root_output);
7520 }
7521 Ok(output)
7522 }
7523}
7524
7525fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7526 if matrix.out_features % tp != 0 {
7527 return Err(format!(
7528 "column-parallel out_features {} is not divisible by TP={tp}",
7529 matrix.out_features
7530 ));
7531 }
7532 let local_out = matrix.out_features / tp;
7533 if local_out % FP8_BLOCK != 0 {
7534 return Err(format!(
7535 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7536 E4M3 scale block"
7537 ));
7538 }
7539 Ok(())
7540}
7541
7542fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7543 if !matches!(tp, 1 | 2 | 4 | 8) {
7544 return Err(format!(
7545 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7546 ));
7547 }
7548 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7549 return Err(format!(
7550 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7551 ));
7552 }
7553 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7554 let local_out = out_features / tp;
7555 if local_out % canonical_rows != 0 {
7556 return Err(format!(
7557 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7558 {canonical_rows}-row chunks"
7559 ));
7560 }
7561 Ok(canonical_rows)
7562}
7563
7564fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7565 if !matches!(tp, 1 | 2 | 4 | 8) {
7566 return Err(format!(
7567 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7568 ));
7569 }
7570 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7571 return Err(format!(
7572 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7573 ));
7574 }
7575 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7576 let local_in = in_features / tp;
7577 if local_in % canonical_cols != 0 {
7578 return Err(format!(
7579 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7580 {canonical_cols}-column chunks"
7581 ));
7582 }
7583 Ok(canonical_cols)
7584}
7585
7586fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7587 if matrix.in_features % tp != 0 {
7588 return Err(format!(
7589 "row-parallel in_features {} is not divisible by TP={tp}",
7590 matrix.in_features
7591 ));
7592 }
7593 let local_in = matrix.in_features / tp;
7594 if local_in % FP8_BLOCK != 0 {
7595 return Err(format!(
7596 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7597 E4M3 scale block"
7598 ));
7599 }
7600 Ok(())
7601}
7602
7603fn upload_rank(
7604 engine: &Engine,
7605 matrix: E4m3BlockMatrix<'_>,
7606) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7607 let _main = engine.gpu.enter_main()?;
7608 matrix.validate()?;
7609 Ok(ResidentE4m3Rank {
7610 codes: engine.htod_bytes(matrix.codes)?,
7611 scales: engine.htod(matrix.scales)?,
7612 out_features: matrix.out_features,
7613 in_features: matrix.in_features,
7614 })
7615}
7616
7617fn upload_bf16_rank(
7618 engine: &Engine,
7619 matrix: Bf16Matrix<'_>,
7620 f32_mirror: bool,
7621) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7622 let _main = engine.gpu.enter_main()?;
7623 matrix.validate()?;
7624 let bytes = engine.htod_bytes(matrix.bytes)?;
7625 let weight = if f32_mirror {
7626 let values = matrix
7627 .out_features
7628 .checked_mul(matrix.in_features)
7629 .ok_or("resident BF16 mirror element count overflow")?;
7630 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7631 } else {
7632 ResidentBf16Weight::Bf16(bytes)
7633 };
7634 let q8 = if crate::step_tp_w8_on() && matrix.in_features % 32 == 0 {
7638 if let ResidentBf16Weight::Bf16(bytes) = &weight {
7639 let row_bytes = Engine::q8_0_row_bytes(matrix.in_features);
7646 let mut interleaved = engine.alloc_u8_uninit(matrix.out_features * row_bytes)?;
7647 engine.encode_q8_0_from_bf16(
7648 bytes,
7649 &mut interleaved,
7650 matrix.in_features,
7651 matrix.out_features,
7652 )?;
7653 let mirror =
7654 engine.build_q8_rp4_raw(&interleaved, matrix.in_features, matrix.out_features)?;
7655 Some(mirror)
7656 } else {
7657 None
7658 }
7659 } else {
7660 None
7661 };
7662 Ok(ResidentBf16Rank {
7663 weight,
7664 out_features: matrix.out_features,
7665 in_features: matrix.in_features,
7666 q8,
7667 })
7668}
7669
7670fn upload_expert_bank_rank(
7671 engine: &Engine,
7672 bank: E4m3ExpertBank<'_>,
7673 expert_range: Range<usize>,
7674) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7675 let _main = engine.gpu.enter_main()?;
7676 bank.validate()?;
7677 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7678 return Err(format!(
7679 "invalid EP expert range {expert_range:?} for {} experts",
7680 bank.expert_count
7681 )
7682 .into());
7683 }
7684 let code_stride = bank.out_features * bank.in_features;
7685 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7686 Ok(ResidentE4m3ExpertBankRank {
7687 codes: engine.htod_bytes(
7688 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7689 )?,
7690 scales: engine.htod(
7691 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7692 )?,
7693 expert_range,
7694 out_features: bank.out_features,
7695 in_features: bank.in_features,
7696 code_stride,
7697 scale_stride,
7698 k_blocks: None,
7699 })
7700}
7701
7702fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7703 if bank.out_features % tp != 0 {
7704 return Err(format!(
7705 "TP expert output width {} is not divisible by TP={tp}",
7706 bank.out_features
7707 ));
7708 }
7709 let local_out = bank.out_features / tp;
7710 if local_out % FP8_BLOCK != 0 {
7711 return Err(format!(
7712 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7713 ));
7714 }
7715 Ok(())
7716}
7717
7718fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7719 if bank.in_features % tp != 0 {
7720 return Err(format!(
7721 "TP expert input width {} is not divisible by TP={tp}",
7722 bank.in_features
7723 ));
7724 }
7725 let local_in = bank.in_features / tp;
7726 if local_in % FP8_BLOCK != 0 {
7727 return Err(format!(
7728 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7729 ));
7730 }
7731 Ok(())
7732}
7733
7734fn upload_column_bank_rank(
7735 engine: &Engine,
7736 bank: E4m3ExpertBank<'_>,
7737 tp: usize,
7738 rank: usize,
7739) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7740 let _main = engine.gpu.enter_main()?;
7741 let packed = pack_column_bank_rank(bank, tp, rank)?;
7742 Ok(ResidentE4m3ExpertBankRank {
7743 codes: engine.htod_bytes(&packed.codes)?,
7744 scales: engine.htod(&packed.scales)?,
7745 expert_range: packed.expert_range,
7746 out_features: packed.out_features,
7747 in_features: packed.in_features,
7748 code_stride: packed.code_stride,
7749 scale_stride: packed.scale_stride,
7750 k_blocks: packed.k_blocks,
7751 })
7752}
7753
7754fn pack_column_bank_rank(
7755 bank: E4m3ExpertBank<'_>,
7756 tp: usize,
7757 rank: usize,
7758) -> Result<PackedE4m3ExpertBankRank, String> {
7759 bank.validate()?;
7760 validate_column_bank_shape(bank, tp)?;
7761 if rank >= tp {
7762 return Err(format!("TP rank {rank} outside 0..{tp}"));
7763 }
7764 let local_out = bank.out_features / tp;
7765 let full_code_stride = bank.out_features * bank.in_features;
7766 let local_code_stride = local_out * bank.in_features;
7767 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7768 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7769 let local_scale_rows = local_out / FP8_BLOCK;
7770 let local_scale_stride = local_scale_rows * scale_cols;
7771 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7772 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7773 let row_start = rank * local_out;
7774 let scale_row_start = rank * local_scale_rows;
7775 for expert in 0..bank.expert_count {
7776 let code_start = expert * full_code_stride + row_start * bank.in_features;
7777 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7778 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7779 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7780 }
7781 Ok(PackedE4m3ExpertBankRank {
7782 codes,
7783 scales,
7784 expert_range: 0..bank.expert_count,
7785 out_features: local_out,
7786 in_features: bank.in_features,
7787 code_stride: local_code_stride,
7788 scale_stride: local_scale_stride,
7789 k_blocks: None,
7790 })
7791}
7792
7793fn upload_row_bank_rank(
7794 engine: &Engine,
7795 bank: E4m3ExpertBank<'_>,
7796 tp: usize,
7797 rank: usize,
7798) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7799 let _main = engine.gpu.enter_main()?;
7800 let packed = pack_row_bank_rank(bank, tp, rank)?;
7801 Ok(ResidentE4m3ExpertBankRank {
7802 codes: engine.htod_bytes(&packed.codes)?,
7803 scales: engine.htod(&packed.scales)?,
7804 expert_range: packed.expert_range,
7805 out_features: packed.out_features,
7806 in_features: packed.in_features,
7807 code_stride: packed.code_stride,
7808 scale_stride: packed.scale_stride,
7809 k_blocks: packed.k_blocks,
7810 })
7811}
7812
7813fn pack_row_bank_rank(
7814 bank: E4m3ExpertBank<'_>,
7815 tp: usize,
7816 rank: usize,
7817) -> Result<PackedE4m3ExpertBankRank, String> {
7818 bank.validate()?;
7819 validate_row_bank_shape(bank, tp)?;
7820 if rank >= tp {
7821 return Err(format!("TP rank {rank} outside 0..{tp}"));
7822 }
7823 let local_in = bank.in_features / tp;
7824 let full_code_stride = bank.out_features * bank.in_features;
7825 let local_code_stride = bank.out_features * local_in;
7826 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7827 let local_scale_cols = local_in / FP8_BLOCK;
7828 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7829 let full_scale_stride = scale_rows * full_scale_cols;
7830 let local_scale_stride = scale_rows * local_scale_cols;
7831 let global_block_start = rank * local_scale_cols;
7832 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7833 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7834 for expert in 0..bank.expert_count {
7835 let expert_code_start = expert * full_code_stride;
7836 let expert_scale_start = expert * full_scale_stride;
7837 for local_block in 0..local_scale_cols {
7838 let global_block = global_block_start + local_block;
7839 let column_start = global_block * FP8_BLOCK;
7840 for row in 0..bank.out_features {
7841 let start = expert_code_start + row * bank.in_features + column_start;
7842 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7843 }
7844 for row in 0..scale_rows {
7845 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7846 }
7847 }
7848 }
7849 Ok(PackedE4m3ExpertBankRank {
7850 codes,
7851 scales,
7852 expert_range: 0..bank.expert_count,
7853 out_features: bank.out_features,
7854 in_features: local_in,
7855 code_stride: local_code_stride,
7856 scale_stride: local_scale_stride,
7857 k_blocks: Some(local_scale_cols),
7858 })
7859}
7860
7861fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7862 if engines.len() != ranks.len() {
7863 return Err(format!(
7864 "resident TP rank count {} != runtime rank count {}",
7865 ranks.len(),
7866 engines.len()
7867 ));
7868 }
7869 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7870 let device = engine.ctx().ordinal();
7871 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7872 return Err(format!(
7873 "resident TP rank {rank} is not owned by runtime device {device}"
7874 ));
7875 }
7876 }
7877 Ok(())
7878}
7879
7880fn validate_tp_bank_residency(
7881 engines: &[Engine],
7882 experts: &ResidentTpExpertBank,
7883) -> Result<(), String> {
7884 if engines.len() != experts.gate.len()
7885 || engines.len() != experts.up.len()
7886 || engines.len() != experts.down.len()
7887 {
7888 return Err(format!(
7889 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7890 experts.gate.len(),
7891 experts.up.len(),
7892 experts.down.len(),
7893 engines.len()
7894 ));
7895 }
7896 for (rank, engine) in engines.iter().enumerate() {
7897 let device = engine.ctx().ordinal();
7898 for (projection, bank) in [
7899 ("gate", &experts.gate[rank]),
7900 ("up", &experts.up[rank]),
7901 ("down", &experts.down[rank]),
7902 ] {
7903 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7904 return Err(format!(
7905 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7906 {device}"
7907 ));
7908 }
7909 }
7910 }
7911 Ok(())
7912}
7913
7914fn validate_ep_residency(
7915 engines: &[Engine],
7916 experts: &ResidentExpertParallel,
7917) -> Result<(), String> {
7918 if engines.len() != experts.ranks.len() {
7919 return Err(format!(
7920 "resident EP rank count {} != runtime rank count {}",
7921 experts.ranks.len(),
7922 engines.len()
7923 ));
7924 }
7925 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7926 let device = engine.ctx().ordinal();
7927 for (projection, bank) in [
7928 ("gate", &resident.gate),
7929 ("up", &resident.up),
7930 ("down", &resident.down),
7931 ] {
7932 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7933 return Err(format!(
7934 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7935 {device}"
7936 ));
7937 }
7938 }
7939 }
7940 Ok(())
7941}
7942
7943fn run_rank(
7944 engine: &Engine,
7945 matrix: E4m3BlockMatrix<'_>,
7946 activations: &[f32],
7947 tokens: usize,
7948) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7949 let _main = engine.gpu.enter_main()?;
7950 let codes = engine.htod_bytes(matrix.codes)?;
7951 let scales = engine.htod(matrix.scales)?;
7952 let activations = engine.htod(activations)?;
7953 let output = engine.qmatvec_mmq_fp8_blk(
7954 &codes,
7955 &scales,
7956 &activations,
7957 tokens,
7958 matrix.in_features,
7959 matrix.out_features,
7960 )?;
7961 engine.dtoh(&output)
7962}
7963
7964fn run_resident_rank(
7965 engine: &Engine,
7966 matrix: &ResidentE4m3Rank,
7967 activations: &[f32],
7968 tokens: usize,
7969) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7970 let _main = engine.gpu.enter_main()?;
7971 let activations = engine.htod(activations)?;
7972 let output = engine.qmatvec_mmq_fp8_blk(
7973 &matrix.codes,
7974 &matrix.scales,
7975 &activations,
7976 tokens,
7977 matrix.in_features,
7978 matrix.out_features,
7979 )?;
7980 engine.dtoh(&output)
7981}
7982
7983fn run_resident_bf16_rank(
7984 engine: &Engine,
7985 matrix: &ResidentBf16Rank,
7986 activations: &[f32],
7987 tokens: usize,
7988 canonical_chunk_rows: Option<usize>,
7989) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7990 let _main = engine.gpu.enter_main()?;
7991 let activations = engine.htod(activations)?;
7992 let output = run_resident_bf16_rank_device(
7993 engine,
7994 matrix,
7995 &activations,
7996 tokens,
7997 canonical_chunk_rows,
7998 false,
7999 )?;
8000 engine.dtoh(&output)
8001}
8002
8003fn run_resident_bf16_rank_device(
8004 engine: &Engine,
8005 matrix: &ResidentBf16Rank,
8006 activations: &CudaSlice<f32>,
8007 tokens: usize,
8008 canonical_chunk_rows: Option<usize>,
8009 strided_chunk_output: bool,
8010) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8011 let _main = engine.gpu.enter_main()?;
8012 if activations.ordinal() != engine.ctx().ordinal() {
8013 return Err(format!(
8014 "resident BF16 activation device {} != rank device {}",
8015 activations.ordinal(),
8016 engine.ctx().ordinal()
8017 )
8018 .into());
8019 }
8020 if activations.len() != tokens * matrix.in_features {
8021 return Err(format!(
8022 "resident BF16 activation count {} != {tokens}x{}",
8023 activations.len(),
8024 matrix.in_features
8025 )
8026 .into());
8027 }
8028 match (&matrix.weight, canonical_chunk_rows) {
8029 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
8030 .linear_bf16_resident_canonical_rows(
8031 activations,
8032 bytes,
8033 tokens,
8034 matrix.in_features,
8035 matrix.out_features,
8036 rows,
8037 ),
8038 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
8039 activations,
8040 bytes,
8041 tokens,
8042 matrix.in_features,
8043 matrix.out_features,
8044 ),
8045 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
8046 .linear_f32_resident_canonical_rows_strided(
8047 activations,
8048 values,
8049 tokens,
8050 matrix.in_features,
8051 matrix.out_features,
8052 rows,
8053 ),
8054 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
8055 activations,
8056 values,
8057 tokens,
8058 matrix.in_features,
8059 matrix.out_features,
8060 rows,
8061 ),
8062 (ResidentBf16Weight::F32(values), None) => engine.linear(
8063 activations,
8064 values,
8065 tokens,
8066 matrix.in_features,
8067 matrix.out_features,
8068 ),
8069 }
8070}
8071
8072fn validate_resident_bf16_ranks(
8073 engines: &[Engine],
8074 ranks: &[ResidentBf16Rank],
8075) -> Result<(), String> {
8076 if engines.len() != ranks.len() {
8077 return Err(format!(
8078 "resident BF16 TP rank count {} != runtime rank count {}",
8079 ranks.len(),
8080 engines.len(),
8081 ));
8082 }
8083 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
8084 let device = engine.ctx().ordinal();
8085 if matrix.weight.ordinal() != device {
8086 return Err(format!(
8087 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
8088 ));
8089 }
8090 }
8091 Ok(())
8092}
8093
8094fn validate_step_bf16_row_residency(
8095 engines: &[Engine],
8096 matrix: &ResidentStepBf16RowParallel,
8097) -> Result<(), String> {
8098 if engines.len() != matrix.ranks.len() {
8099 return Err(format!(
8100 "resident Step BF16 row rank count {} != runtime rank count {}",
8101 matrix.ranks.len(),
8102 engines.len(),
8103 ));
8104 }
8105 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
8106 if matrix.canonical_chunk_cols != canonical_cols {
8107 return Err(format!(
8108 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
8109 matrix.canonical_chunk_cols
8110 ));
8111 }
8112 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
8113 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
8114 if blocks.len() != blocks_per_rank {
8115 return Err(format!(
8116 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
8117 blocks.len()
8118 ));
8119 }
8120 let device = engine.ctx().ordinal();
8121 for (block, resident) in blocks.iter().enumerate() {
8122 if resident.weight.ordinal() != device
8123 || resident.in_features != canonical_cols
8124 || resident.out_features != matrix.out_features
8125 {
8126 return Err(format!(
8127 "resident Step BF16 row rank {rank} block {block} has inconsistent \
8128 device or geometry"
8129 ));
8130 }
8131 }
8132 }
8133 Ok(())
8134}
8135
8136fn validate_replicated_device_rows(
8137 engines: &[Engine],
8138 rows: &ResidentReplicatedDeviceRows,
8139) -> Result<(), String> {
8140 let rank_lengths = rows
8141 .ranks
8142 .iter()
8143 .map(|rank_rows| rank_rows.len())
8144 .collect::<Vec<_>>();
8145 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
8146 if rows
8147 .ranks
8148 .iter()
8149 .zip(engines)
8150 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
8151 {
8152 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
8153 }
8154 Ok(())
8155}
8156
8157fn replicated_device_row_values(
8158 tokens: usize,
8159 width: usize,
8160 expected_ranks: usize,
8161 rank_lengths: &[usize],
8162) -> Result<usize, String> {
8163 let values = tokens
8164 .checked_mul(width)
8165 .ok_or("replicated device row size overflow")?;
8166 if tokens == 0
8167 || width == 0
8168 || expected_ranks == 0
8169 || rank_lengths.len() != expected_ranks
8170 || rank_lengths.iter().any(|&rank_len| rank_len != values)
8171 {
8172 return Err(format!(
8173 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
8174 tokens,
8175 width,
8176 rank_lengths.len(),
8177 expected_ranks
8178 ));
8179 }
8180 Ok(values)
8181}
8182
8183fn replicated_device_row_source_values(
8184 tokens: usize,
8185 width: usize,
8186 source_len: usize,
8187 source_device: usize,
8188 root_device: usize,
8189) -> Result<usize, String> {
8190 let values = tokens
8191 .checked_mul(width)
8192 .ok_or("replicated device row size overflow")?;
8193 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
8194 return Err(format!(
8195 "replicated device row source has inconsistent geometry/device \
8196 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
8197 ));
8198 }
8199 Ok(values)
8200}
8201
8202fn bf16_column_shard(
8203 matrix: Bf16Matrix<'_>,
8204 tp: usize,
8205 rank: usize,
8206) -> Result<Bf16Matrix<'_>, String> {
8207 matrix.validate()?;
8208 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
8209 return Err(format!(
8210 "invalid BF16 column shard out={} TP={tp} rank={rank}",
8211 matrix.out_features
8212 ));
8213 }
8214 let local_out = matrix.out_features / tp;
8215 let row_bytes = matrix.in_features * 2;
8216 let start = rank * local_out * row_bytes;
8217 Ok(Bf16Matrix {
8218 bytes: &matrix.bytes[start..start + local_out * row_bytes],
8219 out_features: local_out,
8220 in_features: matrix.in_features,
8221 })
8222}
8223
8224fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
8225 matrix.validate()?;
8226 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
8227 return Err(format!(
8228 "invalid BF16 row shard in={} TP={tp} rank={rank}",
8229 matrix.in_features
8230 ));
8231 }
8232 let local_in = matrix.in_features / tp;
8233 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
8234 for row in 0..matrix.out_features {
8235 let start = (row * matrix.in_features + rank * local_in) * 2;
8236 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
8237 }
8238 Ok(bytes)
8239}
8240
8241fn bf16_row_block(
8242 matrix: Bf16Matrix<'_>,
8243 col_start: usize,
8244 block_cols: usize,
8245) -> Result<Vec<u8>, String> {
8246 matrix.validate()?;
8247 let col_end = col_start
8248 .checked_add(block_cols)
8249 .ok_or("BF16 row block column overflow")?;
8250 if block_cols == 0 || col_end > matrix.in_features {
8251 return Err(format!(
8252 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
8253 matrix.in_features
8254 ));
8255 }
8256 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
8257 for row in 0..matrix.out_features {
8258 let start = (row * matrix.in_features + col_start) * 2;
8259 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
8260 }
8261 Ok(bytes)
8262}
8263
8264fn run_resident_bank_expert(
8265 engine: &Engine,
8266 bank: &ResidentE4m3ExpertBankRank,
8267 local_expert: usize,
8268 activations: &[f32],
8269 tokens: usize,
8270) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8271 let _main = engine.gpu.enter_main()?;
8272 if bank.k_blocks.is_some() {
8273 return Err("block-major TP row bank requires canonical block execution".into());
8274 }
8275 let local_count = bank.expert_range.end - bank.expert_range.start;
8276 if local_expert >= local_count {
8277 return Err(format!(
8278 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
8279 bank.expert_range
8280 )
8281 .into());
8282 }
8283 validate_activations(activations, tokens, bank.in_features)?;
8284 let activations = engine.htod(activations)?;
8285 let weight = bank
8286 .codes
8287 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8288 let scales = bank
8289 .scales
8290 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8291 let input = activations.slice(0..activations.len());
8292 let output = engine.qmatvec_mmq_fp8_blk_view(
8293 &weight,
8294 &scales,
8295 &input,
8296 tokens,
8297 bank.in_features,
8298 bank.out_features,
8299 )?;
8300 engine.dtoh(&output)
8301}
8302
8303fn run_resident_bank_expert_block(
8304 engine: &Engine,
8305 bank: &ResidentE4m3ExpertBankRank,
8306 local_expert: usize,
8307 block: usize,
8308 activations: &[f32],
8309) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8310 let _main = engine.gpu.enter_main()?;
8311 let local_count = bank.expert_range.end - bank.expert_range.start;
8312 if local_expert >= local_count {
8313 return Err(format!(
8314 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8315 bank.expert_range
8316 )
8317 .into());
8318 }
8319 let blocks = bank
8320 .k_blocks
8321 .ok_or("TP row bank is not packed in native K-block order")?;
8322 if block >= blocks {
8323 return Err(format!("TP row block {block} outside 0..{blocks}").into());
8324 }
8325 validate_activations(activations, 1, FP8_BLOCK)?;
8326 let block_code_stride = bank.out_features * FP8_BLOCK;
8327 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8328 if bank.in_features != blocks * FP8_BLOCK
8329 || bank.code_stride != blocks * block_code_stride
8330 || bank.scale_stride != blocks * block_scale_stride
8331 {
8332 return Err("TP row bank block-major geometry is inconsistent".into());
8333 }
8334
8335 let expert_code_start = local_expert * bank.code_stride;
8336 let expert_scale_start = local_expert * bank.scale_stride;
8337 let weight = bank.codes.slice(
8338 expert_code_start + block * block_code_stride
8339 ..expert_code_start + (block + 1) * block_code_stride,
8340 );
8341 let scales = bank.scales.slice(
8342 expert_scale_start + block * block_scale_stride
8343 ..expert_scale_start + (block + 1) * block_scale_stride,
8344 );
8345 let activations = engine.htod(activations)?;
8346 let input = activations.slice(0..activations.len());
8347 let output = engine.qmatvec_mmq_fp8_blk_view(
8348 &weight,
8349 &scales,
8350 &input,
8351 1,
8352 FP8_BLOCK,
8353 bank.out_features,
8354 )?;
8355 engine.dtoh(&output)
8356}
8357
8358fn run_resident_bank_expert_device(
8359 engine: &Engine,
8360 bank: &ResidentE4m3ExpertBankRank,
8361 local_expert: usize,
8362 activations: &CudaSlice<f32>,
8363 tokens: usize,
8364) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8365 let _main = engine.gpu.enter_main()?;
8366 if bank.k_blocks.is_some() {
8367 return Err("block-major TP row bank requires canonical block execution".into());
8368 }
8369 let local_count = bank.expert_range.end - bank.expert_range.start;
8370 if local_expert >= local_count {
8371 return Err(format!(
8372 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8373 bank.expert_range
8374 )
8375 .into());
8376 }
8377 let expected = tokens
8378 .checked_mul(bank.in_features)
8379 .ok_or("native TP activation size overflow")?;
8380 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
8381 return Err(format!(
8382 "native TP activation len/device {}/{} != expected {expected}/{}",
8383 activations.len(),
8384 activations.ordinal(),
8385 engine.ctx().ordinal()
8386 )
8387 .into());
8388 }
8389 let weight = bank
8390 .codes
8391 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
8392 let scales = bank
8393 .scales
8394 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
8395 let input = activations.slice(0..activations.len());
8396 engine.qmatvec_mmq_fp8_blk_view(
8397 &weight,
8398 &scales,
8399 &input,
8400 tokens,
8401 bank.in_features,
8402 bank.out_features,
8403 )
8404}
8405
8406fn run_resident_bank_expert_block_device(
8407 engine: &Engine,
8408 bank: &ResidentE4m3ExpertBankRank,
8409 local_expert: usize,
8410 block: usize,
8411 activations: &cudarc::driver::CudaView<'_, f32>,
8412) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8413 let _main = engine.gpu.enter_main()?;
8414 let local_count = bank.expert_range.end - bank.expert_range.start;
8415 if local_expert >= local_count {
8416 return Err(format!(
8417 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
8418 bank.expert_range
8419 )
8420 .into());
8421 }
8422 let blocks = bank
8423 .k_blocks
8424 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
8425 if block >= blocks {
8426 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
8427 }
8428 let activation_device = activations.stream().context().ordinal();
8429 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
8430 return Err(format!(
8431 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
8432 activations.len(),
8433 activation_device,
8434 engine.ctx().ordinal()
8435 )
8436 .into());
8437 }
8438 let block_code_stride = bank.out_features * FP8_BLOCK;
8439 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
8440 if bank.in_features != blocks * FP8_BLOCK
8441 || bank.code_stride != blocks * block_code_stride
8442 || bank.scale_stride != blocks * block_scale_stride
8443 {
8444 return Err("native TP row bank block-major geometry is inconsistent".into());
8445 }
8446 let expert_code_start = local_expert * bank.code_stride;
8447 let expert_scale_start = local_expert * bank.scale_stride;
8448 let weight = bank.codes.slice(
8449 expert_code_start + block * block_code_stride
8450 ..expert_code_start + (block + 1) * block_code_stride,
8451 );
8452 let scales = bank.scales.slice(
8453 expert_scale_start + block * block_scale_stride
8454 ..expert_scale_start + (block + 1) * block_scale_stride,
8455 );
8456 engine.qmatvec_mmq_fp8_blk_view(
8457 &weight,
8458 &scales,
8459 activations,
8460 1,
8461 FP8_BLOCK,
8462 bank.out_features,
8463 )
8464}
8465
8466fn configure_native_p2p(
8467 ranks: &[Engine],
8468 devices: &[usize],
8469) -> Result<(), Box<dyn std::error::Error>> {
8470 if ranks.len() != devices.len() || ranks.len() < 2 {
8471 return Err("native TP P2P setup requires matching multi-rank devices".into());
8472 }
8473 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8474 if engine.ctx().ordinal() != device {
8475 return Err(format!(
8476 "native TP rank {rank} context device {} != requested device {device}",
8477 engine.ctx().ordinal()
8478 )
8479 .into());
8480 }
8481 }
8482
8483 for src in 0..ranks.len() {
8484 for dst in 0..ranks.len() {
8485 if src == dst {
8486 continue;
8487 }
8488 let mut can_access = 0;
8489 unsafe {
8490 cudarc::driver::sys::cuDeviceCanAccessPeer(
8491 &mut can_access,
8492 ranks[src].ctx().cu_device(),
8493 ranks[dst].ctx().cu_device(),
8494 )
8495 .result()?;
8496 }
8497 if can_access == 0 {
8498 return Err(format!(
8499 "native TP requires P2P, but dev{} cannot access dev{}",
8500 devices[src], devices[dst]
8501 )
8502 .into());
8503 }
8504 ranks[src].ctx().bind_to_thread()?;
8505 let rc =
8506 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8507 use cudarc::driver::sys::cudaError_enum as E;
8508 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8509 return Err(format!(
8510 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8511 devices[src], devices[dst]
8512 )
8513 .into());
8514 }
8515 }
8516 }
8517
8518 for &owner in devices {
8519 for &accessor in devices {
8520 if owner == accessor {
8521 continue;
8522 }
8523 let device = cudarc::driver::result::device::get(owner as i32)?;
8524 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8525 unsafe {
8526 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8527 }
8528 let desc = cudarc::driver::sys::CUmemAccessDesc {
8529 location: cudarc::driver::sys::CUmemLocation {
8530 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8531 id: accessor as i32,
8532 },
8533 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8534 };
8535 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8536 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8537 return Err(format!(
8538 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8539 {rc:?}"
8540 )
8541 .into());
8542 }
8543 }
8544 }
8545
8546 for src in 0..ranks.len() {
8547 for dst in 0..ranks.len() {
8548 if src == dst {
8549 continue;
8550 }
8551 for &words in NATIVE_P2P_PROBE_WORDS {
8552 let expected = (0..words)
8553 .map(|index| {
8554 (index as u32)
8555 .wrapping_mul(0x9e37_79b9)
8556 .wrapping_add(((src as u32) << 16) | dst as u32)
8557 })
8558 .collect::<Vec<_>>();
8559 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8560 let source = ranks[src].htod_u32_v(&expected)?;
8561 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8562 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8563 let actual = ranks[dst].dtoh_u32(&destination)?;
8564 if actual != expected {
8565 let mismatches = actual
8566 .iter()
8567 .zip(&expected)
8568 .filter(|(actual, expected)| actual != expected)
8569 .count();
8570 return Err(format!(
8571 "native TP peer probe dev{}->dev{} failed at {} bytes: \
8572 {mismatches}/{} words differ",
8573 devices[src],
8574 devices[dst],
8575 words * std::mem::size_of::<u32>(),
8576 expected.len()
8577 )
8578 .into());
8579 }
8580 }
8581 }
8582 }
8583 ranks[0].ctx().bind_to_thread()?;
8584 eprintln!(
8585 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8586 directions={} byte_ladder={:?} mismatches=0",
8587 ranks.len() * (ranks.len() - 1),
8588 NATIVE_P2P_PROBE_WORDS
8589 .iter()
8590 .map(|words| words * std::mem::size_of::<u32>())
8591 .collect::<Vec<_>>(),
8592 );
8593 Ok(())
8594}
8595
8596fn validate_activations(
8597 activations: &[f32],
8598 tokens: usize,
8599 in_features: usize,
8600) -> Result<(), String> {
8601 let expected = tokens
8602 .checked_mul(in_features)
8603 .ok_or_else(|| "activation size overflow".to_string())?;
8604 if activations.len() != expected {
8605 return Err(format!(
8606 "activation count {} != {tokens}x{in_features} ({expected})",
8607 activations.len()
8608 ));
8609 }
8610 if !activations.iter().all(|value| value.is_finite()) {
8611 return Err("activations contain a non-finite value".to_string());
8612 }
8613 Ok(())
8614}
8615
8616fn column_shard(
8617 matrix: E4m3BlockMatrix<'_>,
8618 tp: usize,
8619 rank: usize,
8620) -> Result<E4m3BlockMatrix<'_>, String> {
8621 let local_out = matrix.out_features / tp;
8622 let row_start = rank * local_out;
8623 let code_start = row_start * matrix.in_features;
8624 let code_end = code_start + local_out * matrix.in_features;
8625 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8626 let local_scale_rows = local_out / FP8_BLOCK;
8627 let scale_start = rank * local_scale_rows * scale_cols;
8628 let scale_end = scale_start + local_scale_rows * scale_cols;
8629 Ok(E4m3BlockMatrix {
8630 codes: &matrix.codes[code_start..code_end],
8631 scales: &matrix.scales[scale_start..scale_end],
8632 out_features: local_out,
8633 in_features: matrix.in_features,
8634 })
8635}
8636
8637fn row_shard(
8638 matrix: E4m3BlockMatrix<'_>,
8639 tp: usize,
8640 rank: usize,
8641) -> Result<(Vec<u8>, Vec<f32>), String> {
8642 let local_in = matrix.in_features / tp;
8643 let col_start = rank * local_in;
8644 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8645 for row in 0..matrix.out_features {
8646 let start = row * matrix.in_features + col_start;
8647 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8648 }
8649
8650 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8651 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8652 let local_scale_cols = local_in / FP8_BLOCK;
8653 let scale_col_start = rank * local_scale_cols;
8654 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8655 for row in 0..scale_rows {
8656 let start = row * scale_cols + scale_col_start;
8657 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8658 }
8659 Ok((codes, scales))
8660}
8661
8662fn activation_shard(
8663 activations: &[f32],
8664 tokens: usize,
8665 in_features: usize,
8666 tp: usize,
8667 rank: usize,
8668) -> Vec<f32> {
8669 let local_in = in_features / tp;
8670 let col_start = rank * local_in;
8671 let mut shard = Vec::with_capacity(tokens * local_in);
8672 for token in 0..tokens {
8673 let start = token * in_features + col_start;
8674 shard.extend_from_slice(&activations[start..start + local_in]);
8675 }
8676 shard
8677}
8678
8679#[derive(Clone, Copy)]
8699pub struct Nvfp4BlockMatrix<'a> {
8700 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8704 pub in_features: usize,
8705}
8706
8707impl Nvfp4BlockMatrix<'_> {
8708 pub fn validate(&self) -> Result<(), String> {
8709 if self.in_features == 0 || self.out_features == 0 {
8710 return Err("NVFP4 matrix has a zero dimension".to_string());
8711 }
8712 if self.in_features % 64 != 0 {
8713 return Err(format!(
8714 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8715 self.in_features
8716 ));
8717 }
8718 if self.codes.len() != self.out_features * self.in_features / 2 {
8719 return Err(format!(
8720 "NVFP4 code bytes {} != {}x{}/2",
8721 self.codes.len(),
8722 self.out_features,
8723 self.in_features
8724 ));
8725 }
8726 if self.scales.len() != self.out_features * self.in_features / 16 {
8727 return Err(format!(
8728 "NVFP4 scale bytes {} != {}x{}/16",
8729 self.scales.len(),
8730 self.out_features,
8731 self.in_features
8732 ));
8733 }
8734 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8735 return Err(format!(
8736 "NVFP4 macro scale {} is not finite-positive",
8737 self.macro_scale
8738 ));
8739 }
8740 Ok(())
8741 }
8742}
8743
8744#[derive(Clone, Copy)]
8746pub struct Nvfp4ExpertBank<'a> {
8747 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8751 pub out_features: usize,
8752 pub in_features: usize,
8753}
8754
8755impl Nvfp4ExpertBank<'_> {
8756 pub fn validate(&self) -> Result<(), String> {
8757 if self.expert_count == 0 {
8758 return Err("NVFP4 expert bank is empty".to_string());
8759 }
8760 if self.macros.len() != self.expert_count {
8761 return Err(format!(
8762 "NVFP4 bank macros {} != expert count {}",
8763 self.macros.len(),
8764 self.expert_count
8765 ));
8766 }
8767 self.expert(0).map(|_| ())
8768 }
8769
8770 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8771 if expert >= self.expert_count {
8772 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8773 }
8774 let code_stride = self.out_features * self.in_features / 2;
8775 let scale_stride = self.out_features * self.in_features / 16;
8776 if self.codes.len() != self.expert_count * code_stride
8777 || self.scales.len() != self.expert_count * scale_stride
8778 {
8779 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8780 }
8781 let matrix = Nvfp4BlockMatrix {
8782 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8783 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8784 macro_scale: self.macros[expert],
8785 out_features: self.out_features,
8786 in_features: self.in_features,
8787 };
8788 matrix.validate()?;
8789 Ok(matrix)
8790 }
8791}
8792
8793pub struct ResidentNvfp4Rank {
8795 blocks: crate::CudaSlice<u8>,
8796 macro_scale: f32,
8797 out_features: usize,
8798 in_features: usize,
8799 row_bytes: usize,
8800}
8801
8802pub struct ResidentNvfp4ColumnParallel {
8803 ranks: Vec<ResidentNvfp4Rank>,
8804 pub out_features: usize,
8805 pub in_features: usize,
8806}
8807
8808pub struct ResidentNvfp4RowParallel {
8809 ranks: Vec<ResidentNvfp4Rank>,
8810 pub out_features: usize,
8811 pub in_features: usize,
8812}
8813
8814pub struct ResidentTpNvfp4Expert {
8815 gate: ResidentNvfp4ColumnParallel,
8816 up: ResidentNvfp4ColumnParallel,
8817 down: ResidentNvfp4RowParallel,
8818 pub input_width: usize,
8819 pub expert_width: usize,
8820}
8821
8822pub struct ResidentNvfp4ColumnBankRank {
8826 bank: crate::CudaSlice<u8>,
8830 expert_bytes: usize,
8831 local_out: usize,
8832 in_features: usize,
8833 row_bytes: usize,
8834}
8835
8836impl ResidentNvfp4ColumnBankRank {
8837 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8838 self.bank
8839 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8840 }
8841}
8842
8843pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8849
8850pub struct ResidentNvfp4RowBankRank {
8851 bank: crate::CudaSlice<u8>,
8853 expert_bytes: usize,
8854 device_rank: usize, out_features: usize,
8856 local_in: usize,
8857 row_bytes: usize,
8858}
8859
8860impl ResidentNvfp4RowBankRank {
8861 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8862 self.bank
8863 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8864 }
8865}
8866
8867impl ResidentNvfp4TensorParallel {
8868 pub(crate) fn device_workspace_handle(
8869 &self,
8870 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8871 &self.device_workspace
8872 }
8873}
8874
8875pub struct ResidentNvfp4TensorParallel {
8876 gate: Vec<ResidentNvfp4ColumnBankRank>,
8877 up: Vec<ResidentNvfp4ColumnBankRank>,
8878 down: Vec<ResidentNvfp4RowBankRank>,
8879 macros_gate: Vec<f32>,
8880 macros_up: Vec<f32>,
8881 macros_down: Vec<f32>,
8882 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8886 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8887 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8888 pub expert_count: usize,
8889 pub input_width: usize,
8890 pub expert_width: usize,
8891 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8894 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8898 pub(crate) ep2: bool,
8902}
8903
8904pub struct Nvfp4T2Workspace {
8908 input2: Vec<crate::CudaSlice<f32>>,
8909 in_q2: Vec<crate::CudaSlice<i8>>,
8910 in_d2: Vec<crate::CudaSlice<f32>>,
8911 sel2: Vec<crate::CudaSlice<i32>>,
8912 route_w2: Vec<crate::CudaSlice<f32>>,
8913 gate_out2: Vec<crate::CudaSlice<f32>>,
8914 up_out2: Vec<crate::CudaSlice<f32>>,
8915 act_q2: Vec<crate::CudaSlice<i8>>,
8916 act_d2: Vec<crate::CudaSlice<f32>>,
8917 partial2: Vec<crate::CudaSlice<f32>>,
8918 acc_a: Vec<crate::CudaSlice<f32>>,
8920 acc_b: Vec<crate::CudaSlice<f32>>,
8921 acc2: Vec<crate::CudaSlice<f32>>,
8924 peer2: crate::CudaSlice<f32>,
8925 omix2: crate::CudaSlice<f32>,
8926 peer_a: crate::CudaSlice<f32>,
8928 peer_b: crate::CudaSlice<f32>,
8929 omix_a: crate::CudaSlice<f32>,
8930 omix_b: crate::CudaSlice<f32>,
8931 ev_entry: CudaEvent,
8932 ev_rank: Vec<CudaEvent>,
8933 ev_root: CudaEvent,
8934 t_cap: usize,
8935 n_sel: usize,
8936 e_device: usize,
8937}
8938
8939fn nvfp4_trow_workspace_needs_grow(
8940 current: Option<(usize, usize)>,
8941 t: usize,
8942 n_sel: usize,
8943) -> bool {
8944 current.is_none_or(|(t_cap, n_sel_cap)| t_cap < t || n_sel_cap < n_sel)
8945}
8946
8947#[cfg(test)]
8948mod nvfp4_trow_workspace_tests {
8949 use super::nvfp4_trow_workspace_needs_grow;
8950
8951 #[test]
8952 fn workspace_grows_but_never_shrinks_between_spec_and_batch() {
8953 assert!(nvfp4_trow_workspace_needs_grow(None, 2, 16));
8954 assert!(nvfp4_trow_workspace_needs_grow(Some((2, 16)), 8, 64));
8955 assert!(!nvfp4_trow_workspace_needs_grow(Some((8, 64)), 2, 16));
8956 assert!(!nvfp4_trow_workspace_needs_grow(Some((32, 256)), 8, 64));
8957 }
8958}
8959
8960struct RoutesGraph {
8967 exec: cudarc::driver::sys::CUgraphExec,
8968 parent: cudarc::driver::sys::CUgraph,
8969 _children: Vec<cudarc::driver::CudaGraph>,
8970}
8971unsafe impl Send for RoutesGraph {}
8974
8975impl Drop for RoutesGraph {
8976 fn drop(&mut self) {
8977 unsafe {
8978 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8979 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8980 }
8981 }
8982}
8983
8984impl Nvfp4DeviceRoutesWorkspace {
8985 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8986 self.in_stage_e.as_ref()
8987 }
8988 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8989 self.in_stage_e.as_mut()
8990 }
8991 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8992 self.out_stage_e.as_mut()
8993 }
8994 pub(crate) fn arm_stages(
8996 &mut self,
8997 e: &Engine,
8998 width: usize,
8999 n_sel: usize,
9000 ) -> Result<(), Box<dyn std::error::Error>> {
9001 let _main = e.gpu.enter_main()?;
9002 if self.in_stage_e.is_none() {
9003 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9004 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
9005 }
9006 if self.dev_route_e.is_none() {
9007 self.dev_route_e = Some((
9008 e.htod_i32(&vec![0i32; n_sel])?,
9009 e.htod(&vec![0.0f32; n_sel])?,
9010 ));
9011 }
9012 Ok(())
9013 }
9014
9015 pub(crate) fn in_and_out_stages_mut(
9017 &mut self,
9018 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
9019 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
9020 (Some(input), Some(output)) => Some((input, output)),
9021 _ => None,
9022 }
9023 }
9024 pub(crate) fn dev_route_e_mut(
9025 &mut self,
9026 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
9027 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
9028 }
9029}
9030
9031pub struct Nvfp4DeviceRoutesWorkspace {
9032 gate_out: Vec<crate::CudaSlice<f32>>,
9035 up_out: Vec<crate::CudaSlice<f32>>,
9036 act_q: Vec<crate::CudaSlice<i8>>,
9037 act_d: Vec<crate::CudaSlice<f32>>,
9038 sel: Vec<crate::CudaSlice<i32>>,
9039 partial: Vec<crate::CudaSlice<f32>>,
9040 accumulator: Vec<crate::CudaSlice<f32>>,
9041 combine_w: Vec<crate::CudaSlice<f32>>,
9043 route_w: Vec<crate::CudaSlice<f32>>,
9046 in_q: Vec<crate::CudaSlice<i8>>,
9049 in_d: Vec<crate::CudaSlice<f32>>,
9050 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
9054 prestaged: bool,
9057 rank1_routed: bool,
9060 fence_flags_raw: u64,
9064 fence_ticket: u32,
9065 ev_input: Option<(CudaEvent, usize)>,
9067 in_stage_e: Option<crate::CudaSlice<f32>>,
9070 out_stage_e: Option<crate::CudaSlice<f32>>,
9071 routes_graph: Option<RoutesGraph>,
9072 raw_dev_route_e: Option<(u64, u64)>,
9074 raw_combine: Option<(u64, u64, u64, u64)>,
9075 raw_input: Vec<u64>,
9076 raw_sel: Vec<u64>,
9077 raw_route_w: Vec<u64>,
9078 remote: crate::CudaSlice<f32>,
9079 combined: crate::CudaSlice<f32>,
9080 n_sel: usize,
9081 input: Vec<crate::CudaSlice<f32>>,
9085 ev_rank: Vec<CudaEvent>,
9086 ev_done: Option<CudaEvent>,
9087 ev_entry: Option<(CudaEvent, usize)>,
9088}
9089
9090struct ResidentNvfp4EpRank {
9092 gate: Vec<crate::CudaSlice<u8>>,
9093 up: Vec<crate::CudaSlice<u8>>,
9094 down: Vec<crate::CudaSlice<u8>>,
9095 #[allow(dead_code)]
9096 expert_range: Range<usize>,
9097}
9098
9099pub struct ResidentNvfp4ExpertParallel {
9100 ranks: Vec<ResidentNvfp4EpRank>,
9101 macros_gate: Vec<f32>,
9102 macros_up: Vec<f32>,
9103 macros_down: Vec<f32>,
9104 pub expert_count: usize,
9105 pub input_width: usize,
9106 pub expert_width: usize,
9107 gate_row_bytes: usize,
9108 down_row_bytes: usize,
9109}
9110
9111fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9112 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
9113 matrix.codes,
9114 matrix.scales,
9115 matrix.out_features,
9116 matrix.in_features,
9117 )
9118}
9119
9120fn nvfp4_row_bytes(in_features: usize) -> usize {
9121 in_features / 64 * 36 }
9123
9124pub(crate) fn fuse_rope_append_on() -> bool {
9132 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9133 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
9134}
9135
9136pub(crate) fn no_local_shadow_on() -> bool {
9137 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9138 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
9139}
9140
9141pub(crate) fn nvfp4_bank_v2_on() -> bool {
9142 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9143 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
9144}
9145
9146fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
9150 let row_bytes = nvfp4_row_bytes(in_features);
9151 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
9152 let n_slots = in_features / 32;
9153 let mut out = Vec::with_capacity(v1.len());
9154 for row in 0..out_features {
9155 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
9156 for g in 0..n_slots {
9157 let (sblk, h) = (g / 2, g % 2);
9158 let b = &r[sblk * 36..sblk * 36 + 36];
9159 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
9160 }
9161 for g in 0..n_slots {
9162 let (sblk, h) = (g / 2, g % 2);
9163 let b = &r[sblk * 36..sblk * 36 + 36];
9164 out.push(b[2 * h]);
9165 out.push(b[2 * h + 1]);
9166 }
9167 }
9168 out
9169}
9170
9171fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
9173 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
9174 let v1 = nvfp4_repack_matrix(matrix);
9175 if nvfp4_bank_v2_on() {
9176 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
9177 } else {
9178 v1
9179 }
9180}
9181
9182fn nvfp4_column_shard<'a>(
9185 matrix: Nvfp4BlockMatrix<'a>,
9186 tp: usize,
9187 rank: usize,
9188) -> Result<Nvfp4BlockMatrix<'a>, String> {
9189 if matrix.out_features % tp != 0 {
9190 return Err(format!(
9191 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
9192 matrix.out_features
9193 ));
9194 }
9195 let local_out = matrix.out_features / tp;
9196 let code_row = matrix.in_features / 2;
9197 let scale_row = matrix.in_features / 16;
9198 Ok(Nvfp4BlockMatrix {
9199 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
9200 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
9201 macro_scale: matrix.macro_scale,
9202 out_features: local_out,
9203 in_features: matrix.in_features,
9204 })
9205}
9206
9207fn nvfp4_row_shard(
9210 matrix: Nvfp4BlockMatrix<'_>,
9211 tp: usize,
9212 rank: usize,
9213) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
9214 if matrix.in_features % tp != 0 {
9215 return Err(format!(
9216 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
9217 matrix.in_features
9218 ));
9219 }
9220 let local_in = matrix.in_features / tp;
9221 if local_in % 64 != 0 {
9222 return Err(format!(
9223 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
9224 ));
9225 }
9226 let code_row = matrix.in_features / 2;
9227 let scale_row = matrix.in_features / 16;
9228 let local_code = local_in / 2;
9229 let local_scale = local_in / 16;
9230 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
9231 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
9232 for row in 0..matrix.out_features {
9233 let code_start = row * code_row + rank * local_code;
9234 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
9235 let scale_start = row * scale_row + rank * local_scale;
9236 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
9237 }
9238 Ok((codes, scales, local_in))
9239}
9240
9241fn run_rank_nvfp4(
9245 engine: &Engine,
9246 matrix: Nvfp4BlockMatrix<'_>,
9247 activations: &[f32],
9248 tokens: usize,
9249) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9250 matrix.validate()?;
9251 validate_activations(activations, tokens, matrix.in_features)?;
9252 let _main = engine.gpu.enter_main()?;
9253 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
9254 let activations = engine.htod(activations)?;
9255 let output = engine.qmatvec_nvfp4_fast(
9256 &blocks.slice(0..blocks.len()),
9257 &activations,
9258 tokens,
9259 matrix.in_features,
9260 matrix.out_features,
9261 nvfp4_row_bytes(matrix.in_features),
9262 )?;
9263 engine.dtoh(&output)
9264}
9265
9266fn upload_rank_nvfp4(
9267 engine: &Engine,
9268 matrix: Nvfp4BlockMatrix<'_>,
9269) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
9270 matrix.validate()?;
9271 let _main = engine.gpu.enter_main()?;
9272 Ok(ResidentNvfp4Rank {
9273 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
9274 macro_scale: matrix.macro_scale,
9275 out_features: matrix.out_features,
9276 in_features: matrix.in_features,
9277 row_bytes: nvfp4_row_bytes(matrix.in_features),
9278 })
9279}
9280
9281fn run_resident_rank_nvfp4(
9282 engine: &Engine,
9283 rank: &ResidentNvfp4Rank,
9284 activations: &[f32],
9285 tokens: usize,
9286) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9287 validate_activations(activations, tokens, rank.in_features)?;
9288 let _main = engine.gpu.enter_main()?;
9289 let activations = engine.htod(activations)?;
9290 let output = engine.qmatvec_nvfp4_fast(
9291 &rank.blocks.slice(0..rank.blocks.len()),
9292 &activations,
9293 tokens,
9294 rank.in_features,
9295 rank.out_features,
9296 rank.row_bytes,
9297 )?;
9298 engine.dtoh(&output)
9299}
9300
9301fn apply_macro(values: &mut [f32], macro_scale: f32) {
9302 for value in values.iter_mut() {
9303 *value *= macro_scale;
9304 }
9305}
9306
9307impl TpE4m3HostBounce {
9308 pub fn full_nvfp4(
9310 &self,
9311 matrix: Nvfp4BlockMatrix<'_>,
9312 activations: &[f32],
9313 tokens: usize,
9314 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9315 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
9316 apply_macro(&mut output, matrix.macro_scale);
9317 Ok(output)
9318 }
9319
9320 pub fn column_parallel_nvfp4(
9323 &self,
9324 matrix: Nvfp4BlockMatrix<'_>,
9325 activations: &[f32],
9326 tokens: usize,
9327 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
9328 matrix.validate()?;
9329 validate_activations(activations, tokens, matrix.in_features)?;
9330 let tp = self.ranks.len();
9331 let local_out = matrix.out_features / tp;
9332 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9333 let mut rank_outputs = Vec::with_capacity(tp);
9334 for (rank_index, rank) in self.ranks.iter().enumerate() {
9335 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
9336 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
9337 let row_start = rank_index * local_out;
9338 for token in 0..tokens {
9339 gathered[token * matrix.out_features + row_start
9340 ..token * matrix.out_features + row_start + local_out]
9341 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9342 }
9343 rank_outputs.push(output);
9344 }
9345 apply_macro(&mut gathered, matrix.macro_scale);
9346 Ok(ColumnParallelResult {
9347 gathered,
9348 rank_outputs,
9349 })
9350 }
9351
9352 pub fn row_parallel_nvfp4(
9355 &self,
9356 matrix: Nvfp4BlockMatrix<'_>,
9357 activations: &[f32],
9358 tokens: usize,
9359 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
9360 matrix.validate()?;
9361 validate_activations(activations, tokens, matrix.in_features)?;
9362 let tp = self.ranks.len();
9363 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9364 let mut rank_partials = Vec::with_capacity(tp);
9365 for (rank_index, rank) in self.ranks.iter().enumerate() {
9366 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
9367 let local_activations =
9368 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9369 let shard = Nvfp4BlockMatrix {
9370 codes: &codes,
9371 scales: &scales,
9372 macro_scale: matrix.macro_scale,
9373 out_features: matrix.out_features,
9374 in_features: local_in,
9375 };
9376 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
9377 for (sum, value) in reduced.iter_mut().zip(&partial) {
9378 *sum += *value;
9379 }
9380 rank_partials.push(partial);
9381 }
9382 apply_macro(&mut reduced, matrix.macro_scale);
9383 Ok(RowParallelResult {
9384 reduced,
9385 rank_partials,
9386 })
9387 }
9388
9389 pub fn upload_expert_nvfp4(
9390 &self,
9391 gate: Nvfp4BlockMatrix<'_>,
9392 up: Nvfp4BlockMatrix<'_>,
9393 down: Nvfp4BlockMatrix<'_>,
9394 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
9395 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9396 return Err("NVFP4 TP expert gate/up dimensions differ".into());
9397 }
9398 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9399 return Err(format!(
9400 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
9401 down.out_features, down.in_features, gate.out_features, gate.in_features
9402 )
9403 .into());
9404 }
9405 let tp = self.ranks.len();
9406 let mut gate_ranks = Vec::with_capacity(tp);
9407 let mut up_ranks = Vec::with_capacity(tp);
9408 let mut down_ranks = Vec::with_capacity(tp);
9409 for (rank_index, engine) in self.ranks.iter().enumerate() {
9410 gate_ranks.push(upload_rank_nvfp4(
9411 engine,
9412 nvfp4_column_shard(gate, tp, rank_index)?,
9413 )?);
9414 up_ranks.push(upload_rank_nvfp4(
9415 engine,
9416 nvfp4_column_shard(up, tp, rank_index)?,
9417 )?);
9418 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
9419 down_ranks.push(upload_rank_nvfp4(
9420 engine,
9421 Nvfp4BlockMatrix {
9422 codes: &codes,
9423 scales: &scales,
9424 macro_scale: down.macro_scale,
9425 out_features: down.out_features,
9426 in_features: local_in,
9427 },
9428 )?);
9429 }
9430 Ok(ResidentTpNvfp4Expert {
9431 gate: ResidentNvfp4ColumnParallel {
9432 ranks: gate_ranks,
9433 out_features: gate.out_features,
9434 in_features: gate.in_features,
9435 },
9436 up: ResidentNvfp4ColumnParallel {
9437 ranks: up_ranks,
9438 out_features: up.out_features,
9439 in_features: up.in_features,
9440 },
9441 down: ResidentNvfp4RowParallel {
9442 ranks: down_ranks,
9443 out_features: down.out_features,
9444 in_features: down.in_features,
9445 },
9446 input_width: gate.in_features,
9447 expert_width: gate.out_features,
9448 })
9449 }
9450
9451 fn column_parallel_resident_nvfp4(
9452 &self,
9453 matrix: &ResidentNvfp4ColumnParallel,
9454 activations: &[f32],
9455 tokens: usize,
9456 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9457 validate_activations(activations, tokens, matrix.in_features)?;
9458 let local_out = matrix.out_features / self.ranks.len();
9459 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
9460 let mut macro_scale = None;
9461 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9462 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
9463 let row_start = rank_index * local_out;
9464 for token in 0..tokens {
9465 gathered[token * matrix.out_features + row_start
9466 ..token * matrix.out_features + row_start + local_out]
9467 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
9468 }
9469 macro_scale = Some(shard.macro_scale);
9470 }
9471 apply_macro(
9472 &mut gathered,
9473 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
9474 );
9475 Ok(gathered)
9476 }
9477
9478 fn row_parallel_resident_nvfp4(
9479 &self,
9480 matrix: &ResidentNvfp4RowParallel,
9481 activations: &[f32],
9482 tokens: usize,
9483 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9484 validate_activations(activations, tokens, matrix.in_features)?;
9485 let tp = self.ranks.len();
9486 let local_in = matrix.in_features / tp;
9487 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9488 let mut macro_scale = None;
9489 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9490 if shard.in_features != local_in {
9491 return Err(format!(
9492 "NVFP4 resident row shard in_features {} != expected {local_in}",
9493 shard.in_features
9494 )
9495 .into());
9496 }
9497 let local_activations =
9498 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9499 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9500 for (sum, value) in reduced.iter_mut().zip(&partial) {
9501 *sum += *value;
9502 }
9503 macro_scale = Some(shard.macro_scale);
9504 }
9505 apply_macro(
9506 &mut reduced,
9507 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9508 );
9509 Ok(reduced)
9510 }
9511
9512 pub fn run_expert_nvfp4(
9513 &self,
9514 expert: &ResidentTpNvfp4Expert,
9515 input: &[f32],
9516 tokens: usize,
9517 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9518 validate_activations(input, tokens, expert.input_width)?;
9519 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9520 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9521 let activated: Vec<f32> = gate
9522 .iter()
9523 .zip(&up)
9524 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9525 .collect();
9526 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9527 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9528 }
9529
9530 pub fn upload_tensor_parallel_nvfp4(
9532 &self,
9533 gate: Nvfp4ExpertBank<'_>,
9534 up: Nvfp4ExpertBank<'_>,
9535 down: Nvfp4ExpertBank<'_>,
9536 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9537 gate.validate()?;
9538 up.validate()?;
9539 down.validate()?;
9540 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9541 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9542 }
9543 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9544 return Err("NVFP4 TP gate/up dimensions differ".into());
9545 }
9546 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9547 return Err(format!(
9548 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9549 down.out_features, down.in_features, gate.out_features, gate.in_features
9550 )
9551 .into());
9552 }
9553 let tp = self.ranks.len();
9554 if gate.out_features % tp != 0 {
9555 return Err(format!(
9556 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9557 gate.out_features
9558 )
9559 .into());
9560 }
9561 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9562 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9563 {
9564 return Err(format!(
9565 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9566 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9567 down.in_features
9568 )
9569 .into());
9570 }
9571 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9572 return Err(format!(
9573 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9574 ({NVFP4_CANONICAL_ROW_SHARDS})"
9575 )
9576 .into());
9577 }
9578
9579 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9580 let mut gate_ranks = Vec::with_capacity(tp);
9581 let mut up_ranks = Vec::with_capacity(tp);
9582 let mut macros_gate_dev = Vec::with_capacity(tp);
9583 let mut macros_up_dev = Vec::with_capacity(tp);
9584 let mut macros_down_dev = Vec::with_capacity(tp);
9585 for (rank_index, engine) in self.ranks.iter().enumerate() {
9586 let _main = engine.gpu.enter_main()?;
9587 let mut gate_host: Vec<u8> = Vec::new();
9593 let mut up_host: Vec<u8> = Vec::new();
9594 let mut owned = 0usize;
9595 for expert in 0..gate.expert_count {
9596 if ep2 {
9597 if expert % 2 != rank_index {
9598 continue;
9599 }
9600 owned += 1;
9601 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
9602 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
9603 } else {
9604 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9605 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
9606 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9607 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
9608 }
9609 }
9610 let bank_experts = if ep2 { owned } else { gate.expert_count };
9611 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9612 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9613 let local_out = if ep2 {
9614 gate.out_features
9615 } else {
9616 gate.out_features / tp
9617 };
9618 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9619 bank: engine.htod_bytes(&gate_host)?,
9620 expert_bytes: gate_expert_bytes,
9621 local_out,
9622 in_features: gate.in_features,
9623 row_bytes: nvfp4_row_bytes(gate.in_features),
9624 });
9625 up_ranks.push(ResidentNvfp4ColumnBankRank {
9626 bank: engine.htod_bytes(&up_host)?,
9627 expert_bytes: up_expert_bytes,
9628 local_out,
9629 in_features: up.in_features,
9630 row_bytes: nvfp4_row_bytes(up.in_features),
9631 });
9632 macros_gate_dev.push(engine.htod(gate.macros)?);
9633 macros_up_dev.push(engine.htod(up.macros)?);
9634 macros_down_dev.push(engine.htod(down.macros)?);
9635 }
9636 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9640 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9641 let device_rank = shard_index % tp;
9642 let engine = &self.ranks[device_rank];
9643 let _main = engine.gpu.enter_main()?;
9644 let mut down_host: Vec<u8> = Vec::new();
9645 let mut owned = 0usize;
9646 for expert in 0..down.expert_count {
9647 let down_matrix = down.expert(expert)?;
9648 if ep2 {
9649 if expert % 2 != device_rank {
9652 continue;
9653 }
9654 owned += 1;
9655 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
9656 } else {
9657 let (codes, scales, local_in) =
9658 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9659 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
9660 codes: &codes,
9661 scales: &scales,
9662 macro_scale: down_matrix.macro_scale,
9663 out_features: down_matrix.out_features,
9664 in_features: local_in,
9665 }));
9666 }
9667 }
9668 let bank_experts = if ep2 { owned } else { down.expert_count };
9669 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9670 let local_in = if ep2 {
9671 down.in_features
9672 } else {
9673 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9674 };
9675 down_ranks.push(ResidentNvfp4RowBankRank {
9676 bank: engine.htod_bytes(&down_host)?,
9677 expert_bytes: down_expert_bytes,
9678 device_rank,
9679 out_features: down.out_features,
9680 local_in,
9681 row_bytes: nvfp4_row_bytes(local_in),
9682 });
9683 }
9684 Ok(ResidentNvfp4TensorParallel {
9685 gate: gate_ranks,
9686 up: up_ranks,
9687 down: down_ranks,
9688 macros_gate: gate.macros.to_vec(),
9689 macros_up: up.macros.to_vec(),
9690 macros_down: down.macros.to_vec(),
9691 macros_gate_dev,
9692 macros_up_dev,
9693 macros_down_dev,
9694 expert_count: gate.expert_count,
9695 input_width: gate.in_features,
9696 expert_width: gate.out_features,
9697 device_workspace: std::sync::Mutex::new(None),
9698 t2_workspace: std::sync::Mutex::new(None),
9699 ep2,
9700 })
9701 }
9702
9703 fn run_full_bank_expert_nvfp4(
9707 &self,
9708 ranks: &[ResidentNvfp4ColumnBankRank],
9709 macros: &[f32],
9710 expert: usize,
9711 input: &[f32],
9712 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9713 let owner = expert & 1;
9714 let slot = expert >> 1;
9715 let bank = ranks
9716 .get(owner)
9717 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9718 let engine = &self.ranks[owner];
9719 let _main = engine.gpu.enter_main()?;
9720 let activations = engine.htod(input)?;
9721 let output = if nvfp4_bank_v2_on() {
9722 engine.qmatvec_nvfp4_fast_v2(
9723 &bank.expert(slot),
9724 &activations,
9725 1,
9726 bank.in_features,
9727 bank.local_out,
9728 bank.row_bytes,
9729 )?
9730 } else {
9731 engine.qmatvec_nvfp4_fast(
9732 &bank.expert(slot),
9733 &activations,
9734 1,
9735 bank.in_features,
9736 bank.local_out,
9737 bank.row_bytes,
9738 )?
9739 };
9740 let mut out = engine.dtoh(&output)?;
9741 apply_macro(&mut out, macros[expert]);
9742 Ok(out)
9743 }
9744
9745 fn run_full_down_expert_nvfp4(
9748 &self,
9749 shards: &[ResidentNvfp4RowBankRank],
9750 macros: &[f32],
9751 expert: usize,
9752 input: &[f32],
9753 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9754 let owner = expert & 1;
9755 let slot = expert >> 1;
9756 let shard = shards
9757 .get(owner)
9758 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9759 let engine = &self.ranks[owner];
9760 let _main = engine.gpu.enter_main()?;
9761 let activations = engine.htod(input)?;
9762 let output = if nvfp4_bank_v2_on() {
9763 engine.qmatvec_nvfp4_fast_v2(
9764 &shard.expert(slot),
9765 &activations,
9766 1,
9767 shard.local_in,
9768 shard.out_features,
9769 shard.row_bytes,
9770 )?
9771 } else {
9772 engine.qmatvec_nvfp4_fast(
9773 &shard.expert(slot),
9774 &activations,
9775 1,
9776 shard.local_in,
9777 shard.out_features,
9778 shard.row_bytes,
9779 )?
9780 };
9781 let mut out = engine.dtoh(&output)?;
9782 apply_macro(&mut out, macros[expert]);
9783 Ok(out)
9784 }
9785
9786 fn run_column_bank_expert_nvfp4(
9787 &self,
9788 ranks: &[ResidentNvfp4ColumnBankRank],
9789 macros: &[f32],
9790 expert: usize,
9791 input: &[f32],
9792 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9793 let local_out = ranks
9794 .first()
9795 .ok_or("NVFP4 TP column bank has no ranks")?
9796 .local_out;
9797 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9798 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9799 let _main = engine.gpu.enter_main()?;
9800 let activations = engine.htod(input)?;
9801 let output = if nvfp4_bank_v2_on() {
9802 engine.qmatvec_nvfp4_fast_v2(
9803 &bank.expert(expert),
9804 &activations,
9805 1,
9806 bank.in_features,
9807 bank.local_out,
9808 bank.row_bytes,
9809 )?
9810 } else {
9811 engine.qmatvec_nvfp4_fast(
9812 &bank.expert(expert),
9813 &activations,
9814 1,
9815 bank.in_features,
9816 bank.local_out,
9817 bank.row_bytes,
9818 )?
9819 };
9820 let output = engine.dtoh(&output)?;
9821 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9822 }
9823 apply_macro(&mut gathered, macros[expert]);
9824 Ok(gathered)
9825 }
9826
9827 fn run_row_bank_expert_nvfp4(
9831 &self,
9832 shards: &[ResidentNvfp4RowBankRank],
9833 macros: &[f32],
9834 expert: usize,
9835 input: &[f32],
9836 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9837 let out_features = shards
9838 .first()
9839 .ok_or("NVFP4 TP row bank has no canonical shards")?
9840 .out_features;
9841 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
9842 let mut reduced = vec![0.0f32; out_features];
9843 for (shard_index, shard) in shards.iter().enumerate() {
9844 let engine = self
9845 .ranks
9846 .get(shard.device_rank)
9847 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
9848 let _main = engine.gpu.enter_main()?;
9849 let local_activations =
9850 activation_shard(input, 1, in_features, shards.len(), shard_index);
9851 let activations = engine.htod(&local_activations)?;
9852 let output = if nvfp4_bank_v2_on() {
9853 engine.qmatvec_nvfp4_fast_v2(
9854 &shard.expert(expert),
9855 &activations,
9856 1,
9857 shard.local_in,
9858 shard.out_features,
9859 shard.row_bytes,
9860 )?
9861 } else {
9862 engine.qmatvec_nvfp4_fast(
9863 &shard.expert(expert),
9864 &activations,
9865 1,
9866 shard.local_in,
9867 shard.out_features,
9868 shard.row_bytes,
9869 )?
9870 };
9871 let partial = engine.dtoh(&output)?;
9872 for (sum, value) in reduced.iter_mut().zip(&partial) {
9873 *sum += *value;
9874 }
9875 }
9876 apply_macro(&mut reduced, macros[expert]);
9877 Ok(reduced)
9878 }
9879
9880 pub fn upload_expert_parallel_nvfp4(
9884 &self,
9885 gate: Nvfp4ExpertBank<'_>,
9886 up: Nvfp4ExpertBank<'_>,
9887 down: Nvfp4ExpertBank<'_>,
9888 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
9889 gate.validate()?;
9890 up.validate()?;
9891 down.validate()?;
9892 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9893 return Err("NVFP4 EP gate/up/down expert counts differ".into());
9894 }
9895 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9896 return Err("NVFP4 EP gate/up dimensions differ".into());
9897 }
9898 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9899 return Err(format!(
9900 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9901 down.out_features, down.in_features, gate.out_features, gate.in_features
9902 )
9903 .into());
9904 }
9905 let world = self.ranks.len();
9906 if gate.expert_count % world != 0 {
9907 return Err(format!(
9908 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9909 gate.expert_count
9910 )
9911 .into());
9912 }
9913 let experts_per_rank = gate.expert_count / world;
9914 let mut ranks = Vec::with_capacity(world);
9915 for (rank_index, engine) in self.ranks.iter().enumerate() {
9916 let _main = engine.gpu.enter_main()?;
9917 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9918 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9919 let mut up_experts = Vec::with_capacity(experts_per_rank);
9920 let mut down_experts = Vec::with_capacity(experts_per_rank);
9921 for expert in expert_range.clone() {
9922 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9923 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9924 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9925 }
9926 ranks.push(ResidentNvfp4EpRank {
9927 gate: gate_experts,
9928 up: up_experts,
9929 down: down_experts,
9930 expert_range,
9931 });
9932 }
9933 Ok(ResidentNvfp4ExpertParallel {
9934 ranks,
9935 macros_gate: gate.macros.to_vec(),
9936 macros_up: up.macros.to_vec(),
9937 macros_down: down.macros.to_vec(),
9938 expert_count: gate.expert_count,
9939 input_width: gate.in_features,
9940 expert_width: gate.out_features,
9941 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9942 down_row_bytes: nvfp4_row_bytes(down.in_features),
9943 })
9944 }
9945
9946 #[allow(clippy::too_many_arguments)]
9952 pub fn run_routed_experts_nvfp4(
9953 &self,
9954 experts: &ResidentNvfp4ExpertParallel,
9955 input: &[f32],
9956 tokens: usize,
9957 selected: &[usize],
9958 route_weights: &[f32],
9959 experts_per_token: usize,
9960 activation_limit: Option<f32>,
9961 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9962 validate_activations(input, tokens, experts.input_width)?;
9963 let pairs = tokens
9964 .checked_mul(experts_per_token)
9965 .ok_or("NVFP4 EP route count overflow")?;
9966 if selected.len() != pairs || route_weights.len() != pairs {
9967 return Err(format!(
9968 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9969 {experts_per_token} ({pairs})",
9970 selected.len(),
9971 route_weights.len(),
9972 )
9973 .into());
9974 }
9975 if !route_weights.iter().all(|weight| weight.is_finite()) {
9976 return Err("NVFP4 EP route weights contain a non-finite value".into());
9977 }
9978 let experts_per_rank = experts.expert_count / experts.ranks.len();
9979 let mut output = vec![0.0f32; tokens * experts.input_width];
9980 for token in 0..tokens {
9981 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9982 for slot in 0..experts_per_token {
9983 let pair = token * experts_per_token + slot;
9984 let expert = selected[pair];
9985 if expert >= experts.expert_count {
9986 return Err(format!(
9987 "NVFP4 EP selected expert {expert} outside 0..{}",
9988 experts.expert_count
9989 )
9990 .into());
9991 }
9992 let owner = expert / experts_per_rank;
9993 let local = expert - owner * experts_per_rank;
9994 let rank = &experts.ranks[owner];
9995 let engine = &self.ranks[owner];
9996 let _main = engine.gpu.enter_main()?;
9997 let device_input = engine.htod(input_row)?;
9998 let gate_out = engine.qmatvec_nvfp4_fast(
9999 &rank.gate[local].slice(0..rank.gate[local].len()),
10000 &device_input,
10001 1,
10002 experts.input_width,
10003 experts.expert_width,
10004 experts.gate_row_bytes,
10005 )?;
10006 let up_out = engine.qmatvec_nvfp4_fast(
10007 &rank.up[local].slice(0..rank.up[local].len()),
10008 &device_input,
10009 1,
10010 experts.input_width,
10011 experts.expert_width,
10012 experts.gate_row_bytes,
10013 )?;
10014 let mut gate_host = engine.dtoh(&gate_out)?;
10015 let mut up_host = engine.dtoh(&up_out)?;
10016 apply_macro(&mut gate_host, experts.macros_gate[expert]);
10017 apply_macro(&mut up_host, experts.macros_up[expert]);
10018 let activated: Vec<f32> = gate_host
10019 .iter()
10020 .zip(&up_host)
10021 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
10022 .collect();
10023 let device_activated = engine.htod(&activated)?;
10024 let down_out = engine.qmatvec_nvfp4_fast(
10025 &rank.down[local].slice(0..rank.down[local].len()),
10026 &device_activated,
10027 1,
10028 experts.expert_width,
10029 experts.input_width,
10030 experts.down_row_bytes,
10031 )?;
10032 let mut down_host = engine.dtoh(&down_out)?;
10033 apply_macro(&mut down_host, experts.macros_down[expert]);
10034 let weight = route_weights[pair];
10035 for (sum, value) in output
10036 [token * experts.input_width..(token + 1) * experts.input_width]
10037 .iter_mut()
10038 .zip(down_host)
10039 {
10040 *sum += weight * value;
10041 }
10042 }
10043 }
10044 Ok(output)
10045 }
10046
10047 pub fn run_tensor_parallel_routes_nvfp4_device(
10061 &self,
10062 experts: &ResidentNvfp4TensorParallel,
10063 input: &[f32],
10064 selected: &[usize],
10065 route_weights: &[f32],
10066 experts_per_token: usize,
10067 activation_limit: Option<f32>,
10068 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
10069 validate_activations(input, 1, experts.input_width)?;
10070 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10071 return Err(format!(
10072 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
10073 selected.len(),
10074 route_weights.len(),
10075 )
10076 .into());
10077 }
10078 if !route_weights.iter().all(|weight| weight.is_finite()) {
10079 return Err("NVFP4 device route weights contain a non-finite value".into());
10080 }
10081 let world = self.ranks.len();
10082 if world != NVFP4_CANONICAL_ROW_SHARDS {
10083 return Err(format!(
10084 "NVFP4 device routes require world == canonical shard grid \
10085 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10086 )
10087 .into());
10088 }
10089 let local_out = if experts.ep2 {
10090 experts.expert_width
10091 } else {
10092 experts.expert_width / world
10093 };
10094
10095 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10099 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10100 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10101 let started = timing.then(std::time::Instant::now);
10102
10103 let n_sel = experts_per_token;
10104 let mut workspace_guard = experts
10105 .device_workspace
10106 .lock()
10107 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10108 if workspace_guard.is_none() {
10109 let mut gate_out = Vec::with_capacity(world);
10110 let mut up_out = Vec::with_capacity(world);
10111 let mut act_q = Vec::with_capacity(world);
10112 let mut act_d = Vec::with_capacity(world);
10113 let mut sel = Vec::with_capacity(world);
10114 let mut partial = Vec::with_capacity(world);
10115 let mut accumulator = Vec::with_capacity(world);
10116 let mut combine_w = Vec::with_capacity(world);
10117 let mut route_w = Vec::with_capacity(world);
10118 let mut in_q = Vec::with_capacity(world);
10119 let mut in_d = Vec::with_capacity(world);
10120 let mut input = Vec::with_capacity(world);
10121 let mut ev_rank = Vec::with_capacity(world);
10122 let moe_direct = moe_direct_on();
10123 for (rank, engine) in self.ranks.iter().enumerate() {
10124 let _main = engine.gpu.enter_main()?;
10125 gate_out.push(engine.uninit(n_sel * local_out)?);
10126 up_out.push(engine.uninit(n_sel * local_out)?);
10127 act_q.push(engine.uninit_i8(n_sel * local_out)?);
10128 act_d.push(engine.uninit(n_sel * local_out / 32)?);
10129 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
10130 partial.push(engine.uninit(n_sel * experts.input_width)?);
10131 if moe_direct && rank != 0 {
10133 let root = &self.ranks[0];
10134 let _root_main = root.gpu.enter_main()?;
10135 accumulator.push(root.zeros(experts.input_width)?);
10136 } else {
10137 accumulator.push(engine.zeros(experts.input_width)?);
10138 }
10139 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10140 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
10141 in_q.push(engine.uninit_i8(experts.input_width)?);
10142 in_d.push(engine.uninit(experts.input_width / 32)?);
10143 input.push(engine.uninit(experts.input_width)?);
10144 ev_rank.push(engine.ctx().new_event(None)?);
10145 }
10146 let root = &self.ranks[0];
10147 let _main = root.gpu.enter_main()?;
10148 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
10149 prestaged: false,
10150 rank1_routed: false,
10151 ev_input: None,
10152 fence_flags_raw: 0,
10153 fence_ticket: 0,
10154 gate_out,
10155 up_out,
10156 act_q,
10157 act_d,
10158 sel,
10159 partial,
10160 accumulator,
10161 combine_w,
10162 route_w,
10163 in_q,
10164 in_d,
10165 dev_route_e: None,
10166 in_stage_e: None,
10167 out_stage_e: None,
10168 routes_graph: None,
10169 raw_dev_route_e: None,
10170 raw_combine: None,
10171 raw_input: Vec::new(),
10172 raw_sel: Vec::new(),
10173 raw_route_w: Vec::new(),
10174 remote: root.uninit(experts.input_width)?,
10175 combined: root.uninit(experts.input_width)?,
10176 n_sel,
10177 input,
10178 ev_rank,
10179 ev_done: Some(root.ctx().new_event(None)?),
10180 ev_entry: None,
10181 });
10182 }
10183 let workspace = workspace_guard
10184 .as_mut()
10185 .expect("NVFP4 device routes workspace initialized above");
10186 if experts.ep2 {
10189 return Ok(vec![0.0f32; experts.input_width]);
10190 }
10191 if workspace.n_sel != n_sel {
10192 return Err(format!(
10193 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10194 workspace.n_sel
10195 )
10196 .into());
10197 }
10198 for &expert in selected {
10199 if expert >= experts.expert_count {
10200 return Err(format!(
10201 "NVFP4 device selected expert {expert} outside 0..{}",
10202 experts.expert_count
10203 )
10204 .into());
10205 }
10206 }
10207 let sel_i32 = selected
10208 .iter()
10209 .map(|&expert| expert as i32)
10210 .collect::<Vec<_>>();
10211
10212 for (rank_index, engine) in self.ranks.iter().enumerate() {
10219 let _main = engine.gpu.enter_main()?;
10220 let device_input = engine.htod(input)?;
10221 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
10222 engine.quantize_q8_1_into(
10223 &device_input,
10224 1,
10225 experts.input_width,
10226 &mut in_q[rank_index],
10227 &mut in_d[rank_index],
10228 )?;
10229 }
10231 self.nvfp4_routes_batched_sweeps(
10232 experts,
10233 workspace,
10234 selected,
10235 route_weights,
10236 &sel_i32,
10237 local_out,
10238 n_sel,
10239 activation_limit,
10240 false,
10241 )?;
10242
10243 let root = &self.ranks[0];
10246 for engine in &self.ranks[1..] {
10247 let _main = engine.gpu.enter_main()?;
10248 engine.stream().synchronize()?;
10249 }
10250 let _main = root.gpu.enter_main()?;
10251 root.stream()
10252 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10253 root.add(
10254 &workspace.accumulator[0],
10255 &workspace.remote,
10256 &mut workspace.combined,
10257 experts.input_width,
10258 )?;
10259 let output = root.dtoh(&workspace.combined)?;
10260 if let Some(started) = started {
10261 use std::sync::atomic::Ordering;
10262 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10263 + started.elapsed().as_nanos() as u64;
10264 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10265 if calls % 430 == 0 {
10266 eprintln!(
10267 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10268 ns as f64 / 1.0e6,
10269 ns as f64 / calls as f64 / 1.0e3,
10270 );
10271 }
10272 }
10273 Ok(output)
10274 }
10275
10276 #[allow(clippy::too_many_arguments)]
10281 fn nvfp4_routes_batched_sweeps(
10282 &self,
10283 experts: &ResidentNvfp4TensorParallel,
10284 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10285 selected: &[usize],
10286 route_weights: &[f32],
10287 sel_i32: &[i32],
10288 local_out: usize,
10289 n_sel: usize,
10290 activation_limit: Option<f32>,
10291 device_routed: bool,
10292 ) -> Result<(), Box<dyn std::error::Error>> {
10293 for rank_index in 0..self.ranks.len() {
10294 self.nvfp4_routes_batched_sweeps_rank(
10295 experts,
10296 workspace,
10297 selected,
10298 route_weights,
10299 sel_i32,
10300 local_out,
10301 n_sel,
10302 activation_limit,
10303 device_routed,
10304 rank_index,
10305 )?;
10306 }
10307 Ok(())
10308 }
10309
10310 #[allow(clippy::too_many_arguments)]
10313 fn nvfp4_routes_batched_sweeps_rank(
10314 &self,
10315 experts: &ResidentNvfp4TensorParallel,
10316 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10317 selected: &[usize],
10318 route_weights: &[f32],
10319 sel_i32: &[i32],
10320 local_out: usize,
10321 n_sel: usize,
10322 activation_limit: Option<f32>,
10323 device_routed: bool,
10324 rank_index: usize,
10325 ) -> Result<(), Box<dyn std::error::Error>> {
10326 {
10327 let engine = &self.ranks[rank_index];
10328 let _main = engine.gpu.enter_main()?;
10329 if experts.ep2 {
10334 if !device_routed {
10335 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
10336 }
10337 let gate_bank = &experts.gate[rank_index];
10338 let up_bank = &experts.up[rank_index];
10339 if gate_bank.local_out != experts.expert_width
10340 || gate_bank.expert_bytes != up_bank.expert_bytes
10341 {
10342 return Err("NVFP4 EP2 bank geometry drifted".into());
10343 }
10344 {
10345 let Nvfp4DeviceRoutesWorkspace {
10346 sel,
10347 gate_out,
10348 up_out,
10349 in_q,
10350 in_d,
10351 ..
10352 } = &mut *workspace;
10353 engine.qmatvec_nvfp4_sel_gu_ep_into(
10354 &gate_bank.bank,
10355 &up_bank.bank,
10356 &sel[rank_index],
10357 &in_q[rank_index],
10358 &in_d[rank_index],
10359 &mut gate_out[rank_index],
10360 &mut up_out[rank_index],
10361 n_sel,
10362 gate_bank.in_features,
10363 gate_bank.local_out,
10364 gate_bank.row_bytes,
10365 gate_bank.expert_bytes,
10366 rank_index,
10367 )?;
10368 }
10369 {
10370 let Nvfp4DeviceRoutesWorkspace {
10371 gate_out,
10372 up_out,
10373 sel,
10374 act_q,
10375 act_d,
10376 ..
10377 } = &mut *workspace;
10378 engine.silu_mul_scaled_q8_1_sel_ep_into(
10379 &gate_out[rank_index],
10380 &up_out[rank_index],
10381 &experts.macros_gate_dev[rank_index],
10382 &experts.macros_up_dev[rank_index],
10383 &sel[rank_index],
10384 activation_limit,
10385 &mut act_q[rank_index],
10386 &mut act_d[rank_index],
10387 local_out,
10388 n_sel,
10389 rank_index,
10390 )?;
10391 }
10392 let shard = &experts.down[rank_index];
10393 if shard.device_rank != rank_index || shard.local_in != local_out {
10394 return Err("NVFP4 EP2 down bank placement drifted".into());
10395 }
10396 {
10397 let Nvfp4DeviceRoutesWorkspace {
10398 sel,
10399 act_q,
10400 act_d,
10401 route_w,
10402 accumulator,
10403 ..
10404 } = &mut *workspace;
10405 engine.qmatvec_nvfp4_sel_down8_ep_into(
10406 &shard.bank,
10407 &sel[rank_index],
10408 &act_q[rank_index],
10409 &act_d[rank_index],
10410 &route_w[rank_index],
10411 &experts.macros_down_dev[rank_index],
10412 &mut accumulator[rank_index],
10413 n_sel,
10414 shard.local_in,
10415 shard.out_features,
10416 shard.row_bytes,
10417 shard.expert_bytes,
10418 local_out,
10419 local_out / 32,
10420 rank_index,
10421 )?;
10422 }
10423 return Ok(());
10424 }
10425 if !device_routed {
10426 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
10427 let folded = (0..n_sel)
10430 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
10431 .collect::<Vec<_>>();
10432 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
10433 engine.stream().memcpy_htod(&folded, &mut view)?;
10434 }
10435 let gate_bank = &experts.gate[rank_index];
10436 let up_bank = &experts.up[rank_index];
10437 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
10438 let gu_fused = nvfp4_bank_v2_on()
10441 && gate_bank.in_features == up_bank.in_features
10442 && gate_bank.local_out == up_bank.local_out
10443 && gate_bank.row_bytes == up_bank.row_bytes
10444 && gate_bank.expert_bytes == up_bank.expert_bytes;
10445 if gu_fused {
10446 let Nvfp4DeviceRoutesWorkspace {
10447 sel,
10448 gate_out,
10449 up_out,
10450 in_q,
10451 in_d,
10452 ..
10453 } = &mut *workspace;
10454 engine.qmatvec_nvfp4_sel_gu_into(
10455 &gate_bank.bank,
10456 &up_bank.bank,
10457 &sel[rank_index],
10458 &in_q[rank_index],
10459 &in_d[rank_index],
10460 &mut gate_out[rank_index],
10461 &mut up_out[rank_index],
10462 n_sel,
10463 gate_bank.in_features,
10464 gate_bank.local_out,
10465 gate_bank.row_bytes,
10466 gate_bank.expert_bytes,
10467 )?;
10468 } else {
10469 engine.qmatvec_nvfp4_sel_into(
10470 &gate_bank.bank,
10471 &workspace.sel[rank_index],
10472 aq,
10473 ad,
10474 &mut workspace.gate_out[rank_index],
10475 n_sel,
10476 gate_bank.in_features,
10477 gate_bank.local_out,
10478 gate_bank.row_bytes,
10479 gate_bank.expert_bytes,
10480 0,
10481 0,
10482 )?;
10483 engine.qmatvec_nvfp4_sel_into(
10484 &up_bank.bank,
10485 &workspace.sel[rank_index],
10486 aq,
10487 ad,
10488 &mut workspace.up_out[rank_index],
10489 n_sel,
10490 up_bank.in_features,
10491 up_bank.local_out,
10492 up_bank.row_bytes,
10493 up_bank.expert_bytes,
10494 0,
10495 0,
10496 )?;
10497 }
10498 {
10502 let Nvfp4DeviceRoutesWorkspace {
10503 gate_out,
10504 up_out,
10505 sel,
10506 act_q,
10507 act_d,
10508 ..
10509 } = &mut *workspace;
10510 engine.silu_mul_scaled_q8_1_sel_into(
10511 &gate_out[rank_index],
10512 &up_out[rank_index],
10513 &experts.macros_gate_dev[rank_index],
10514 &experts.macros_up_dev[rank_index],
10515 &sel[rank_index],
10516 activation_limit,
10517 &mut act_q[rank_index],
10518 &mut act_d[rank_index],
10519 local_out,
10520 n_sel,
10521 )?;
10522 }
10523 let shard = &experts.down[rank_index];
10524 if shard.device_rank != rank_index || shard.local_in != local_out {
10525 return Err(
10526 "NVFP4 device routes: down canonical shard placement drifted from \
10527 the gate/up column split"
10528 .into(),
10529 );
10530 }
10531 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
10538 {
10539 static SEEN: std::sync::Mutex<Vec<(bool, bool)>> =
10543 std::sync::Mutex::new(Vec::new());
10544 if std::env::var("MEMRA_SWEEP_TRACE").as_deref() == Ok("1") {
10545 let mut seen = SEEN.lock().unwrap();
10546 if !seen.contains(&(down8, device_routed)) {
10547 seen.push((down8, device_routed));
10548 eprintln!(
10549 "[sweep-trace] down8={down8} device_routed={device_routed} \
10550 sel_down8_on={} local_in={} n_sel={n_sel}",
10551 sel_down8_on(),
10552 shard.local_in
10553 );
10554 }
10555 }
10556 }
10557 if down8 {
10558 let Nvfp4DeviceRoutesWorkspace {
10559 sel,
10560 act_q,
10561 act_d,
10562 route_w,
10563 accumulator,
10564 ..
10565 } = &mut *workspace;
10566 engine.qmatvec_nvfp4_sel_down8_into(
10567 &shard.bank,
10568 &sel[rank_index],
10569 &act_q[rank_index],
10570 &act_d[rank_index],
10571 &route_w[rank_index],
10572 &experts.macros_down_dev[rank_index],
10573 &mut accumulator[rank_index],
10574 n_sel,
10575 shard.local_in,
10576 shard.out_features,
10577 shard.row_bytes,
10578 shard.expert_bytes,
10579 local_out,
10580 local_out / 32,
10581 )?;
10582 } else {
10583 let Nvfp4DeviceRoutesWorkspace {
10584 sel,
10585 act_q,
10586 act_d,
10587 partial,
10588 ..
10589 } = &mut *workspace;
10590 engine.qmatvec_nvfp4_sel_into(
10591 &shard.bank,
10592 &sel[rank_index],
10593 &act_q[rank_index],
10594 &act_d[rank_index],
10595 &mut partial[rank_index],
10596 n_sel,
10597 shard.local_in,
10598 shard.out_features,
10599 shard.row_bytes,
10600 shard.expert_bytes,
10601 local_out,
10602 local_out / 32,
10603 )?;
10604 }
10605 if !down8 {
10610 let Nvfp4DeviceRoutesWorkspace {
10611 partial,
10612 combine_w,
10613 route_w,
10614 sel,
10615 accumulator,
10616 ..
10617 } = &mut *workspace;
10618 if device_routed {
10619 engine.axpy_rows_seq_md_into(
10620 &partial[rank_index],
10621 &route_w[rank_index],
10622 &experts.macros_down_dev[rank_index],
10623 &sel[rank_index],
10624 &mut accumulator[rank_index],
10625 experts.input_width,
10626 n_sel,
10627 )?;
10628 } else {
10629 engine.axpy_rows_seq_into(
10630 &partial[rank_index],
10631 &combine_w[rank_index],
10632 &mut accumulator[rank_index],
10633 experts.input_width,
10634 n_sel,
10635 )?;
10636 }
10637 }
10638 }
10639 Ok(())
10640 }
10641
10642 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10650 &self,
10651 experts: &ResidentNvfp4TensorParallel,
10652 e: &Engine,
10653 input_dev: &crate::CudaSlice<f32>,
10654 selected: &[usize],
10655 route_weights: &[f32],
10656 experts_per_token: usize,
10657 activation_limit: Option<f32>,
10658 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10659 if input_dev.len() != experts.input_width {
10660 return Err(format!(
10661 "NVFP4 device-io routes input {} != width {}",
10662 input_dev.len(),
10663 experts.input_width
10664 )
10665 .into());
10666 }
10667 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10668 return Err(format!(
10669 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10670 selected.len(),
10671 route_weights.len(),
10672 )
10673 .into());
10674 }
10675 if !route_weights.iter().all(|weight| weight.is_finite()) {
10676 return Err("NVFP4 device route weights contain a non-finite value".into());
10677 }
10678 let world = self.ranks.len();
10679 if world != NVFP4_CANONICAL_ROW_SHARDS {
10680 return Err(format!(
10681 "NVFP4 device routes require world == canonical shard grid \
10682 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10683 )
10684 .into());
10685 }
10686 let local_out = experts.expert_width / world;
10687 let n_sel = experts_per_token;
10688
10689 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10690 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10691 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10692 let started = timing.then(std::time::Instant::now);
10693
10694 let mut workspace_guard = experts
10695 .device_workspace
10696 .lock()
10697 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10698 if workspace_guard.is_none() {
10699 drop(workspace_guard);
10700 let zero = vec![0.0f32; experts.input_width];
10703 let zero_sel = vec![0usize; n_sel];
10704 let zero_w = vec![0.0f32; n_sel];
10705 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10706 experts,
10707 &zero,
10708 &zero_sel,
10709 &zero_w,
10710 n_sel,
10711 activation_limit,
10712 )?;
10713 workspace_guard = experts
10714 .device_workspace
10715 .lock()
10716 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10717 }
10718 let workspace = workspace_guard
10719 .as_mut()
10720 .expect("NVFP4 device routes workspace initialized above");
10721 if workspace.n_sel != n_sel {
10722 return Err(format!(
10723 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10724 workspace.n_sel
10725 )
10726 .into());
10727 }
10728 for &expert in selected {
10729 if expert >= experts.expert_count {
10730 return Err(format!(
10731 "NVFP4 device selected expert {expert} outside 0..{}",
10732 experts.expert_count
10733 )
10734 .into());
10735 }
10736 }
10737 let sel_i32 = selected
10738 .iter()
10739 .map(|&expert| expert as i32)
10740 .collect::<Vec<_>>();
10741
10742 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10746 if *device != e.ctx().ordinal() {
10747 return Err("NVFP4 device-io routes engine changed".into());
10748 }
10749 } else {
10750 let _main = e.gpu.enter_main()?;
10751 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10752 }
10753 {
10754 let _main = e.gpu.enter_main()?;
10755 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10756 ev_entry.record(&e.stream())?;
10757 }
10758 for (rank_index, engine) in self.ranks.iter().enumerate() {
10759 let _main = engine.gpu.enter_main()?;
10760 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10761 engine.stream().wait(ev_entry)?;
10762 {
10763 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10764 engine
10765 .stream()
10766 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10767 }
10768 {
10769 let Nvfp4DeviceRoutesWorkspace {
10770 input, in_q, in_d, ..
10771 } = &mut *workspace;
10772 engine.quantize_q8_1_into(
10773 &input[rank_index],
10774 1,
10775 experts.input_width,
10776 &mut in_q[rank_index],
10777 &mut in_d[rank_index],
10778 )?;
10779 }
10780 }
10781 self.nvfp4_routes_batched_sweeps(
10782 experts,
10783 workspace,
10784 selected,
10785 route_weights,
10786 &sel_i32,
10787 local_out,
10788 n_sel,
10789 activation_limit,
10790 false,
10791 )?;
10792
10793 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10799 let _main = engine.gpu.enter_main()?;
10800 workspace.ev_rank[rank_index].record(&engine.stream())?;
10801 }
10802 if moe_direct_on() && self.ranks.len() == 2 {
10803 {
10810 let root = &self.ranks[0];
10811 let _main = root.gpu.enter_main()?;
10812 workspace
10813 .ev_done
10814 .as_ref()
10815 .expect("device routes done event")
10816 .record(&root.stream())?;
10817 }
10818 let _main = e.gpu.enter_main()?;
10819 e.stream().wait(
10820 workspace
10821 .ev_done
10822 .as_ref()
10823 .expect("device routes done event"),
10824 )?;
10825 for ev in workspace.ev_rank.iter().skip(1) {
10826 e.stream().wait(ev)?;
10827 }
10828 let mut output = e.uninit(experts.input_width)?;
10829 e.add(
10830 &workspace.accumulator[0],
10831 &workspace.accumulator[1],
10832 &mut output,
10833 experts.input_width,
10834 )?;
10835 let output = output;
10836 if let Some(started) = started {
10837 use std::sync::atomic::Ordering;
10838 let ns = TIMING_NS
10839 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10840 + started.elapsed().as_nanos() as u64;
10841 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10842 if calls % 430 == 0 {
10843 eprintln!(
10844 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10845 ns as f64 / 1.0e6,
10846 ns as f64 / calls as f64 / 1.0e3,
10847 );
10848 }
10849 }
10850 return Ok(output);
10851 }
10852 {
10853 let root = &self.ranks[0];
10854 let _main = root.gpu.enter_main()?;
10855 for ev in workspace.ev_rank.iter().skip(1) {
10856 root.stream().wait(ev)?;
10857 }
10858 root.stream()
10859 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10860 {
10861 let Nvfp4DeviceRoutesWorkspace {
10862 accumulator,
10863 remote,
10864 combined,
10865 ..
10866 } = &mut *workspace;
10867 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10868 }
10869 workspace
10870 .ev_done
10871 .as_ref()
10872 .expect("device routes done event")
10873 .record(&root.stream())?;
10874 }
10875 let output = {
10876 let _main = e.gpu.enter_main()?;
10877 e.stream().wait(
10878 workspace
10879 .ev_done
10880 .as_ref()
10881 .expect("device routes done event"),
10882 )?;
10883 let mut output = e.uninit(experts.input_width)?;
10886 e.stream().memcpy_dtod(
10887 &workspace.combined.slice(0..experts.input_width),
10888 &mut output.slice_mut(0..experts.input_width),
10889 )?;
10890 output
10891 };
10892 if let Some(started) = started {
10893 use std::sync::atomic::Ordering;
10894 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10895 + started.elapsed().as_nanos() as u64;
10896 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10897 if calls % 430 == 0 {
10898 eprintln!(
10899 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10900 ns as f64 / 1.0e6,
10901 ns as f64 / calls as f64 / 1.0e3,
10902 );
10903 }
10904 }
10905 Ok(output)
10906 }
10907
10908 #[allow(clippy::too_many_arguments)]
10914 pub fn nvfp4_routes_prestage(
10919 &self,
10920 experts: &ResidentNvfp4TensorParallel,
10921 e: &Engine,
10922 input_dev: &crate::CudaSlice<f32>,
10923 ) -> Result<bool, Box<dyn std::error::Error>> {
10924 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
10925 }
10926
10927 pub fn nvfp4_routes_prestage_with(
10933 &self,
10934 experts: &ResidentNvfp4TensorParallel,
10935 e: &Engine,
10936 input_dev: &crate::CudaSlice<f32>,
10937 rank1_router: impl FnOnce(
10938 &Engine,
10939 &crate::CudaSlice<f32>,
10940 &mut crate::CudaSlice<i32>,
10941 &mut crate::CudaSlice<f32>,
10942 ) -> Result<bool, Box<dyn std::error::Error>>,
10943 ) -> Result<bool, Box<dyn std::error::Error>> {
10944 if !routes_prestage_on() || step_tp_graph_enabled()? {
10945 return Ok(false);
10946 }
10947 if input_dev.len() != experts.input_width {
10948 return Err("NVFP4 prestage input width mismatch".into());
10949 }
10950 let mut workspace_guard = experts
10951 .device_workspace
10952 .lock()
10953 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10954 let Some(workspace) = workspace_guard.as_mut() else {
10955 return Ok(false);
10956 };
10957 if workspace.ev_input.is_none() {
10958 let _main = e.gpu.enter_main()?;
10959 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10960 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
10961 return Err("NVFP4 prestage engine changed".into());
10962 }
10963 {
10964 let _main = e.gpu.enter_main()?;
10965 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10966 ev.record(&e.stream())?;
10967 }
10968 for (rank_index, engine) in self.ranks.iter().enumerate() {
10969 let _main = engine.gpu.enter_main()?;
10970 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10971 engine.stream().wait(ev)?;
10972 {
10973 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10974 engine
10975 .stream()
10976 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10977 }
10978 {
10979 let Nvfp4DeviceRoutesWorkspace {
10980 input, in_q, in_d, ..
10981 } = &mut *workspace;
10982 engine.quantize_q8_1_into(
10983 &input[rank_index],
10984 1,
10985 experts.input_width,
10986 &mut in_q[rank_index],
10987 &mut in_d[rank_index],
10988 )?;
10989 }
10990 }
10991 if self.ranks.len() == 2 {
10992 let rank1 = &self.ranks[1];
10993 let _r1 = rank1.gpu.enter_main()?;
10994 let Nvfp4DeviceRoutesWorkspace {
10995 input,
10996 sel,
10997 route_w,
10998 ..
10999 } = &mut *workspace;
11000 let (in1, rest_sel) = (&input[1], &mut sel[1]);
11001 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
11002 workspace.rank1_routed = true;
11003 }
11004 }
11005 workspace.prestaged = true;
11006 Ok(true)
11007 }
11008
11009 #[allow(clippy::too_many_arguments)]
11020 pub fn run_tensor_parallel_routes_nvfp4_device_routed_tn(
11021 &self,
11022 experts: &ResidentNvfp4TensorParallel,
11023 e: &Engine,
11024 z_t: &crate::CudaSlice<f32>,
11025 sel_d: &crate::CudaSlice<i32>,
11026 w_d: &crate::CudaSlice<f32>,
11027 t: usize,
11028 n_sel_col: usize,
11029 activation_limit: Option<f32>,
11030 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11031 let world = self.ranks.len();
11032 if world != NVFP4_CANONICAL_ROW_SHARDS {
11033 return Err("NVFP4 t-row routes require the canonical 2-shard grid".into());
11034 }
11035 let width = experts.input_width;
11036 let n_sel = t * n_sel_col;
11037 if t == 0 || t > 32 || z_t.len() < t * width || sel_d.len() < n_sel || w_d.len() < n_sel {
11038 return Err("NVFP4 t-row routes geometry".into());
11039 }
11040 if !nvfp4_bank_v2_on() {
11041 return Err("NVFP4 t-row routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
11042 }
11043 let local_out = experts.expert_width / world;
11044 let mut guard = experts
11045 .t2_workspace
11046 .lock()
11047 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
11048 if nvfp4_trow_workspace_needs_grow(guard.as_ref().map(|ws| (ws.t_cap, ws.n_sel)), t, n_sel)
11049 {
11050 let t_cap = guard.as_ref().map_or(t, |ws| ws.t_cap.max(t));
11051 let n_sel_cap = guard.as_ref().map_or(n_sel, |ws| ws.n_sel.max(n_sel));
11052 let mut input2 = Vec::new();
11053 let mut in_q2 = Vec::new();
11054 let mut in_d2 = Vec::new();
11055 let mut sel2 = Vec::new();
11056 let mut route_w2 = Vec::new();
11057 let mut gate_out2 = Vec::new();
11058 let mut up_out2 = Vec::new();
11059 let mut act_q2 = Vec::new();
11060 let mut act_d2 = Vec::new();
11061 let mut partial2 = Vec::new();
11062 let mut acc_a = Vec::new();
11063 let mut acc_b = Vec::new();
11064 let mut acc2 = Vec::new();
11065 let mut ev_rank = Vec::new();
11066 for engine in &self.ranks {
11067 let _m = engine.gpu.enter_main()?;
11068 input2.push(engine.uninit(t_cap * width)?);
11069 in_q2.push(engine.alloc_i8_uninit(t_cap * width)?);
11070 in_d2.push(engine.uninit(t_cap * (width / 32))?);
11071 sel2.push(engine.htod_i32(&vec![0i32; n_sel_cap])?);
11072 route_w2.push(engine.uninit(n_sel_cap)?);
11073 gate_out2.push(engine.uninit(n_sel_cap * local_out)?);
11074 up_out2.push(engine.uninit(n_sel_cap * local_out)?);
11075 act_q2.push(engine.alloc_i8_uninit(n_sel_cap * local_out)?);
11076 act_d2.push(engine.uninit(n_sel_cap * (local_out / 32))?);
11077 partial2.push(engine.uninit(n_sel_cap * width)?);
11078 acc_a.push(engine.uninit(width)?);
11079 acc_b.push(engine.uninit(width)?);
11080 acc2.push(engine.uninit(t_cap * width)?);
11081 ev_rank.push(engine.ctx().new_event(None)?);
11082 }
11083 let root = &self.ranks[0];
11084 let (peer_a, peer_b, omix_a, omix_b, peer2, omix2, ev_root) = {
11085 let _m = root.gpu.enter_main()?;
11086 (
11087 root.uninit(width)?,
11088 root.uninit(width)?,
11089 root.uninit(width)?,
11090 root.uninit(width)?,
11091 root.uninit(t_cap * width)?,
11092 root.uninit(t_cap * width)?,
11093 root.ctx().new_event(None)?,
11094 )
11095 };
11096 let ev_entry = {
11097 let _m = e.gpu.enter_main()?;
11098 e.ctx().new_event(None)?
11099 };
11100 *guard = Some(Nvfp4T2Workspace {
11101 input2,
11102 in_q2,
11103 in_d2,
11104 sel2,
11105 route_w2,
11106 gate_out2,
11107 up_out2,
11108 act_q2,
11109 act_d2,
11110 partial2,
11111 acc_a,
11112 acc_b,
11113 acc2,
11114 peer2,
11115 omix2,
11116 peer_a,
11117 peer_b,
11118 omix_a,
11119 omix_b,
11120 ev_entry,
11121 ev_rank,
11122 ev_root,
11123 t_cap,
11124 n_sel: n_sel_cap,
11125 e_device: e.ctx().ordinal(),
11126 });
11127 }
11128 let ws = guard.as_mut().expect("armed above");
11129 if ws.e_device != e.ctx().ordinal() {
11130 return Err("NVFP4 t2 routes engine changed".into());
11131 }
11132 {
11133 let _main = e.gpu.enter_main()?;
11134 ws.ev_entry.record(&e.stream())?;
11135 }
11136 let down8 = sel_down8_on() && (local_out >> 5) <= 32 && n_sel_col <= 8;
11140 if !down8 && t != 2 {
11141 return Err(
11142 "NVFP4 t-row routes at t != 2 require MEMRA_SEL_DOWN8=1 (fused rows kernel)".into(),
11143 );
11144 }
11145 for rank in 0..world {
11146 let engine = &self.ranks[rank];
11147 let _main = engine.gpu.enter_main()?;
11148 engine.stream().wait(&ws.ev_entry)?;
11149 {
11150 let mut dst = ws.input2[rank].slice_mut(0..t * width);
11151 engine
11152 .stream()
11153 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
11154 }
11155 {
11156 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
11157 engine
11158 .stream()
11159 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11160 }
11161 {
11162 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
11163 engine
11164 .stream()
11165 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11166 }
11167 {
11168 let Nvfp4T2Workspace {
11169 input2,
11170 in_q2,
11171 in_d2,
11172 ..
11173 } = &mut *ws;
11174 engine.quantize_q8_1_into(
11175 &input2[rank],
11176 t,
11177 width,
11178 &mut in_q2[rank],
11179 &mut in_d2[rank],
11180 )?;
11181 }
11182 let gate_bank = &experts.gate[rank];
11183 let up_bank = &experts.up[rank];
11184 if gate_bank.in_features != up_bank.in_features
11185 || gate_bank.local_out != up_bank.local_out
11186 || gate_bank.row_bytes != up_bank.row_bytes
11187 || gate_bank.expert_bytes != up_bank.expert_bytes
11188 {
11189 return Err("NVFP4 t-row routes need matched gate/up bank geometry".into());
11190 }
11191 {
11192 let Nvfp4T2Workspace {
11193 sel2,
11194 in_q2,
11195 in_d2,
11196 gate_out2,
11197 up_out2,
11198 ..
11199 } = &mut *ws;
11200 engine.qmatvec_nvfp4_sel_gu_tcol_into(
11201 &gate_bank.bank,
11202 &up_bank.bank,
11203 &sel2[rank],
11204 &in_q2[rank],
11205 &in_d2[rank],
11206 &mut gate_out2[rank],
11207 &mut up_out2[rank],
11208 n_sel,
11209 n_sel_col,
11210 gate_bank.in_features,
11211 gate_bank.local_out,
11212 gate_bank.row_bytes,
11213 gate_bank.expert_bytes,
11214 width,
11215 width / 32,
11216 )?;
11217 }
11218 {
11219 let Nvfp4T2Workspace {
11220 gate_out2,
11221 up_out2,
11222 sel2,
11223 act_q2,
11224 act_d2,
11225 ..
11226 } = &mut *ws;
11227 engine.silu_mul_scaled_q8_1_sel_into(
11228 &gate_out2[rank],
11229 &up_out2[rank],
11230 &experts.macros_gate_dev[rank],
11231 &experts.macros_up_dev[rank],
11232 &sel2[rank],
11233 activation_limit,
11234 &mut act_q2[rank],
11235 &mut act_d2[rank],
11236 local_out,
11237 n_sel,
11238 )?;
11239 }
11240 let shard = &experts.down[rank];
11241 if shard.device_rank != rank || shard.local_in != local_out {
11242 return Err("NVFP4 t-row routes: down shard placement drifted".into());
11243 }
11244 if down8 {
11248 let Nvfp4T2Workspace {
11249 sel2,
11250 act_q2,
11251 act_d2,
11252 route_w2,
11253 acc2,
11254 ..
11255 } = &mut *ws;
11256 engine.qmatvec_nvfp4_sel_down8_rows_into(
11257 &shard.bank,
11258 &sel2[rank],
11259 &act_q2[rank],
11260 &act_d2[rank],
11261 &route_w2[rank],
11262 &experts.macros_down_dev[rank],
11263 &mut acc2[rank],
11264 t,
11265 n_sel_col,
11266 shard.local_in,
11267 shard.out_features,
11268 shard.row_bytes,
11269 shard.expert_bytes,
11270 local_out,
11271 local_out / 32,
11272 )?;
11273 } else {
11274 {
11275 let Nvfp4T2Workspace {
11276 sel2,
11277 act_q2,
11278 act_d2,
11279 partial2,
11280 ..
11281 } = &mut *ws;
11282 engine.qmatvec_nvfp4_sel_into(
11283 &shard.bank,
11284 &sel2[rank],
11285 &act_q2[rank],
11286 &act_d2[rank],
11287 &mut partial2[rank],
11288 n_sel,
11289 shard.local_in,
11290 shard.out_features,
11291 shard.row_bytes,
11292 shard.expert_bytes,
11293 local_out,
11294 local_out / 32,
11295 )?;
11296 }
11297 let Nvfp4T2Workspace {
11298 partial2,
11299 route_w2,
11300 sel2,
11301 acc_a,
11302 acc_b,
11303 ..
11304 } = &mut *ws;
11305 engine.axpy_rows_seq_md_off_into(
11306 &partial2[rank],
11307 &route_w2[rank],
11308 &experts.macros_down_dev[rank],
11309 &sel2[rank],
11310 &mut acc_a[rank],
11311 width,
11312 n_sel_col,
11313 0,
11314 )?;
11315 engine.axpy_rows_seq_md_off_into(
11316 &partial2[rank],
11317 &route_w2[rank],
11318 &experts.macros_down_dev[rank],
11319 &sel2[rank],
11320 &mut acc_b[rank],
11321 width,
11322 n_sel_col,
11323 n_sel_col,
11324 )?;
11325 }
11326 if rank != 0 {
11327 ws.ev_rank[rank].record(&engine.stream())?;
11328 }
11329 }
11330 let root = &self.ranks[0];
11331 {
11332 let _main = root.gpu.enter_main()?;
11333 for ev in ws.ev_rank.iter().skip(1) {
11334 root.stream().wait(ev)?;
11335 }
11336 if down8 {
11337 let Nvfp4T2Workspace {
11340 acc2, peer2, omix2, ..
11341 } = &mut *ws;
11342 {
11343 let mut dst = peer2.slice_mut(0..t * width);
11344 root.stream()
11345 .memcpy_dtod(&acc2[1].slice(0..t * width), &mut dst)?;
11346 }
11347 root.add(&acc2[0], peer2, omix2, t * width)?;
11348 } else {
11349 let Nvfp4T2Workspace {
11350 acc_a,
11351 acc_b,
11352 peer_a,
11353 peer_b,
11354 omix_a,
11355 omix_b,
11356 ..
11357 } = &mut *ws;
11358 {
11359 let mut dst = peer_a.slice_mut(0..width);
11360 root.stream()
11361 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
11362 }
11363 {
11364 let mut dst = peer_b.slice_mut(0..width);
11365 root.stream()
11366 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
11367 }
11368 root.add(&acc_a[0], peer_a, omix_a, width)?;
11369 root.add(&acc_b[0], peer_b, omix_b, width)?;
11370 }
11371 ws.ev_root.record(&root.stream())?;
11372 }
11373 let _main = e.gpu.enter_main()?;
11374 e.stream().wait(&ws.ev_root)?;
11375 let mut out = e.uninit(t * width)?;
11376 if down8 {
11377 e.stream().memcpy_dtod(
11378 &ws.omix2.slice(0..t * width),
11379 &mut out.slice_mut(0..t * width),
11380 )?;
11381 } else {
11382 e.stream()
11383 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
11384 e.stream().memcpy_dtod(
11385 &ws.omix_b.slice(0..width),
11386 &mut out.slice_mut(width..2 * width),
11387 )?;
11388 }
11389 Ok(out)
11390 }
11391
11392 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
11393 &self,
11394 experts: &ResidentNvfp4TensorParallel,
11395 e: &Engine,
11396 input_dev: &crate::CudaSlice<f32>,
11397 sel_d: &crate::CudaSlice<i32>,
11398 w_d: &crate::CudaSlice<f32>,
11399 experts_per_token: usize,
11400 activation_limit: Option<f32>,
11401 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11402 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11403 experts,
11404 e,
11405 input_dev,
11406 sel_d,
11407 w_d,
11408 experts_per_token,
11409 activation_limit,
11410 || Ok(()),
11411 )
11412 }
11413
11414 #[allow(clippy::too_many_arguments)]
11420 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
11421 &self,
11422 experts: &ResidentNvfp4TensorParallel,
11423 e: &Engine,
11424 input_dev: &crate::CudaSlice<f32>,
11425 sel_d: &crate::CudaSlice<i32>,
11426 w_d: &crate::CudaSlice<f32>,
11427 experts_per_token: usize,
11428 activation_limit: Option<f32>,
11429 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11430 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11431 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11432 experts,
11433 e,
11434 input_dev,
11435 sel_d,
11436 w_d,
11437 experts_per_token,
11438 activation_limit,
11439 pre_join,
11440 None,
11441 )
11442 }
11443
11444 #[allow(clippy::too_many_arguments)]
11449 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
11450 &self,
11451 experts: &ResidentNvfp4TensorParallel,
11452 e: &Engine,
11453 input_dev: &crate::CudaSlice<f32>,
11454 sel_d: &crate::CudaSlice<i32>,
11455 w_d: &crate::CudaSlice<f32>,
11456 experts_per_token: usize,
11457 activation_limit: Option<f32>,
11458 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
11459 post_add: Option<(u64, u64)>,
11460 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
11461 if input_dev.len() != experts.input_width {
11462 return Err(format!(
11463 "NVFP4 device-routed input {} != width {}",
11464 input_dev.len(),
11465 experts.input_width
11466 )
11467 .into());
11468 }
11469 let n_sel = experts_per_token;
11470 if sel_d.len() < n_sel || w_d.len() < n_sel {
11471 return Err(format!(
11472 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
11473 sel_d.len(),
11474 w_d.len()
11475 )
11476 .into());
11477 }
11478 let world = self.ranks.len();
11479 if world != NVFP4_CANONICAL_ROW_SHARDS {
11480 return Err(format!(
11481 "NVFP4 device routes require world == canonical shard grid \
11482 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
11483 )
11484 .into());
11485 }
11486 let local_out = if experts.ep2 {
11487 experts.expert_width
11488 } else {
11489 experts.expert_width / world
11490 };
11491
11492 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11493 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
11494 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
11495 let started = timing.then(std::time::Instant::now);
11496
11497 let mut workspace_guard = experts
11498 .device_workspace
11499 .lock()
11500 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11501 if workspace_guard.is_none() {
11502 drop(workspace_guard);
11503 let zero = vec![0.0f32; experts.input_width];
11504 let zero_sel = vec![0usize; n_sel];
11505 let zero_w = vec![0.0f32; n_sel];
11506 let _ = self.run_tensor_parallel_routes_nvfp4_device(
11507 experts,
11508 &zero,
11509 &zero_sel,
11510 &zero_w,
11511 n_sel,
11512 activation_limit,
11513 )?;
11514 workspace_guard = experts
11515 .device_workspace
11516 .lock()
11517 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11518 }
11519 let workspace = workspace_guard
11520 .as_mut()
11521 .expect("NVFP4 device routes workspace initialized above");
11522 if workspace.n_sel != n_sel {
11523 return Err(format!(
11524 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
11525 workspace.n_sel
11526 )
11527 .into());
11528 }
11529
11530 if step_tp_graph_enabled()? {
11535 if experts.ep2 {
11536 return Err(
11537 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
11538 co-gated; unset one"
11539 .into(),
11540 );
11541 }
11542 if workspace.dev_route_e.is_none() {
11543 let _main = e.gpu.enter_main()?;
11544 workspace.dev_route_e = Some((
11545 e.htod_i32(&vec![0i32; n_sel])?,
11546 e.htod(&vec![0.0f32; n_sel])?,
11547 ));
11548 }
11549 if workspace.in_stage_e.is_none() {
11550 let _main = e.gpu.enter_main()?;
11551 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11552 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11553 }
11554 if workspace.routes_graph.is_none() {
11555 let graph = self.nvfp4_routes_build_graph(
11556 experts,
11557 workspace,
11558 local_out,
11559 n_sel,
11560 activation_limit,
11561 )?;
11562 workspace.routes_graph = Some(graph);
11563 eprintln!(
11564 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
11565 children=3 updates=none performance_claim=false"
11566 );
11567 }
11568 let output = {
11569 let _main = e.gpu.enter_main()?;
11570 {
11571 let (sel_e, w_e) = workspace
11572 .dev_route_e
11573 .as_mut()
11574 .expect("device route staging set above");
11575 {
11576 let mut dst = sel_e.slice_mut(0..n_sel);
11577 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11578 }
11579 {
11580 let mut dst = w_e.slice_mut(0..n_sel);
11581 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11582 }
11583 }
11584 {
11585 let in_stage = workspace
11586 .in_stage_e
11587 .as_mut()
11588 .expect("graph staging set above");
11589 let mut dst = in_stage.slice_mut(0..experts.input_width);
11590 e.stream()
11591 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
11592 }
11593 unsafe {
11594 let r = cudarc::driver::sys::cuGraphLaunch(
11595 workspace
11596 .routes_graph
11597 .as_ref()
11598 .expect("routes graph built above")
11599 .exec,
11600 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11601 );
11602 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11603 return Err(format!("routes graph launch: {r:?}").into());
11604 }
11605 }
11606 let mut output = e.uninit(experts.input_width)?;
11607 {
11608 let out_stage = workspace
11609 .out_stage_e
11610 .as_ref()
11611 .expect("graph staging set above");
11612 e.stream().memcpy_dtod(
11613 &out_stage.slice(0..experts.input_width),
11614 &mut output.slice_mut(0..experts.input_width),
11615 )?;
11616 }
11617 output
11618 };
11619 if let Some(started) = started {
11620 use std::sync::atomic::Ordering;
11621 let ns = TIMING_NS
11622 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11623 + started.elapsed().as_nanos() as u64;
11624 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11625 if calls % 430 == 0 {
11626 eprintln!(
11627 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11628 ns as f64 / 1.0e6,
11629 ns as f64 / calls as f64 / 1.0e3,
11630 );
11631 }
11632 }
11633 return Ok(output);
11634 }
11635
11636 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11640 if *device != e.ctx().ordinal() {
11641 return Err("NVFP4 device-routed routes engine changed".into());
11642 }
11643 } else {
11644 let _main = e.gpu.enter_main()?;
11645 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11646 }
11647 if workspace.dev_route_e.is_none() {
11648 let _main = e.gpu.enter_main()?;
11649 workspace.dev_route_e = Some((
11650 e.htod_i32(&vec![0i32; n_sel])?,
11651 e.htod(&vec![0.0f32; n_sel])?,
11652 ));
11653 }
11654 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
11660 let e_device = e.ctx().ordinal();
11661 let rank1_routed_peek = workspace.rank1_routed;
11663 let stage_needed = !mirror
11664 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
11665 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
11666 });
11667 {
11668 let _main = e.gpu.enter_main()?;
11669 if stage_needed {
11670 let (sel_e, w_e) = workspace
11671 .dev_route_e
11672 .as_mut()
11673 .expect("device route staging set above");
11674 {
11675 let mut dst = sel_e.slice_mut(0..n_sel);
11676 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11677 }
11678 {
11679 let mut dst = w_e.slice_mut(0..n_sel);
11680 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11681 }
11682 }
11683 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11684 ev_entry.record(&e.stream())?;
11685 }
11686 let prestaged = std::mem::take(&mut workspace.prestaged);
11689 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
11690 for (rank_index, engine) in self.ranks.iter().enumerate() {
11691 let _main = engine.gpu.enter_main()?;
11692 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11693 engine.stream().wait(ev_entry)?;
11694 if !prestaged {
11695 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11696 engine
11697 .stream()
11698 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11699 }
11700 if !(rank1_routed && rank_index == 1) {
11701 let same_dev = engine.ctx().ordinal() == e_device;
11705 if mirror {
11706 let Nvfp4DeviceRoutesWorkspace {
11709 sel,
11710 route_w,
11711 dev_route_e,
11712 ..
11713 } = &mut *workspace;
11714 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
11715 if same_dev {
11716 (sel_d, w_d)
11717 } else {
11718 let (sel_e, w_e) = dev_route_e
11719 .as_ref()
11720 .expect("device route staging set above");
11721 (sel_e, w_e)
11722 };
11723 engine.moe_sel_w_mirror(
11724 src_sel,
11725 src_w,
11726 &mut sel[rank_index],
11727 &mut route_w[rank_index],
11728 n_sel,
11729 )?;
11730 } else {
11731 let (sel_e, w_e) = workspace
11732 .dev_route_e
11733 .as_ref()
11734 .expect("device route staging set above");
11735 {
11736 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
11737 engine
11738 .stream()
11739 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
11740 }
11741 {
11742 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
11743 engine
11744 .stream()
11745 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11746 }
11747 }
11748 }
11749 if !prestaged {
11750 let Nvfp4DeviceRoutesWorkspace {
11751 input, in_q, in_d, ..
11752 } = &mut *workspace;
11753 engine.quantize_q8_1_into(
11754 &input[rank_index],
11755 1,
11756 experts.input_width,
11757 &mut in_q[rank_index],
11758 &mut in_d[rank_index],
11759 )?;
11760 }
11761 }
11762 self.nvfp4_routes_batched_sweeps(
11763 experts,
11764 workspace,
11765 &[],
11766 &[],
11767 &[],
11768 local_out,
11769 n_sel,
11770 activation_limit,
11771 true,
11772 )?;
11773
11774 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11777 let _main = engine.gpu.enter_main()?;
11778 workspace.ev_rank[rank_index].record(&engine.stream())?;
11779 }
11780 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
11783 let mut ticket = 0u32;
11784 if memops {
11785 use cudarc::driver::sys;
11786 if workspace.fence_flags_raw == 0 {
11787 let root = &self.ranks[0];
11788 let _main = root.gpu.enter_main()?;
11789 let mut ptr: sys::CUdeviceptr = 0;
11790 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
11791 if r != sys::CUresult::CUDA_SUCCESS {
11792 return Err(format!("fence flag alloc: {r:?}").into());
11793 }
11794 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
11795 if r != sys::CUresult::CUDA_SUCCESS {
11796 return Err(format!("fence flag memset: {r:?}").into());
11797 }
11798 workspace.fence_flags_raw = ptr as u64;
11799 }
11800 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
11801 ticket = workspace.fence_ticket;
11802 let base = workspace.fence_flags_raw;
11803 if fence_rank1_on() {
11809 let peer = &self.ranks[1];
11810 let _pmain = peer.gpu.enter_main()?;
11811 peer.ring_flag_raw(base, ticket)?;
11812 }
11813 {
11814 let root = &self.ranks[0];
11815 let _main = root.gpu.enter_main()?;
11816 let r = unsafe {
11817 sys::cuStreamWriteValue32_v2(
11818 root.stream().cu_stream() as sys::CUstream,
11819 (base + 4) as sys::CUdeviceptr,
11820 ticket,
11821 0,
11822 )
11823 };
11824 if r != sys::CUresult::CUDA_SUCCESS {
11825 return Err(format!("fence write root: {r:?}").into());
11826 }
11827 }
11828 }
11829 pre_join()?;
11832
11833 if moe_direct_on() && self.ranks.len() == 2 {
11834 let _main = e.gpu.enter_main()?;
11841 if memops {
11842 use cudarc::driver::sys;
11843 let base = workspace.fence_flags_raw;
11844 let r = unsafe {
11845 sys::cuStreamWaitValue32_v2(
11846 e.stream().cu_stream() as sys::CUstream,
11847 (base + 4) as sys::CUdeviceptr,
11848 ticket,
11849 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11850 )
11851 };
11852 if r != sys::CUresult::CUDA_SUCCESS {
11853 return Err(format!("fence wait: {r:?}").into());
11854 }
11855 if fence_rank1_on() {
11856 let r = unsafe {
11858 sys::cuStreamWaitValue32_v2(
11859 e.stream().cu_stream() as sys::CUstream,
11860 base as sys::CUdeviceptr,
11861 ticket,
11862 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11863 )
11864 };
11865 if r != sys::CUresult::CUDA_SUCCESS {
11866 return Err(format!("fence wait rank1: {r:?}").into());
11867 }
11868 } else {
11869 for ev in workspace.ev_rank.iter().skip(1) {
11870 e.stream().wait(ev)?;
11871 }
11872 }
11873 } else {
11874 {
11875 let root = &self.ranks[0];
11876 let _rmain = root.gpu.enter_main()?;
11877 workspace
11878 .ev_done
11879 .as_ref()
11880 .expect("device routes done event")
11881 .record(&root.stream())?;
11882 }
11883 e.stream().wait(
11884 workspace
11885 .ev_done
11886 .as_ref()
11887 .expect("device routes done event"),
11888 )?;
11889 for ev in workspace.ev_rank.iter().skip(1) {
11890 e.stream().wait(ev)?;
11891 }
11892 }
11893 let mut output = e.uninit(experts.input_width)?;
11894 if let Some((sh_raw, scale_raw)) = post_add {
11895 e.add3_raw(
11898 &workspace.accumulator[0],
11899 &workspace.accumulator[1],
11900 sh_raw,
11901 scale_raw,
11902 &mut output,
11903 experts.input_width,
11904 )?;
11905 } else {
11906 e.add(
11907 &workspace.accumulator[0],
11908 &workspace.accumulator[1],
11909 &mut output,
11910 experts.input_width,
11911 )?;
11912 }
11913 let output = output;
11914 if let Some(started) = started {
11915 use std::sync::atomic::Ordering;
11916 let ns = TIMING_NS
11917 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11918 + started.elapsed().as_nanos() as u64;
11919 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11920 if calls % 430 == 0 {
11921 eprintln!(
11922 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11923 ns as f64 / 1.0e6,
11924 ns as f64 / calls as f64 / 1.0e3,
11925 );
11926 }
11927 }
11928 return Ok(output);
11929 }
11930 {
11931 let root = &self.ranks[0];
11932 let _main = root.gpu.enter_main()?;
11933 for ev in workspace.ev_rank.iter().skip(1) {
11934 root.stream().wait(ev)?;
11935 }
11936 root.stream()
11937 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11938 {
11939 let Nvfp4DeviceRoutesWorkspace {
11940 accumulator,
11941 remote,
11942 combined,
11943 ..
11944 } = &mut *workspace;
11945 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11946 }
11947 workspace
11948 .ev_done
11949 .as_ref()
11950 .expect("device routes done event")
11951 .record(&root.stream())?;
11952 }
11953 let output = {
11954 let _main = e.gpu.enter_main()?;
11955 e.stream().wait(
11956 workspace
11957 .ev_done
11958 .as_ref()
11959 .expect("device routes done event"),
11960 )?;
11961 let mut output = e.uninit(experts.input_width)?;
11964 e.stream().memcpy_dtod(
11965 &workspace.combined.slice(0..experts.input_width),
11966 &mut output.slice_mut(0..experts.input_width),
11967 )?;
11968 output
11969 };
11970 if let Some(started) = started {
11971 use std::sync::atomic::Ordering;
11972 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11973 + started.elapsed().as_nanos() as u64;
11974 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11975 if calls % 430 == 0 {
11976 eprintln!(
11977 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11978 ns as f64 / 1.0e6,
11979 ns as f64 / calls as f64 / 1.0e3,
11980 );
11981 }
11982 }
11983 Ok(output)
11984 }
11985
11986 pub(crate) fn decode_v2_finish_root_fused(
11990 &self,
11991 ws: &mut StepTpDecodeV2Ws,
11992 ) -> Result<(), Box<dyn std::error::Error>> {
11993 let root = &self.ranks[0];
11994 let _main = root.gpu.enter_main()?;
11995 if ws.raw_peer_partial != 0 {
11996 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
11998 } else {
11999 root.stream()
12000 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
12001 }
12002 {
12003 let StepTpDecodeV2Ws {
12004 o_partials,
12005 peer_partial,
12006 reduce_a,
12007 o_out,
12008 ..
12009 } = &mut *ws;
12010 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
12011 }
12012 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
12013 if shadows {
12014 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
12017 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
12018 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
12019 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
12020 }
12021 if shadows && ws.raw_peer_partial != 0 {
12022 raw_copy_bytes(
12023 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
12024 ws.raw_k1,
12025 ws.local_kv_dim * 4,
12026 root,
12027 )?;
12028 raw_copy_bytes(
12029 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
12030 ws.raw_v1,
12031 ws.local_kv_dim * 4,
12032 root,
12033 )?;
12034 } else if shadows {
12035 let start = ws.local_kv_dim;
12036 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
12037 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
12038 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
12039 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
12040 }
12041 if ws.raw_mixed_stage_e != 0 {
12042 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
12045 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
12046 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
12047 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
12048 }
12049 Ok(())
12050 }
12051
12052 pub(crate) fn decode_v2_arm_token_mirrors(
12055 &self,
12056 ws: &mut StepTpDecodeV2Ws,
12057 mixed_stage_e: u64,
12058 shadow_stage_e: (u64, u64),
12059 ) -> Result<(), Box<dyn std::error::Error>> {
12060 use cudarc::driver::DevicePtr;
12061 let root = &self.ranks[0];
12062 let _main = root.gpu.enter_main()?;
12063 let stream = root.stream();
12064 let (a, _g) = ws.reduce_a.device_ptr(&stream);
12065 ws.raw_reduce_a = a as u64;
12066 ws.raw_mixed_stage_e = mixed_stage_e;
12067 ws.raw_shadow_stage_e = shadow_stage_e;
12068 Ok(())
12069 }
12070
12071 fn nvfp4_routes_build_graph(
12077 &self,
12078 experts: &ResidentNvfp4TensorParallel,
12079 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12080 local_out: usize,
12081 n_sel: usize,
12082 activation_limit: Option<f32>,
12083 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
12084 use cudarc::driver::DevicePtr;
12085 use cudarc::driver::sys;
12086 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12087 if r == sys::CUresult::CUDA_SUCCESS {
12088 Ok(())
12089 } else {
12090 Err(format!("{what}: {r:?}").into())
12091 }
12092 }
12093 let world = self.ranks.len();
12094 if world != 2 {
12095 return Err("routes graph door is built for the TP2 pair".into());
12096 }
12097 let width = experts.input_width;
12098
12099 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
12101 let stream = engine.stream();
12102 let (ptr, _g) = buf.device_ptr(&stream);
12103 ptr as u64
12104 };
12105 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
12106 let stream = engine.stream();
12107 let (ptr, _g) = buf.device_ptr(&stream);
12108 ptr as u64
12109 };
12110 let (sel_e, w_e) = workspace
12111 .dev_route_e
12112 .as_ref()
12113 .expect("device route staging set before graph build");
12114 let root_engine = &self.ranks[0];
12115 let p_in_stage = ptr_f32(
12116 workspace.in_stage_e.as_ref().expect("graph staging"),
12117 root_engine,
12118 );
12119 let p_out_stage = ptr_f32(
12120 workspace.out_stage_e.as_ref().expect("graph staging"),
12121 root_engine,
12122 );
12123 let p_sel_e = ptr_i32(sel_e, root_engine);
12124 let p_w_e = ptr_f32(w_e, root_engine);
12125 let p_input: Vec<u64> = (0..world)
12126 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
12127 .collect();
12128 let p_sel: Vec<u64> = (0..world)
12129 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
12130 .collect();
12131 let p_route_w: Vec<u64> = (0..world)
12132 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
12133 .collect();
12134 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
12135 let p_remote = ptr_f32(&workspace.remote, root_engine);
12136 let p_combined = ptr_f32(&workspace.combined, root_engine);
12137
12138 let raw_copy = |dst: u64,
12139 src: u64,
12140 bytes: usize,
12141 engine: &Engine|
12142 -> Result<(), Box<dyn std::error::Error>> {
12143 unsafe {
12144 cu_try(
12145 sys::cuMemcpyAsync(
12146 dst as sys::CUdeviceptr,
12147 src as sys::CUdeviceptr,
12148 bytes,
12149 engine.stream().cu_stream() as sys::CUstream,
12150 ),
12151 "routes graph cuMemcpyAsync",
12152 )
12153 }
12154 };
12155
12156 let mut children = Vec::with_capacity(3);
12157 for rank in 0..world {
12158 let engine = &self.ranks[rank];
12159 let _main = engine.gpu.enter_main()?;
12160 let (child, _retained) = engine.capture_graph_retained(|_| {
12161 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
12162 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
12163 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
12164 {
12165 let Nvfp4DeviceRoutesWorkspace {
12166 input, in_q, in_d, ..
12167 } = &mut *workspace;
12168 engine.quantize_q8_1_into(
12169 &input[rank],
12170 1,
12171 width,
12172 &mut in_q[rank],
12173 &mut in_d[rank],
12174 )?;
12175 }
12176 self.nvfp4_routes_batched_sweeps_rank(
12177 experts,
12178 workspace,
12179 &[],
12180 &[],
12181 &[],
12182 local_out,
12183 n_sel,
12184 activation_limit,
12185 true,
12186 rank,
12187 )?;
12188 Ok(())
12189 })?;
12190 children.push(child);
12191 }
12192 {
12193 let root = &self.ranks[0];
12194 let _main = root.gpu.enter_main()?;
12195 let (child, _retained) = root.capture_graph_retained(|_| {
12196 raw_copy(p_remote, p_acc1, width * 4, root)?;
12197 {
12198 let Nvfp4DeviceRoutesWorkspace {
12199 accumulator,
12200 remote,
12201 combined,
12202 ..
12203 } = &mut *workspace;
12204 root.add(&accumulator[0], remote, combined, width)?;
12205 }
12206 raw_copy(p_out_stage, p_combined, width * 4, root)?;
12207 Ok(())
12208 })?;
12209 children.push(child);
12210 }
12211
12212 let mut parent: sys::CUgraph = std::ptr::null_mut();
12213 unsafe {
12214 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
12215 }
12216 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
12217 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
12218 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
12219 unsafe {
12220 cu_try(
12221 sys::cuGraphAddChildGraphNode(
12222 &mut n0,
12223 parent,
12224 std::ptr::null(),
12225 0,
12226 children[0].cu_graph(),
12227 ),
12228 "routes child r0",
12229 )?;
12230 cu_try(
12231 sys::cuGraphAddChildGraphNode(
12232 &mut n1,
12233 parent,
12234 std::ptr::null(),
12235 0,
12236 children[1].cu_graph(),
12237 ),
12238 "routes child r1",
12239 )?;
12240 let deps = [n0, n1];
12241 cu_try(
12242 sys::cuGraphAddChildGraphNode(
12243 &mut n2,
12244 parent,
12245 deps.as_ptr(),
12246 2,
12247 children[2].cu_graph(),
12248 ),
12249 "routes child root",
12250 )?;
12251 }
12252 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12253 unsafe {
12254 cu_try(
12255 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
12256 "routes instantiate",
12257 )?;
12258 }
12259 Ok(RoutesGraph {
12260 exec,
12261 parent,
12262 _children: children,
12263 })
12264 }
12265
12266 #[allow(clippy::too_many_arguments)]
12270 pub(crate) fn routes_rank_section(
12271 &self,
12272 experts: &ResidentNvfp4TensorParallel,
12273 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12274 raw_input_src: u64,
12275 local_out: usize,
12276 n_sel: usize,
12277 activation_limit: Option<f32>,
12278 rank_index: usize,
12279 ) -> Result<(), Box<dyn std::error::Error>> {
12280 let engine = &self.ranks[rank_index];
12281 {
12282 let _main = engine.gpu.enter_main()?;
12283 let (sel_e_ptr, w_e_ptr) = workspace
12285 .raw_dev_route_e
12286 .ok_or("routes rank section requires armed staging pointers")?;
12287 raw_copy_bytes(
12288 workspace.raw_input[rank_index],
12289 raw_input_src,
12290 experts.input_width * 4,
12291 engine,
12292 )?;
12293 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
12294 raw_copy_bytes(
12295 workspace.raw_route_w[rank_index],
12296 w_e_ptr,
12297 n_sel * 4,
12298 engine,
12299 )?;
12300 {
12301 let Nvfp4DeviceRoutesWorkspace {
12302 input, in_q, in_d, ..
12303 } = &mut *workspace;
12304 engine.quantize_q8_1_into(
12305 &input[rank_index],
12306 1,
12307 experts.input_width,
12308 &mut in_q[rank_index],
12309 &mut in_d[rank_index],
12310 )?;
12311 }
12312 }
12313 self.nvfp4_routes_batched_sweeps_rank(
12314 experts,
12315 workspace,
12316 &[],
12317 &[],
12318 &[],
12319 local_out,
12320 n_sel,
12321 activation_limit,
12322 true,
12323 rank_index,
12324 )
12325 }
12326
12327 pub(crate) fn routes_root_section(
12330 &self,
12331 experts: &ResidentNvfp4TensorParallel,
12332 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12333 ) -> Result<(), Box<dyn std::error::Error>> {
12334 let root = &self.ranks[0];
12335 let _main = root.gpu.enter_main()?;
12336 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
12337 .raw_combine
12338 .ok_or("routes root section requires armed combine pointers")?;
12339 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
12340 {
12341 let Nvfp4DeviceRoutesWorkspace {
12342 accumulator,
12343 remote,
12344 combined,
12345 ..
12346 } = &mut *workspace;
12347 root.add(&accumulator[0], remote, combined, experts.input_width)?;
12348 }
12349 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
12350 Ok(())
12351 }
12352
12353 pub(crate) fn routes_arm_raw(
12356 &self,
12357 experts: &ResidentNvfp4TensorParallel,
12358 workspace: &mut Nvfp4DeviceRoutesWorkspace,
12359 ) -> Result<(), Box<dyn std::error::Error>> {
12360 use cudarc::driver::DevicePtr;
12361 if workspace.raw_dev_route_e.is_some() {
12362 return Ok(());
12363 }
12364 let _ = experts;
12365 let (sel_e, w_e) = workspace
12366 .dev_route_e
12367 .as_ref()
12368 .ok_or("routes staging not armed")?;
12369 let root = &self.ranks[0];
12370 {
12371 let _main = root.gpu.enter_main()?;
12372 let stream = root.stream();
12373 let (a, _g) = sel_e.device_ptr(&stream);
12374 let (b, _g) = w_e.device_ptr(&stream);
12375 workspace.raw_dev_route_e = Some((a as u64, b as u64));
12376 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
12377 let (d, _g) = workspace.remote.device_ptr(&stream);
12378 let (f, _g) = workspace.combined.device_ptr(&stream);
12379 let out_stage = workspace
12380 .out_stage_e
12381 .as_ref()
12382 .ok_or("routes out stage not armed")?;
12383 let (g_, _g) = out_stage.device_ptr(&stream);
12384 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
12385 }
12386 for rank in 0..self.ranks.len() {
12387 let engine = &self.ranks[rank];
12388 let _main = engine.gpu.enter_main()?;
12389 let stream = engine.stream();
12390 let (a, _g) = workspace.input[rank].device_ptr(&stream);
12391 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
12392 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
12393 workspace.raw_input.push(a as u64);
12394 workspace.raw_sel.push(b as u64);
12395 workspace.raw_route_w.push(c as u64);
12396 }
12397 Ok(())
12398 }
12399
12400 pub fn run_tensor_parallel_routes_nvfp4(
12404 &self,
12405 experts: &ResidentNvfp4TensorParallel,
12406 input: &[f32],
12407 tokens: usize,
12408 selected: &[usize],
12409 route_weights: &[f32],
12410 experts_per_token: usize,
12411 activation_limit: Option<f32>,
12412 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
12413 validate_activations(input, tokens, experts.input_width)?;
12414 let pairs = tokens
12415 .checked_mul(experts_per_token)
12416 .ok_or("NVFP4 TP route count overflow")?;
12417 if selected.len() != pairs || route_weights.len() != pairs {
12418 return Err(format!(
12419 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
12420 {experts_per_token} ({pairs})",
12421 selected.len(),
12422 route_weights.len(),
12423 )
12424 .into());
12425 }
12426 if !route_weights.iter().all(|weight| weight.is_finite()) {
12427 return Err("NVFP4 TP route weights contain a non-finite value".into());
12428 }
12429
12430 let mut output = vec![0.0f32; tokens * experts.input_width];
12431 for token in 0..tokens {
12432 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
12433 for slot in 0..experts_per_token {
12434 let pair = token * experts_per_token + slot;
12435 let expert = selected[pair];
12436 if expert >= experts.expert_count {
12437 return Err(format!(
12438 "NVFP4 TP selected expert {expert} outside 0..{}",
12439 experts.expert_count
12440 )
12441 .into());
12442 }
12443 let gate = if experts.ep2 {
12449 self.run_full_bank_expert_nvfp4(
12450 &experts.gate,
12451 &experts.macros_gate,
12452 expert,
12453 input_row,
12454 )?
12455 } else {
12456 self.run_column_bank_expert_nvfp4(
12457 &experts.gate,
12458 &experts.macros_gate,
12459 expert,
12460 input_row,
12461 )?
12462 };
12463 let up = if experts.ep2 {
12464 self.run_full_bank_expert_nvfp4(
12465 &experts.up,
12466 &experts.macros_up,
12467 expert,
12468 input_row,
12469 )?
12470 } else {
12471 self.run_column_bank_expert_nvfp4(
12472 &experts.up,
12473 &experts.macros_up,
12474 expert,
12475 input_row,
12476 )?
12477 };
12478 let activated: Vec<f32> = gate
12479 .iter()
12480 .zip(&up)
12481 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
12482 .collect();
12483 debug_assert_eq!(activated.len(), experts.expert_width);
12484 let down = if experts.ep2 {
12485 self.run_full_down_expert_nvfp4(
12486 &experts.down,
12487 &experts.macros_down,
12488 expert,
12489 &activated,
12490 )?
12491 } else {
12492 self.run_row_bank_expert_nvfp4(
12493 &experts.down,
12494 &experts.macros_down,
12495 expert,
12496 &activated,
12497 )?
12498 };
12499 let weight = route_weights[pair];
12500 for (sum, value) in output
12501 [token * experts.input_width..(token + 1) * experts.input_width]
12502 .iter_mut()
12503 .zip(down)
12504 {
12505 *sum += weight * value;
12506 }
12507 }
12508 }
12509 Ok(output)
12510 }
12511}
12512
12513#[cfg(test)]
12514mod tests {
12515 use super::*;
12516
12517 #[test]
12518 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
12519 let limit = Some(7.0);
12520 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
12521 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
12522 assert!(
12523 step_expert_activation_host(-20.0, 9.0, limit).abs()
12524 < step_expert_activation_host(-20.0, 9.0, None).abs()
12525 );
12526 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
12527 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
12528 assert!(validate_step_expert_activation_limit(limit).is_ok());
12529 }
12530
12531 #[test]
12532 fn moe_residual_host_preserves_official_add_order() {
12533 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
12534 assert_eq!(output, [0.0]);
12535 assert_eq!(
12536 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
12537 "MoE residual lengths residual=1 routed=2 shared=1"
12538 );
12539 }
12540
12541 #[test]
12542 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
12543 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
12544 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
12545 assert_eq!(owners.len(), 4);
12546 for (rank, owner) in owners.iter().enumerate() {
12547 assert_eq!(owner.rank, rank);
12548 assert_eq!(owner.selected, vec![0, 36]);
12549 assert_eq!(owner.token_rows, vec![0, 0]);
12550 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
12551 }
12552 }
12553
12554 #[test]
12555 fn expert_owner_routes_validate_geometry_and_selected_experts() {
12556 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
12557 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
12558 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
12559 assert!(error.contains("outside 0..288"));
12560 }
12561
12562 #[test]
12563 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
12564 let selected = [
12565 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
12566 ];
12567 assert_eq!(
12568 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
12569 16
12570 );
12571 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
12572 assert_eq!(
12573 owners
12574 .iter()
12575 .map(|owner| owner.selected.len())
12576 .collect::<Vec<_>>(),
12577 vec![2, 4, 6, 4]
12578 );
12579 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
12580 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
12581 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
12582 }
12583
12584 #[test]
12585 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
12586 let owner0 = [0usize, 3];
12587 let owner1 = [1usize, 2];
12588 let owners = [owner0.as_slice(), owner1.as_slice()];
12589 assert_eq!(
12590 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
12591 .unwrap(),
12592 WeightedRouteCombineShape {
12593 pairs: 4,
12594 max_pairs: 12,
12595 }
12596 );
12597 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
12598 assert!(
12599 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
12600 .is_err()
12601 );
12602 assert!(
12603 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
12604 .is_err()
12605 );
12606 assert!(
12607 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
12608 .is_err()
12609 );
12610 }
12611
12612 #[test]
12613 fn native_p2p_door_is_strict_and_default_off() {
12614 assert!(!parse_step_tp_native_p2p(None).unwrap());
12615 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
12616 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
12617 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
12618 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
12619 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
12620 }
12621
12622 #[test]
12623 fn bulk_p2p_door_is_strict_and_default_off() {
12624 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
12625 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
12626 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
12627 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
12628 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
12629 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
12630 }
12631
12632 #[test]
12633 fn ep_device_arithmetic_door_is_strict_and_default_off() {
12634 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
12635 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
12636 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
12637 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
12638 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
12639 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
12640 }
12641
12642 #[test]
12643 fn f32_mirror_door_is_strict_and_default_off() {
12644 assert!(!parse_step_tp_f32_mirror(None).unwrap());
12645 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
12646 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
12647 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
12648 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
12649 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
12650 }
12651
12652 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
12653 let codes = (0..out_features * in_features)
12654 .map(|index| (index % 251) as u8)
12655 .collect();
12656 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
12657 .map(|index| index as f32 + 1.0)
12658 .collect();
12659 (codes, scales)
12660 }
12661
12662 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
12663 (0..out_features * in_features)
12664 .flat_map(|value| (value as u16).to_le_bytes())
12665 .collect()
12666 }
12667
12668 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
12669 bytes
12670 .chunks_exact(2)
12671 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
12672 .collect()
12673 }
12674
12675 #[test]
12676 fn bf16_matrix_rejects_wrong_byte_count() {
12677 let bytes = vec![0u8; 4 * 4 * 2 - 1];
12678 let matrix = Bf16Matrix {
12679 bytes: &bytes,
12680 out_features: 4,
12681 in_features: 4,
12682 };
12683 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
12684 }
12685
12686 #[test]
12687 fn replicated_device_rows_require_exact_rank_local_shapes() {
12688 assert_eq!(
12689 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
12690 12_288
12691 );
12692 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
12693 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
12694 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
12695 assert!(
12696 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
12697 );
12698 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
12699 }
12700
12701 #[test]
12702 fn replicated_device_row_refresh_requires_exact_root_source() {
12703 assert_eq!(
12704 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
12705 12_288
12706 );
12707 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
12708 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
12709 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
12710 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
12711 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
12712 }
12713
12714 #[test]
12715 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
12716 for tp in [1, 2, 4, 8] {
12717 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
12718 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
12719 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
12720 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
12721 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
12722 }
12723 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
12724 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
12725 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
12726 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
12727 }
12728
12729 #[test]
12730 fn cache_rows_split_by_token_then_rank() {
12731 let rows = (0u8..24).collect::<Vec<_>>();
12732 assert_eq!(
12733 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
12734 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
12735 );
12736 assert_eq!(
12737 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
12738 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
12739 );
12740 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
12741 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
12742 }
12743
12744 #[test]
12745 fn bf16_column_shard_preserves_contiguous_output_rows() {
12746 let bytes = bf16_matrix_bytes(4, 4);
12747 let matrix = Bf16Matrix {
12748 bytes: &bytes,
12749 out_features: 4,
12750 in_features: 4,
12751 };
12752 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
12753 assert_eq!(shard.out_features, 2);
12754 assert_eq!(shard.in_features, 4);
12755 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
12756 }
12757
12758 #[test]
12759 fn bf16_row_shard_preserves_each_input_column_window() {
12760 let bytes = bf16_matrix_bytes(3, 4);
12761 let matrix = Bf16Matrix {
12762 bytes: &bytes,
12763 out_features: 3,
12764 in_features: 4,
12765 };
12766 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
12767 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
12768 }
12769
12770 #[test]
12771 fn bf16_row_block_preserves_global_column_order() {
12772 let bytes = bf16_matrix_bytes(3, 8);
12773 let matrix = Bf16Matrix {
12774 bytes: &bytes,
12775 out_features: 3,
12776 in_features: 8,
12777 };
12778 let block = bf16_row_block(matrix, 2, 3).unwrap();
12779 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
12780 }
12781
12782 #[test]
12783 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
12784 let (codes, scales) = matrix(1280, 4096);
12785 let matrix = E4m3BlockMatrix {
12786 codes: &codes,
12787 scales: &scales,
12788 out_features: 1280,
12789 in_features: 4096,
12790 };
12791 let shard = column_shard(matrix, 2, 1).unwrap();
12792 assert_eq!(shard.out_features, 640);
12793 assert_eq!(shard.codes, &codes[640 * 4096..]);
12794 assert_eq!(shard.scales, &scales[5 * 32..]);
12795 }
12796
12797 #[test]
12798 fn row_shard_preserves_each_weight_and_scale_column_window() {
12799 let (codes, scales) = matrix(4096, 1280);
12800 let matrix = E4m3BlockMatrix {
12801 codes: &codes,
12802 scales: &scales,
12803 out_features: 4096,
12804 in_features: 1280,
12805 };
12806 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
12807 assert_eq!(shard_codes.len(), 4096 * 640);
12808 assert_eq!(&shard_codes[..640], &codes[640..1280]);
12809 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
12810 assert_eq!(shard_scales.len(), 32 * 5);
12811 assert_eq!(&shard_scales[..5], &scales[5..10]);
12812 assert_eq!(&shard_scales[5..10], &scales[15..20]);
12813 }
12814
12815 #[test]
12816 fn activation_shards_keep_token_rows_separate() {
12817 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
12818 assert_eq!(
12819 activation_shard(&activations, 2, 8, 2, 1),
12820 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
12821 );
12822 }
12823
12824 #[test]
12825 fn expert_bank_selects_expert_major_code_and_scale_planes() {
12826 let expert_count = 2;
12827 let out_features = 128;
12828 let in_features = 128;
12829 let code_stride = out_features * in_features;
12830 let codes: Vec<u8> = (0..expert_count * code_stride)
12831 .map(|index| (index % 251) as u8)
12832 .collect();
12833 let scales = vec![1.0f32, 2.0];
12834 let bank = E4m3ExpertBank {
12835 codes: &codes,
12836 scales: &scales,
12837 expert_count,
12838 out_features,
12839 in_features,
12840 };
12841 bank.validate().unwrap();
12842 let expert = bank.expert(1).unwrap();
12843 assert_eq!(expert.codes, &codes[code_stride..]);
12844 assert_eq!(expert.scales, &[2.0]);
12845 }
12846
12847 #[test]
12848 fn expert_bank_rejects_non_positive_scale() {
12849 let codes = vec![0u8; 128 * 128];
12850 let scales = vec![0.0f32];
12851 let bank = E4m3ExpertBank {
12852 codes: &codes,
12853 scales: &scales,
12854 expert_count: 1,
12855 out_features: 128,
12856 in_features: 128,
12857 };
12858 assert!(bank.validate().unwrap_err().contains("non-positive"));
12859 }
12860
12861 #[test]
12862 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
12863 let expert_count = 2;
12864 let out_features = 256;
12865 let in_features = 128;
12866 let code_stride = out_features * in_features;
12867 let scale_stride = 2;
12868 let codes = (0..expert_count * code_stride)
12869 .map(|index| (index % 251) as u8)
12870 .collect::<Vec<_>>();
12871 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12872 let bank = E4m3ExpertBank {
12873 codes: &codes,
12874 scales: &scales,
12875 expert_count,
12876 out_features,
12877 in_features,
12878 };
12879
12880 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
12881 assert_eq!(rank.out_features, 128);
12882 assert_eq!(rank.in_features, 128);
12883 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12884 assert_eq!(rank.scales, vec![11.0, 21.0]);
12885 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
12886 assert_eq!(
12887 &rank.codes[128 * 128..],
12888 &codes[code_stride + 128 * 128..2 * code_stride]
12889 );
12890 assert_eq!(scale_stride, scales.len() / expert_count);
12891 }
12892
12893 #[test]
12894 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
12895 let expert_count = 2;
12896 let out_features = 128;
12897 let in_features = 256;
12898 let code_stride = out_features * in_features;
12899 let codes = (0..expert_count * code_stride)
12900 .map(|index| (index % 251) as u8)
12901 .collect::<Vec<_>>();
12902 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12903 let bank = E4m3ExpertBank {
12904 codes: &codes,
12905 scales: &scales,
12906 expert_count,
12907 out_features,
12908 in_features,
12909 };
12910
12911 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12912 assert_eq!(rank.out_features, 128);
12913 assert_eq!(rank.in_features, 128);
12914 assert_eq!(rank.k_blocks, Some(1));
12915 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12916 assert_eq!(rank.scales, vec![11.0, 21.0]);
12917 assert_eq!(&rank.codes[..128], &codes[128..256]);
12918 assert_eq!(
12919 &rank.codes[128 * 128..128 * 128 + 128],
12920 &codes[code_stride + 128..code_stride + 256]
12921 );
12922 }
12923
12924 #[test]
12925 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
12926 let expert_count = 2;
12927 let out_features = 256;
12928 let in_features = 512;
12929 let code_stride = out_features * in_features;
12930 let mut codes = vec![0u8; expert_count * code_stride];
12931 for expert in 0..expert_count {
12932 for row in 0..out_features {
12933 for block in 0..4 {
12934 let value = (expert * 80 + block * 16 + row % 16) as u8;
12935 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
12936 codes[start..start + FP8_BLOCK].fill(value);
12937 }
12938 }
12939 }
12940 let scales = vec![
12941 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,
12942 112.0, 113.0, 114.0,
12943 ];
12944 let bank = E4m3ExpertBank {
12945 codes: &codes,
12946 scales: &scales,
12947 expert_count,
12948 out_features,
12949 in_features,
12950 };
12951
12952 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12953 assert_eq!(rank.out_features, out_features);
12954 assert_eq!(rank.in_features, 256);
12955 assert_eq!(rank.k_blocks, Some(2));
12956 assert_eq!(rank.code_stride, out_features * 256);
12957 assert_eq!(rank.scale_stride, 4);
12958 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
12959 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
12960
12961 let block_stride = out_features * FP8_BLOCK;
12962 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
12963 assert!(
12964 rank.codes[block_stride..block_stride + FP8_BLOCK]
12965 .iter()
12966 .all(|&code| code == 48)
12967 );
12968 assert!(
12969 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
12970 .iter()
12971 .all(|&code| code == 112)
12972 );
12973 assert!(
12974 rank.codes
12975 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
12976 .iter()
12977 .all(|&code| code == 128)
12978 );
12979 }
12980
12981 #[test]
12982 fn step_ep_layer_specs_are_literal_and_fail_closed() {
12983 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
12984 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
12985 assert_eq!(
12986 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
12987 vec![StepEpLayerSpec {
12988 layer: 24,
12989 devices: vec![1, 2],
12990 }]
12991 );
12992 assert_eq!(
12993 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
12994 vec![
12995 StepEpLayerSpec {
12996 layer: 24,
12997 devices: vec![1, 2],
12998 },
12999 StepEpLayerSpec {
13000 layer: 25,
13001 devices: vec![1, 2],
13002 },
13003 StepEpLayerSpec {
13004 layer: 31,
13005 devices: vec![0, 2],
13006 },
13007 ]
13008 );
13009 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
13010 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
13011 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
13012 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
13013 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
13014 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13015 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
13016 }
13017
13018 #[test]
13019 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
13020 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
13021 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
13022 assert_eq!(
13023 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
13024 vec![
13025 StepTpLayerSpec {
13026 layer: 24,
13027 devices: vec![1, 2],
13028 },
13029 StepTpLayerSpec {
13030 layer: 25,
13031 devices: vec![1, 2],
13032 },
13033 ]
13034 );
13035 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
13036 assert!(error.contains("MEMRA_STEP_TP"));
13037 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
13038 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
13039
13040 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
13041 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
13042 assert_eq!(all.first().unwrap().layer, 0);
13043 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
13044 let devices = (0..8).collect::<Vec<_>>();
13045 assert!(all.iter().all(|spec| spec.devices == devices));
13046 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
13047 }
13048}
13049
13050struct TokenGraphChild {
13062 graph: cudarc::driver::CudaGraph,
13063 node: cudarc::driver::sys::CUgraphNode,
13064 ctx: cudarc::driver::sys::CUcontext,
13065}
13066
13067struct TokenGraphFaSite {
13071 ctx: cudarc::driver::sys::CUcontext,
13072 memset_o: cudarc::driver::sys::CUgraphNode,
13073 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
13074 fa: cudarc::driver::sys::CUgraphNode,
13075 combine: cudarc::driver::sys::CUgraphNode,
13076 window: usize,
13077 n_head: usize,
13078 n_head_kv: usize,
13079 head_dim: usize,
13080}
13081
13082pub struct TokenGraphBuilder {
13083 parent: cudarc::driver::sys::CUgraph,
13084 children: Vec<TokenGraphChild>,
13085 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
13088 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
13091 group: Option<(
13094 u32,
13095 Vec<cudarc::driver::sys::CUgraphNode>,
13096 Vec<cudarc::driver::sys::CUgraphNode>,
13097 )>,
13098}
13099
13100unsafe impl Send for TokenGraphBuilder {}
13102
13103impl TokenGraphBuilder {
13104 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
13105 use cudarc::driver::sys;
13106 let mut parent: sys::CUgraph = std::ptr::null_mut();
13107 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
13108 if r != sys::CUresult::CUDA_SUCCESS {
13109 return Err(format!("token graph create: {r:?}").into());
13110 }
13111 Ok(Self {
13112 parent,
13113 children: Vec::new(),
13114 frontier: Vec::new(),
13115 pending_detached: Vec::new(),
13116 group: None,
13117 })
13118 }
13119
13120 fn push_child(
13121 &mut self,
13122 graph: cudarc::driver::CudaGraph,
13123 parallel_group: Option<u32>,
13124 detached: bool,
13125 absorb: bool,
13126 ctx: cudarc::driver::sys::CUcontext,
13127 ) -> Result<(), Box<dyn std::error::Error>> {
13128 use cudarc::driver::sys;
13129 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
13133 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
13134 (state, Some(group)) => {
13135 if let Some((_, _, members)) = state.take() {
13137 self.frontier = members;
13138 }
13139 let base = self.frontier.clone();
13140 *state = Some((group, base.clone(), Vec::new()));
13141 base
13142 }
13143 (state, None) if detached => match state.as_ref() {
13144 Some((_, base, _)) => base.clone(),
13145 None => self.frontier.clone(),
13146 },
13147 (state, None) => {
13148 if let Some((_, _, members)) = state.take() {
13149 self.frontier = members;
13150 }
13151 let mut deps = self.frontier.clone();
13152 if absorb {
13153 deps.append(&mut self.pending_detached);
13154 }
13155 deps
13156 }
13157 };
13158 let mut node: sys::CUgraphNode = std::ptr::null_mut();
13159 let r = unsafe {
13160 sys::cuGraphAddChildGraphNode(
13161 &mut node,
13162 self.parent,
13163 if deps.is_empty() {
13164 std::ptr::null()
13165 } else {
13166 deps.as_ptr()
13167 },
13168 deps.len(),
13169 graph.cu_graph(),
13170 )
13171 };
13172 if r != sys::CUresult::CUDA_SUCCESS {
13173 return Err(format!("token graph child: {r:?}").into());
13174 }
13175 match (&mut self.group, parallel_group, detached) {
13176 (_, None, true) => self.pending_detached.push(node),
13177 (Some((_, _, members)), Some(_), _) => members.push(node),
13178 _ => self.frontier = vec![node],
13179 }
13180 self.children.push(TokenGraphChild { graph, node, ctx });
13181 Ok(())
13182 }
13183
13184 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
13185 use cudarc::driver::sys;
13186 if let Some((_, _, members)) = self.group.take() {
13187 self.frontier = members;
13188 }
13189 let mut fa_sites = Vec::new();
13192 for child in &self.children {
13193 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
13194 fa_sites.push(site);
13195 }
13196 }
13197 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
13198 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
13199 if r != sys::CUresult::CUDA_SUCCESS {
13200 return Err(format!("token graph instantiate: {r:?}").into());
13201 }
13202 Ok(TokenGraph {
13203 exec,
13204 parent: self.parent,
13205 _children: self.children,
13206 fa_sites,
13207 })
13208 }
13209}
13210
13211fn discover_fa_site(
13214 child_node: cudarc::driver::sys::CUgraphNode,
13215 ctx: cudarc::driver::sys::CUcontext,
13216) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
13217 use cudarc::driver::sys;
13218 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13219 if r == sys::CUresult::CUDA_SUCCESS {
13220 Ok(())
13221 } else {
13222 Err(format!("{what}: {r:?}").into())
13223 }
13224 }
13225 let mut graph: sys::CUgraph = std::ptr::null_mut();
13226 unsafe {
13227 cu_try(
13228 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
13229 "fa-site child GetGraph",
13230 )?;
13231 }
13232 let mut count: usize = 0;
13233 unsafe {
13234 cu_try(
13235 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
13236 "fa-site GetNodes(count)",
13237 )?;
13238 }
13239 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
13240 unsafe {
13241 cu_try(
13242 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
13243 "fa-site GetNodes",
13244 )?;
13245 }
13246 nodes.truncate(count);
13247 let node_type =
13248 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
13249 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
13250 unsafe {
13251 cu_try(
13252 sys::cuGraphNodeGetType(node, &mut ty),
13253 "fa-site NodeGetType",
13254 )?;
13255 }
13256 Ok(ty)
13257 };
13258 let memsets: Vec<sys::CUgraphNode> = {
13259 let mut v = Vec::new();
13260 for &node in &nodes {
13261 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
13262 v.push(node);
13263 }
13264 }
13265 v
13266 };
13267 if memsets.len() != 3 {
13268 return Ok(None);
13269 }
13270 let dependents =
13272 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
13273 let mut n: usize = 0;
13274 unsafe {
13275 cu_try(
13276 sys::cuGraphNodeGetDependentNodes_v2(
13277 node,
13278 std::ptr::null_mut(),
13279 std::ptr::null_mut(),
13280 &mut n,
13281 ),
13282 "fa-site GetDependentNodes(count)",
13283 )?;
13284 }
13285 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
13286 unsafe {
13287 cu_try(
13288 sys::cuGraphNodeGetDependentNodes_v2(
13289 node,
13290 v.as_mut_ptr(),
13291 std::ptr::null_mut(),
13292 &mut n,
13293 ),
13294 "fa-site GetDependentNodes",
13295 )?;
13296 }
13297 v.truncate(n);
13298 Ok(v)
13299 };
13300 let mut fa: Option<sys::CUgraphNode> = None;
13303 let mut last_memset: Option<sys::CUgraphNode> = None;
13304 for &ms in &memsets {
13305 for dep in dependents(ms)? {
13306 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13307 fa = Some(dep);
13308 last_memset = Some(ms);
13309 }
13310 }
13311 }
13312 let (Some(fa), Some(_last)) = (fa, last_memset) else {
13313 return Ok(None);
13314 };
13315 let mut combine: Option<sys::CUgraphNode> = None;
13316 for dep in dependents(fa)? {
13317 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
13318 combine = Some(dep);
13319 }
13320 }
13321 let Some(combine) = combine else {
13322 return Ok(None);
13323 };
13324 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13327 unsafe {
13328 cu_try(
13329 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
13330 "fa-site KernelNodeGetParams",
13331 )?;
13332 }
13333 let arg_i32 =
13334 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
13335 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
13336 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
13338 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13339 unsafe {
13340 cu_try(
13341 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13342 "fa-site MemsetNodeGetParams",
13343 )?;
13344 }
13345 Ok(mp.width)
13346 };
13347 let mut widest = memsets[0];
13348 for &ms in &memsets[1..] {
13349 if width_of(ms)? > width_of(widest)? {
13350 widest = ms;
13351 }
13352 }
13353 let memset_m: Vec<sys::CUgraphNode> =
13354 memsets.iter().copied().filter(|&m| m != widest).collect();
13355 Ok(Some(TokenGraphFaSite {
13356 ctx,
13357 memset_o: widest,
13358 memset_m: [memset_m[0], memset_m[1]],
13359 fa,
13360 combine,
13361 window: win as usize,
13362 n_head: nh as usize,
13363 n_head_kv: nhkv as usize,
13364 head_dim: hd as usize,
13365 }))
13366}
13367
13368pub struct TokenGraph {
13369 exec: cudarc::driver::sys::CUgraphExec,
13370 parent: cudarc::driver::sys::CUgraph,
13371 _children: Vec<TokenGraphChild>,
13372 fa_sites: Vec<TokenGraphFaSite>,
13373}
13374
13375unsafe impl Send for TokenGraph {}
13376
13377impl TokenGraph {
13378 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
13383 use cudarc::driver::sys;
13384 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
13385 if r == sys::CUresult::CUDA_SUCCESS {
13386 Ok(())
13387 } else {
13388 Err(format!("{what}: {r:?}").into())
13389 }
13390 }
13391 for site in &self.fa_sites {
13392 let layer_bucket = if site.window > 0 {
13393 bucket.min(site.window)
13394 } else {
13395 bucket
13396 };
13397 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
13398 let nsp = layer_bucket.div_ceil(sp).max(1);
13399 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13401 unsafe {
13402 cu_try(
13403 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
13404 "retarget fa GetParams",
13405 )?;
13406 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
13407 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
13408 params.gridDimY = nsp as u32;
13409 cu_try(
13410 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
13411 "retarget fa SetParams",
13412 )?;
13413 }
13414 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
13416 unsafe {
13417 cu_try(
13418 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
13419 "retarget combine GetParams",
13420 )?;
13421 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
13422 cu_try(
13423 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
13424 "retarget combine SetParams",
13425 )?;
13426 }
13427 let set_width =
13429 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
13430 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
13431 unsafe {
13432 cu_try(
13433 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
13434 "retarget memset GetParams",
13435 )?;
13436 }
13437 mp.width = width;
13438 unsafe {
13439 cu_try(
13440 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
13441 "retarget memset SetParams",
13442 )?;
13443 }
13444 Ok(())
13445 };
13446 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
13447 set_width(site.memset_m[0], site.n_head * nsp)?;
13448 set_width(site.memset_m[1], site.n_head * nsp)?;
13449 }
13450 Ok(())
13451 }
13452
13453 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
13454 use cudarc::driver::sys;
13455 let _main = e.gpu.enter_main()?;
13456 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
13457 if r != sys::CUresult::CUDA_SUCCESS {
13458 return Err(format!("token graph launch: {r:?}").into());
13459 }
13460 Ok(())
13461 }
13462}
13463
13464impl Drop for TokenGraph {
13465 fn drop(&mut self) {
13466 unsafe {
13467 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
13468 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
13469 }
13470 }
13471}
13472
13473std::thread_local! {
13474 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
13475 const { std::cell::RefCell::new(None) };
13476}
13477
13478pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
13480 let builder = TokenGraphBuilder::new()?;
13481 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
13482 Ok(())
13483}
13484
13485pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
13487 let builder = TOKEN_GRAPH_BUILDER
13488 .with(|cell| cell.borrow_mut().take())
13489 .ok_or("token graph build was not begun")?;
13490 builder.finish()
13491}
13492
13493pub fn token_graph_building() -> bool {
13495 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
13496}
13497
13498pub fn graph_section<F>(
13503 engine: &Engine,
13504 parallel_group: Option<u32>,
13505 f: F,
13506) -> Result<(), Box<dyn std::error::Error>>
13507where
13508 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13509{
13510 graph_section_opts(engine, parallel_group, false, false, f)
13511}
13512
13513pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13515where
13516 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13517{
13518 graph_section_opts(engine, None, false, true, f)
13519}
13520
13521pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13524where
13525 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13526{
13527 graph_section_opts(engine, None, true, false, f)
13528}
13529
13530pub fn graph_section_opts<F>(
13531 engine: &Engine,
13532 parallel_group: Option<u32>,
13533 detached: bool,
13534 absorb: bool,
13535 f: F,
13536) -> Result<(), Box<dyn std::error::Error>>
13537where
13538 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13539{
13540 let building = token_graph_building();
13541 if !building {
13542 let mut f = f;
13543 return f();
13544 }
13545 let (child, ctx) = {
13546 let _main = engine.gpu.enter_main()?;
13547 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
13548 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
13549 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13550 return Err(format!("graph section ctx query: {r:?}").into());
13551 }
13552 let mut f = f;
13553 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
13556 (child, ctx)
13557 };
13558 TOKEN_GRAPH_BUILDER.with(|cell| {
13559 cell.borrow_mut()
13560 .as_mut()
13561 .expect("builder checked above")
13562 .push_child(child, parallel_group, detached, absorb, ctx)
13563 })
13564}