1use crate::Engine;
9use crate::mmq_ffi::{DeviceExpertCsr, ExpertCsr, Fp8GroupedWorkspace};
10use crate::parallel::{PRODUCT_MAX_CARDS, STEP37_TRUNK_LAYERS};
11use cudarc::driver::{CudaEvent, CudaSlice, DeviceSlice};
12use std::ops::Range;
13
14const FP8_BLOCK: usize = 128;
15const NATIVE_P2P_PROBE_WORDS: usize = 4096;
16const STEP_GROUPED_FP8_EXPERTS: usize = 288;
17const STEP_GROUPED_FP8_TOP_K: usize = 8;
18const STEP_GROUPED_FP8_WIDTH: usize = 1280;
19
20fn validate_step_expert_activation_limit(limit: Option<f32>) -> Result<(), String> {
21 if let Some(limit) = limit {
22 if !limit.is_finite() || limit <= 0.0 {
23 return Err(format!(
24 "Step routed-expert activation limit must be positive and finite, got {limit}"
25 ));
26 }
27 }
28 Ok(())
29}
30
31pub(crate) fn routes_prestage_on() -> bool {
54 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
55 *ON.get_or_init(|| std::env::var("MEMRA_ROUTES_PRESTAGE").as_deref() == Ok("1"))
56}
57
58pub(crate) fn oproj_tail_on() -> bool {
76 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
77 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_TAIL").as_deref() == Ok("1"))
78}
79thread_local! {
80 static OPROJ_TAIL_PENDING: std::cell::Cell<Option<(u64, u64)>> =
81 const { std::cell::Cell::new(None) };
82}
83thread_local! {
84 static OPROJ_TAIL_ELIGIBLE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
89}
90pub(crate) struct OprojTailScope(());
92pub(crate) fn oproj_tail_scope() -> OprojTailScope {
93 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(true));
94 OprojTailScope(())
95}
96impl Drop for OprojTailScope {
97 fn drop(&mut self) {
98 OPROJ_TAIL_ELIGIBLE.with(|c| c.set(false));
99 OPROJ_TAIL_PENDING.with(|c| c.set(None));
101 }
102}
103thread_local! {
104 static VERIFY_TCOL: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
107}
108pub(crate) fn set_verify_tcol(c: Option<usize>) {
109 VERIFY_TCOL.with(|x| x.set(c));
110}
111pub(crate) fn take_verify_tcol() -> Option<usize> {
112 VERIFY_TCOL.with(|x| x.take())
113}
114
115pub(crate) fn tcol_oproj_on() -> bool {
122 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
123 *ON.get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ").as_deref() == Ok("1"))
124}
125thread_local! {
126 static TCOL_OPROJ_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
130 static TCOL_OPROJ_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
131}
132pub(crate) fn set_tcol_oproj_defer(c: Option<usize>) {
133 TCOL_OPROJ_DEFER.with(|x| x.set(c));
134}
135pub(crate) fn take_tcol_oproj_defer() -> Option<usize> {
136 TCOL_OPROJ_DEFER.with(|x| x.take())
137}
138pub(crate) fn set_tcol_oproj_stashed() {
139 TCOL_OPROJ_STASHED.with(|x| x.set(true));
140}
141pub(crate) fn take_tcol_oproj_stashed() -> bool {
142 TCOL_OPROJ_STASHED.with(|x| x.replace(false))
143}
144
145pub(crate) fn oproj_tail_eligible() -> bool {
146 OPROJ_TAIL_ELIGIBLE.with(|c| c.get())
147}
148pub(crate) fn take_oproj_tail() -> Option<(u64, u64)> {
149 OPROJ_TAIL_PENDING.with(|c| c.take())
150}
151pub(crate) fn set_oproj_tail(v: (u64, u64)) {
152 OPROJ_TAIL_PENDING.with(|c| c.set(Some(v)));
153}
154
155pub(crate) fn rank0_merge_on() -> bool {
156 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
157 *ON.get_or_init(|| std::env::var("MEMRA_RANK0_MERGE").as_deref() == Ok("1"))
158}
159
160pub(crate) fn len_mirror_lazy_on() -> bool {
161 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
162 *ON.get_or_init(|| std::env::var("MEMRA_LEN_MIRROR_LAZY").as_deref() == Ok("1"))
163}
164
165pub(crate) fn fence_memops_on() -> bool {
166 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
167 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_MEMOPS").as_deref() == Ok("1"))
168}
169
170pub(crate) fn moe_direct_on() -> bool {
171 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
172 *ON.get_or_init(|| std::env::var("MEMRA_MOE_DIRECT").as_deref() == Ok("1"))
173}
174
175pub(crate) fn fence_rank1_on() -> bool {
190 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
191 *ON.get_or_init(|| std::env::var("MEMRA_FENCE_RANK1").as_deref() == Ok("1"))
192}
193
194pub(crate) fn spec_fa2_on() -> bool {
200 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
201 *ON.get_or_init(|| std::env::var("MEMRA_SPEC_FA2").as_deref() == Ok("1"))
202}
203thread_local! {
204 static SPEC_FA2_DEFER: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
207 static SPEC_FA2_STASHED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
208}
209pub(crate) fn set_spec_fa2_defer(c: Option<usize>) {
210 SPEC_FA2_DEFER.with(|x| x.set(c));
211}
212pub(crate) fn take_spec_fa2_defer() -> Option<usize> {
213 SPEC_FA2_DEFER.with(|x| x.take())
214}
215pub(crate) fn set_spec_fa2_stashed() {
216 SPEC_FA2_STASHED.with(|x| x.set(true));
217}
218pub(crate) fn take_spec_fa2_stashed() -> bool {
219 SPEC_FA2_STASHED.with(|x| x.replace(false))
220}
221
222pub(crate) fn sel_mirror_on() -> bool {
223 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
224 *ON.get_or_init(|| std::env::var("MEMRA_SEL_MIRROR").as_deref() == Ok("1"))
225}
226
227pub(crate) fn step_nvfp4_ep2_on() -> bool {
234 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
235 *ON.get_or_init(|| std::env::var("MEMRA_STEP_NVFP4_EP2").as_deref() == Ok("1"))
236}
237
238pub(crate) fn sel_down8_on() -> bool {
239 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
240 *ON.get_or_init(|| std::env::var("MEMRA_SEL_DOWN8").as_deref() == Ok("1"))
241}
242
243pub(crate) fn oproj_direct_on() -> bool {
244 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
245 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
246}
247
248pub(crate) fn raw_copy_bytes(
249 dst: u64,
250 src: u64,
251 bytes: usize,
252 engine: &Engine,
253) -> Result<(), Box<dyn std::error::Error>> {
254 use cudarc::driver::sys;
255 let r = unsafe {
256 sys::cuMemcpyAsync(
257 dst as sys::CUdeviceptr,
258 src as sys::CUdeviceptr,
259 bytes,
260 engine.stream().cu_stream() as sys::CUstream,
261 )
262 };
263 if r == sys::CUresult::CUDA_SUCCESS {
264 Ok(())
265 } else {
266 Err(format!("raw_copy_bytes: {r:?}").into())
267 }
268}
269
270pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
271 let silu = gate / (1.0 + (-gate).exp());
272 match limit {
273 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
274 None => silu * up,
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq)]
279struct ExpertOwnerRoutes {
280 rank: usize,
281 selected: Vec<usize>,
282 token_rows: Vec<usize>,
283 global_pairs: Vec<usize>,
284}
285
286fn partition_expert_owner_routes(
287 expert_count: usize,
288 ranks: usize,
289 tokens: usize,
290 experts_per_token: usize,
291 selected: &[usize],
292) -> Result<Vec<ExpertOwnerRoutes>, String> {
293 if expert_count == 0
294 || ranks == 0
295 || tokens == 0
296 || experts_per_token == 0
297 || expert_count % ranks != 0
298 {
299 return Err(format!(
300 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
301 tokens={tokens} experts_per_token={experts_per_token}"
302 ));
303 }
304 let pairs = tokens
305 .checked_mul(experts_per_token)
306 .ok_or("expert-owner route count overflow")?;
307 if selected.len() != pairs {
308 return Err(format!(
309 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
310 selected.len()
311 ));
312 }
313 let per_rank = expert_count / ranks;
314 let mut owners = (0..ranks)
315 .map(|rank| ExpertOwnerRoutes {
316 rank,
317 selected: Vec::new(),
318 token_rows: Vec::new(),
319 global_pairs: Vec::new(),
320 })
321 .collect::<Vec<_>>();
322 for (pair, &expert) in selected.iter().enumerate() {
323 if expert >= expert_count {
324 return Err(format!(
325 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
326 ));
327 }
328 let rank = expert / per_rank;
329 owners[rank].selected.push(expert - rank * per_rank);
330 owners[rank].token_rows.push(pair / experts_per_token);
331 owners[rank].global_pairs.push(pair);
332 }
333 Ok(owners)
334}
335
336fn validate_step_grouped_owner_routes(
337 expert_count: usize,
338 tokens: usize,
339 selected: &[usize],
340) -> Result<usize, String> {
341 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
342 return Err(format!(
343 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
344 experts={expert_count} tokens={tokens}",
345 STEP_GROUPED_FP8_EXPERTS
346 ));
347 }
348 let pairs = tokens
349 .checked_mul(STEP_GROUPED_FP8_TOP_K)
350 .ok_or("official Step owner-grouped FP8 route count overflow")?;
351 if selected.len() != pairs {
352 return Err(format!(
353 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
354 selected.len(),
355 STEP_GROUPED_FP8_TOP_K,
356 ));
357 }
358 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
359 let mut unique = routes.to_vec();
360 unique.sort_unstable();
361 unique.dedup();
362 if unique.len() != STEP_GROUPED_FP8_TOP_K {
363 return Err(format!(
364 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
365 {routes:?}"
366 ));
367 }
368 }
369 Ok(pairs)
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373struct WeightedRouteCombineShape {
374 pairs: usize,
375 max_pairs: usize,
376}
377
378fn validate_weighted_route_combine(
379 width: usize,
380 experts_per_token: usize,
381 max_tokens: usize,
382 tokens: usize,
383 owner_global_pairs: &[&[usize]],
384 route_weights: &[f32],
385) -> Result<WeightedRouteCombineShape, String> {
386 if width == 0
387 || experts_per_token == 0
388 || max_tokens == 0
389 || tokens == 0
390 || tokens > max_tokens
391 || width > i32::MAX as usize
392 || experts_per_token > i32::MAX as usize
393 || tokens > i32::MAX as usize
394 {
395 return Err(format!(
396 "invalid weighted route combine geometry width={width} experts_per_token=\
397 {experts_per_token} tokens={tokens}/{max_tokens}"
398 ));
399 }
400 let pairs = tokens
401 .checked_mul(experts_per_token)
402 .ok_or("weighted route combine pair count overflow")?;
403 let max_pairs = max_tokens
404 .checked_mul(experts_per_token)
405 .ok_or("weighted route combine capacity overflow")?;
406 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
407 return Err(format!(
408 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
409 route_weights.len()
410 ));
411 }
412 let mut seen = vec![false; pairs];
413 let mut observed = 0usize;
414 for pairs_for_owner in owner_global_pairs {
415 observed = observed
416 .checked_add(pairs_for_owner.len())
417 .ok_or("weighted route combine observed pair count overflow")?;
418 for &pair in *pairs_for_owner {
419 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
420 return Err(format!(
421 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
422 ));
423 }
424 }
425 }
426 if observed != pairs || seen.iter().any(|present| !present) {
427 return Err(format!(
428 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
429 ));
430 }
431 Ok(WeightedRouteCombineShape { pairs, max_pairs })
432}
433
434fn cache_rank_rows(
435 rows: &[u8],
436 tokens: usize,
437 local_token_bytes: usize,
438 ranks: usize,
439 rank: usize,
440) -> Result<Vec<u8>, String> {
441 if ranks == 0 || rank >= ranks {
442 return Err(format!(
443 "TP cache rank {rank} is outside a {ranks}-rank layout"
444 ));
445 }
446 let global_token_bytes = local_token_bytes
447 .checked_mul(ranks)
448 .ok_or("TP cache global token-byte overflow")?;
449 let expected = tokens
450 .checked_mul(global_token_bytes)
451 .ok_or("TP cache row-byte overflow")?;
452 if rows.len() != expected {
453 return Err(format!(
454 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
455 rows.len()
456 ));
457 }
458 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
459 for token in 0..tokens {
460 let start = token * global_token_bytes + rank * local_token_bytes;
461 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
462 }
463 Ok(shard)
464}
465
466fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
467 match value {
468 None | Some("") | Some("0") => Ok(false),
469 Some("1") => Ok(true),
470 Some(value) => Err(format!(
471 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
472 )),
473 }
474}
475
476pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
477 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
478}
479
480fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
481 match value {
482 None | Some("") | Some("0") => Ok(false),
483 Some("1") => Ok(true),
484 Some(value) => Err(format!(
485 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
486 )),
487 }
488}
489
490pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
491 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
492}
493
494fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
495 match value {
496 None | Some("") | Some("0") => Ok(false),
497 Some("1") => Ok(true),
498 Some(value) => Err(format!(
499 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
500 )),
501 }
502}
503
504fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
505 match value {
506 None | Some("") | Some("0") => Ok(false),
507 Some("1") => Ok(true),
508 Some(value) => Err(format!(
509 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
510 )),
511 }
512}
513
514pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
517 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
518}
519
520pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
521 parse_step_ep_device_arithmetic(
522 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
523 .ok()
524 .as_deref(),
525 )
526}
527
528fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
529 match value {
530 None | Some("") | Some("0") => Ok(false),
531 Some("1") => Ok(true),
532 Some(value) => Err(format!(
533 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
534 )),
535 }
536}
537
538pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
539 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
540}
541
542fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
543 match value {
544 None | Some("") | Some("0") => Ok(false),
545 Some("1") => Ok(true),
546 Some(value) => Err(format!(
547 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
548 )),
549 }
550}
551
552pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
557 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
558}
559
560fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
561 match value {
562 None | Some("") | Some("0") => Ok(false),
563 Some("1") => Ok(true),
564 Some(value) => Err(format!(
565 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
566 )),
567 }
568}
569
570fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
571 match value {
572 None | Some("") | Some("0") => Ok(false),
573 Some("1") => Ok(true),
574 Some(value) => Err(format!(
575 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
576 )),
577 }
578}
579
580pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
584 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
585}
586
587fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
588 match value {
589 None | Some("") | Some("0") => Ok(false),
590 Some("1") => Ok(true),
591 Some(value) => Err(format!(
592 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
593 )),
594 }
595}
596
597fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
598 match value {
599 None | Some("") | Some("0") => Ok(false),
600 Some("1") => Ok(true),
601 Some(value) => Err(format!(
602 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
603 )),
604 }
605}
606
607pub fn step_tp_dcw_enabled() -> Result<bool, String> {
612 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
613}
614
615pub fn step_tp_graph_enabled() -> Result<bool, String> {
620 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
621}
622
623pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
627 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
628}
629
630#[derive(Debug, Clone, PartialEq, Eq)]
631pub struct StepEpLayerSpec {
632 pub layer: usize,
633 pub devices: Vec<usize>,
634}
635
636pub type StepTpLayerSpec = StepEpLayerSpec;
637
638fn parse_step_layer_specs(
639 flag: &str,
640 value: Option<&str>,
641 allow_full_model: bool,
642) -> Result<Vec<StepEpLayerSpec>, String> {
643 let Some(value) = value else {
644 return Ok(Vec::new());
645 };
646 if value.is_empty() || value == "0" {
647 return Ok(Vec::new());
648 }
649
650 let mut specs = Vec::new();
651 for item in value.split(';') {
652 let (layers, devices) = item.split_once('@').ok_or_else(|| {
653 let layers = if allow_full_model {
654 "LAYER[-LAYER] or all"
655 } else {
656 "LAYER[-LAYER]"
657 };
658 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
659 })?;
660 let (first, last) = if layers == "all" {
661 if !allow_full_model {
662 return Err(format!(
663 "{flag} does not support the full-model shorthand; assign routed layers \
664 explicitly"
665 ));
666 }
667 (0, STEP37_TRUNK_LAYERS - 1)
668 } else {
669 match layers.split_once('-') {
670 Some((first, last)) => {
671 let first = first
672 .parse::<usize>()
673 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
674 let last = last
675 .parse::<usize>()
676 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
677 if first > last {
678 return Err(format!("{flag} layer range {first}-{last} is reversed"));
679 }
680 if last - first + 1 > 128 {
681 return Err(format!(
682 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
683 ));
684 }
685 (first, last)
686 }
687 None => {
688 let layer = layers
689 .parse::<usize>()
690 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
691 (layer, layer)
692 }
693 }
694 };
695 let devices = devices
696 .split(',')
697 .map(|device| {
698 device
699 .parse::<usize>()
700 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
701 })
702 .collect::<Result<Vec<_>, _>>()?;
703 if !(2..=8).contains(&devices.len()) {
704 return Err(format!(
705 "{flag} requires 2..=8 devices, got {}",
706 devices.len()
707 ));
708 }
709 let mut unique = devices.clone();
710 unique.sort_unstable();
711 unique.dedup();
712 if unique.len() != devices.len() {
713 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
714 }
715 for layer in first..=last {
716 if specs
717 .iter()
718 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
719 {
720 return Err(format!("{flag} assigns layer {layer} more than once"));
721 }
722 specs.push(StepEpLayerSpec {
723 layer,
724 devices: devices.clone(),
725 });
726 }
727 }
728 Ok(specs)
729}
730
731pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
732 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
733}
734
735pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
736 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
737}
738
739pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
740 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
741}
742
743pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
744 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
745}
746
747#[derive(Clone, Copy)]
748pub struct E4m3BlockMatrix<'a> {
749 pub codes: &'a [u8],
750 pub scales: &'a [f32],
751 pub out_features: usize,
752 pub in_features: usize,
753}
754
755impl E4m3BlockMatrix<'_> {
756 fn validate(&self) -> Result<(), String> {
757 let code_count = self
758 .out_features
759 .checked_mul(self.in_features)
760 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
761 if self.codes.len() != code_count {
762 return Err(format!(
763 "E4M3 code count {} != {}x{} ({code_count})",
764 self.codes.len(),
765 self.out_features,
766 self.in_features,
767 ));
768 }
769 let scale_count =
770 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
771 if self.scales.len() != scale_count {
772 return Err(format!(
773 "E4M3 scale count {} != {scale_count} for {}x{}",
774 self.scales.len(),
775 self.out_features,
776 self.in_features,
777 ));
778 }
779 if !self
780 .scales
781 .iter()
782 .all(|scale| scale.is_finite() && *scale > 0.0)
783 {
784 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
785 }
786 Ok(())
787 }
788}
789
790#[derive(Clone, Copy)]
791pub struct E4m3ExpertBank<'a> {
792 pub codes: &'a [u8],
793 pub scales: &'a [f32],
794 pub expert_count: usize,
795 pub out_features: usize,
796 pub in_features: usize,
797}
798
799impl E4m3ExpertBank<'_> {
800 fn validate(&self) -> Result<(), String> {
801 if self.expert_count == 0 {
802 return Err("E4M3 expert bank is empty".to_string());
803 }
804 let code_stride = self
805 .out_features
806 .checked_mul(self.in_features)
807 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
808 let code_count = self
809 .expert_count
810 .checked_mul(code_stride)
811 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
812 if self.codes.len() != code_count {
813 return Err(format!(
814 "E4M3 expert code count {} != {}x{} ({code_count})",
815 self.codes.len(),
816 self.expert_count,
817 code_stride,
818 ));
819 }
820 let scale_stride =
821 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
822 let scale_count = self
823 .expert_count
824 .checked_mul(scale_stride)
825 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
826 if self.scales.len() != scale_count {
827 return Err(format!(
828 "E4M3 expert scale count {} != {}x{} ({scale_count})",
829 self.scales.len(),
830 self.expert_count,
831 scale_stride,
832 ));
833 }
834 if !self
835 .scales
836 .iter()
837 .all(|scale| scale.is_finite() && *scale > 0.0)
838 {
839 return Err(
840 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
841 );
842 }
843 Ok(())
844 }
845
846 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
847 if expert >= self.expert_count {
848 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
849 }
850 let code_stride = self.out_features * self.in_features;
851 let scale_stride =
852 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
853 Ok(E4m3BlockMatrix {
854 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
855 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
856 out_features: self.out_features,
857 in_features: self.in_features,
858 })
859 }
860}
861
862pub struct ColumnParallelResult {
863 pub gathered: Vec<f32>,
864 pub rank_outputs: Vec<Vec<f32>>,
865}
866
867pub struct RowParallelResult {
868 pub reduced: Vec<f32>,
869 pub rank_partials: Vec<Vec<f32>>,
870}
871
872#[derive(Clone, Copy)]
873pub struct Bf16Matrix<'a> {
874 pub bytes: &'a [u8],
875 pub out_features: usize,
876 pub in_features: usize,
877}
878
879impl Bf16Matrix<'_> {
880 pub fn validate(&self) -> Result<(), String> {
881 if self.out_features == 0 || self.in_features == 0 {
882 return Err("BF16 matrix dimensions must be nonzero".into());
883 }
884 let expected = self
885 .out_features
886 .checked_mul(self.in_features)
887 .and_then(|values| values.checked_mul(2))
888 .ok_or("BF16 matrix byte count overflow")?;
889 if self.bytes.len() != expected {
890 return Err(format!(
891 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
892 self.bytes.len(),
893 self.out_features,
894 self.in_features,
895 ));
896 }
897 Ok(())
898 }
899}
900
901struct ResidentE4m3Rank {
902 codes: CudaSlice<u8>,
903 scales: CudaSlice<f32>,
904 out_features: usize,
905 in_features: usize,
906}
907
908enum ResidentBf16Weight {
909 Bf16(CudaSlice<u8>),
910 F32(CudaSlice<f32>),
911}
912
913impl ResidentBf16Weight {
914 fn ordinal(&self) -> usize {
915 match self {
916 Self::Bf16(bytes) => bytes.ordinal(),
917 Self::F32(values) => values.ordinal(),
918 }
919 }
920}
921
922struct ResidentBf16Rank {
923 weight: ResidentBf16Weight,
924 out_features: usize,
925 in_features: usize,
926}
927
928pub struct ResidentColumnParallel {
929 ranks: Vec<ResidentE4m3Rank>,
930 out_features: usize,
931 in_features: usize,
932}
933
934pub struct ResidentRowParallel {
935 ranks: Vec<ResidentE4m3Rank>,
936 out_features: usize,
937 in_features: usize,
938}
939
940pub struct ResidentBf16ColumnParallel {
941 ranks: Vec<ResidentBf16Rank>,
942 out_features: usize,
943 in_features: usize,
944 canonical_chunk_rows: Option<usize>,
945}
946
947pub struct ResidentBf16RowParallel {
948 ranks: Vec<ResidentBf16Rank>,
949 out_features: usize,
950 in_features: usize,
951}
952
953pub struct ResidentStepBf16RowParallel {
954 ranks: Vec<Vec<ResidentBf16Rank>>,
955 out_features: usize,
956 in_features: usize,
957 canonical_chunk_cols: usize,
958}
959
960pub struct ResidentSigmoidTopKRouter {
962 weight: CudaSlice<f32>,
963 correction_bias: CudaSlice<f32>,
964 active: CudaSlice<u8>,
965 root_device: usize,
966 input_width: usize,
967 expert_count: usize,
968 experts_per_token: usize,
969 active_count: usize,
970 scaling_factor: f32,
971 route_norm: bool,
972}
973
974pub struct SigmoidTopKHostOutput {
975 pub logits: Vec<f32>,
976 pub selected: Vec<u32>,
977 pub weights: Vec<f32>,
978}
979
980pub struct ResidentReplicatedBf16SwiGlu {
982 gate: Vec<ResidentBf16Rank>,
983 up: Vec<ResidentBf16Rank>,
984 down: Vec<ResidentBf16Rank>,
985 input_width: usize,
986 intermediate_width: usize,
987}
988
989pub struct ResidentReplicatedDeviceRows {
994 ranks: Vec<CudaSlice<f32>>,
995 tokens: usize,
996 width: usize,
997}
998
999impl ResidentReplicatedDeviceRows {
1000 pub fn tokens(&self) -> usize {
1001 self.tokens
1002 }
1003
1004 pub fn width(&self) -> usize {
1005 self.width
1006 }
1007
1008 pub fn ranks(&self) -> usize {
1009 self.ranks.len()
1010 }
1011}
1012
1013pub fn moe_residual_host(
1015 residual: &[f32],
1016 routed: &[f32],
1017 shared: &[f32],
1018) -> Result<Vec<f32>, String> {
1019 if residual.len() != routed.len() || residual.len() != shared.len() {
1020 return Err(format!(
1021 "MoE residual lengths residual={} routed={} shared={}",
1022 residual.len(),
1023 routed.len(),
1024 shared.len()
1025 ));
1026 }
1027 let ffn = routed
1028 .iter()
1029 .zip(shared)
1030 .map(|(&routed, &shared)| routed + shared)
1031 .collect::<Vec<_>>();
1032 Ok(residual
1033 .iter()
1034 .zip(ffn)
1035 .map(|(&residual, ffn)| residual + ffn)
1036 .collect())
1037}
1038
1039pub use memra_kv::{
1040 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1041};
1042
1043pub struct ResidentTpExpert {
1049 gate: ResidentColumnParallel,
1050 up: ResidentColumnParallel,
1051 down: ResidentRowParallel,
1052 input_width: usize,
1053 expert_width: usize,
1054}
1055
1056struct ResidentE4m3ExpertBankRank {
1057 codes: CudaSlice<u8>,
1058 scales: CudaSlice<f32>,
1059 expert_range: Range<usize>,
1060 out_features: usize,
1061 in_features: usize,
1062 code_stride: usize,
1063 scale_stride: usize,
1064 k_blocks: Option<usize>,
1067}
1068
1069struct PackedE4m3ExpertBankRank {
1070 codes: Vec<u8>,
1071 scales: Vec<f32>,
1072 expert_range: Range<usize>,
1073 out_features: usize,
1074 in_features: usize,
1075 code_stride: usize,
1076 scale_stride: usize,
1077 k_blocks: Option<usize>,
1078}
1079
1080struct ResidentEpRank {
1081 gate: ResidentE4m3ExpertBankRank,
1082 up: ResidentE4m3ExpertBankRank,
1083 down: ResidentE4m3ExpertBankRank,
1084}
1085
1086pub struct ResidentExpertParallel {
1093 ranks: Vec<ResidentEpRank>,
1094 expert_count: usize,
1095 input_width: usize,
1096 expert_width: usize,
1097}
1098
1099pub struct StepGroupedFp8ProjectionOutput {
1104 pub gate: Vec<f32>,
1105 pub up: Vec<f32>,
1106 pub down: Vec<f32>,
1107}
1108
1109pub struct PreparedStepGroupedFp8Gate {
1114 device: usize,
1115 gate: ResidentE4m3ExpertBankRank,
1116 up: ResidentE4m3ExpertBankRank,
1117 down: ResidentE4m3ExpertBankRank,
1118 input: CudaSlice<f32>,
1119 route_csr: DeviceExpertCsr,
1120 down_csr: DeviceExpertCsr,
1121 gate_workspace: Fp8GroupedWorkspace,
1122 up_workspace: Fp8GroupedWorkspace,
1123 down_workspace: Fp8GroupedWorkspace,
1124 activation: CudaSlice<f32>,
1125 activation_limit: Option<f32>,
1126 tokens: usize,
1127 pairs: usize,
1128}
1129
1130impl PreparedStepGroupedFp8Gate {
1131 pub fn tokens(&self) -> usize {
1132 self.tokens
1133 }
1134
1135 pub fn pairs(&self) -> usize {
1136 self.pairs
1137 }
1138}
1139
1140struct PreparedStepGroupedExpertOwner {
1141 rank: usize,
1142 global_pairs: Vec<usize>,
1143 route_csr: DeviceExpertCsr,
1144 down_csr: DeviceExpertCsr,
1145 gate_workspace: Fp8GroupedWorkspace,
1146 up_workspace: Fp8GroupedWorkspace,
1147 down_workspace: Fp8GroupedWorkspace,
1148 activation: CudaSlice<f32>,
1149}
1150
1151struct StepGroupedExpertOwnerSchedule {
1152 global_pairs: Vec<usize>,
1153 route_csr: ExpertCsr,
1154 down_csr: ExpertCsr,
1155}
1156
1157pub struct PreparedStepGroupedExpertParallelGate {
1163 rank_inputs: Vec<CudaSlice<f32>>,
1164 owners: Vec<PreparedStepGroupedExpertOwner>,
1165 activation_limit: Option<f32>,
1166 tokens: usize,
1167 pairs: usize,
1168 max_tokens: usize,
1169 max_pairs: usize,
1170 input_width: usize,
1171 expert_width: usize,
1172 generation: u64,
1173 executed_generation: Option<u64>,
1174 ready: bool,
1175}
1176
1177impl PreparedStepGroupedExpertParallelGate {
1178 pub fn tokens(&self) -> usize {
1179 self.tokens
1180 }
1181
1182 pub fn pairs(&self) -> usize {
1183 self.pairs
1184 }
1185
1186 pub fn max_tokens(&self) -> usize {
1187 self.max_tokens
1188 }
1189
1190 pub fn input_width(&self) -> usize {
1191 self.input_width
1192 }
1193
1194 pub fn expert_width(&self) -> usize {
1195 self.expert_width
1196 }
1197
1198 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1199 validate_step_expert_activation_limit(limit)?;
1200 self.activation_limit = limit;
1201 self.executed_generation = None;
1202 Ok(())
1203 }
1204
1205 pub fn active_owners(&self) -> usize {
1206 self.owners
1207 .iter()
1208 .filter(|owner| !owner.global_pairs.is_empty())
1209 .count()
1210 }
1211
1212 pub fn owner_pair_counts(&self) -> Vec<usize> {
1213 self.owners
1214 .iter()
1215 .map(|owner| owner.global_pairs.len())
1216 .collect()
1217 }
1218
1219 pub fn generation(&self) -> u64 {
1220 self.generation
1221 }
1222}
1223
1224struct PreparedPeerWeightedRouteOwner {
1225 token_rows: CudaSlice<i32>,
1226 slots: CudaSlice<i32>,
1227 weights: CudaSlice<f32>,
1228 active_pairs: usize,
1229}
1230
1231pub struct PreparedPeerWeightedRouteCombine {
1237 root_device: usize,
1238 owners: Vec<PreparedPeerWeightedRouteOwner>,
1239 peer_staging: CudaSlice<f32>,
1240 slots: CudaSlice<f32>,
1241 weights: CudaSlice<f32>,
1242 output: CudaSlice<f32>,
1243 peer_devices: Vec<usize>,
1244 peer_outputs: Vec<CudaSlice<f32>>,
1245 width: usize,
1246 experts_per_token: usize,
1247 max_tokens: usize,
1248 max_pairs: usize,
1249 tokens: usize,
1250 pairs: usize,
1251 projection_generation: u64,
1252 output_generation: Option<u64>,
1253 broadcast_generation: Option<u64>,
1254 ready: bool,
1255}
1256
1257impl PreparedPeerWeightedRouteCombine {
1258 pub fn tokens(&self) -> usize {
1259 self.tokens
1260 }
1261
1262 pub fn pairs(&self) -> usize {
1263 self.pairs
1264 }
1265
1266 pub fn owner_pair_counts(&self) -> Vec<usize> {
1267 self.owners.iter().map(|owner| owner.active_pairs).collect()
1268 }
1269
1270 pub fn distributed_ranks(&self) -> usize {
1271 1 + self.peer_outputs.len()
1272 }
1273}
1274
1275struct ResidentTpExpertBank {
1276 gate: Vec<ResidentE4m3ExpertBankRank>,
1277 up: Vec<ResidentE4m3ExpertBankRank>,
1278 down: Vec<ResidentE4m3ExpertBankRank>,
1279 expert_count: usize,
1280 input_width: usize,
1281 expert_width: usize,
1282}
1283
1284pub struct ResidentTensorParallel {
1290 bank: ResidentTpExpertBank,
1291}
1292
1293pub struct TpE4m3HostBounce {
1299 devices: Vec<usize>,
1300 ranks: Vec<Engine>,
1301 native_p2p: bool,
1302 ep_device_arithmetic: bool,
1303 bulk_p2p: bool,
1304 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1307}
1308
1309pub enum StepTpGateShards<'a> {
1318 F32(&'a [crate::CudaSlice<f32>]),
1319 Bf16(&'a [crate::CudaSlice<u8>]),
1320}
1321
1322pub struct StepTpDecodeV2Ws {
1323 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1327 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1328 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1329 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1330 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1331 pub(crate) tcol_cap: usize,
1332 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1339 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1340 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1341 pub(crate) fa2_cap: usize,
1342 tcol_gated: Vec<CudaSlice<f32>>,
1343 tcol_opart: Vec<CudaSlice<f32>>,
1344 tcol_opeer: Option<CudaSlice<f32>>,
1345 tcol_omix: Option<CudaSlice<f32>>,
1346 tcol_ocap: usize,
1347 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1350 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1351 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1352 pub(crate) q: Vec<CudaSlice<f32>>,
1353 pub(crate) k: Vec<CudaSlice<f32>>,
1354 pub(crate) pos: Vec<CudaSlice<i32>>,
1355 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1357 pub(crate) gate: Vec<CudaSlice<f32>>,
1358 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1359 pub(crate) gated: Vec<CudaSlice<f32>>,
1360 o_partials: Vec<Vec<CudaSlice<f32>>>,
1362 ev_rank: Vec<CudaEvent>,
1364 peer_partial: CudaSlice<f32>,
1366 reduce_a: CudaSlice<f32>,
1367 reduce_b: CudaSlice<f32>,
1368 zeros: CudaSlice<f32>,
1370 pub(crate) k_shadow: CudaSlice<f32>,
1371 pub(crate) v_shadow: CudaSlice<f32>,
1372 ev_refresh: CudaEvent,
1373 ev_oproj: CudaEvent,
1374 gate_e: CudaSlice<f32>,
1376 pub(crate) h_stage: Option<CudaSlice<f32>>,
1379 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1380 attn_in: Vec<CudaSlice<f32>>,
1384 raw_h_stage: u64,
1386 raw_pos_stage: u64,
1387 raw_attn_in: Vec<u64>,
1388 raw_pos: Vec<u64>,
1389 raw_o_partial1: u64,
1390 raw_peer_partial: u64,
1391 raw_k1: u64,
1392 raw_v1: u64,
1393 raw_k_shadow: u64,
1394 raw_v_shadow: u64,
1395 raw_mixed_stage_e: u64,
1399 raw_reduce_a: u64,
1400 raw_shadow_stage_e: (u64, u64),
1401 ev_entry: CudaEvent,
1402 e_device: usize,
1403 local_q_dim: usize,
1405 local_kv_dim: usize,
1406 heads: usize,
1407 pub(crate) o_out: usize,
1408 o_block_cols: usize,
1409 blocks_per_rank: usize,
1410}
1411
1412impl TpE4m3HostBounce {
1413 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1414 Self::new_inner(devices, false, false, false, false)
1415 }
1416
1417 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1418 Self::new_inner(devices, false, true, false, false)
1419 }
1420
1421 pub fn new_native_p2p_device_arithmetic(
1422 devices: &[usize],
1423 ) -> Result<Self, Box<dyn std::error::Error>> {
1424 Self::new_inner(devices, false, true, true, false)
1425 }
1426
1427 pub(crate) fn new_configured(
1428 devices: &[usize],
1429 native_p2p: bool,
1430 ep_device_arithmetic: bool,
1431 bulk_p2p: bool,
1432 ) -> Result<Self, Box<dyn std::error::Error>> {
1433 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1434 }
1435
1436 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1441 Self::new_inner(&[device], true, false, false, false)
1442 }
1443
1444 fn new_inner(
1445 devices: &[usize],
1446 allow_single_rank: bool,
1447 native_p2p: bool,
1448 ep_device_arithmetic: bool,
1449 bulk_p2p: bool,
1450 ) -> Result<Self, Box<dyn std::error::Error>> {
1451 if ep_device_arithmetic && !native_p2p {
1452 return Err("device-resident EP arithmetic requires native P2P".into());
1453 }
1454 if bulk_p2p && !native_p2p {
1455 return Err("bulk TP transport requires native P2P".into());
1456 }
1457 let minimum = if allow_single_rank { 1 } else { 2 };
1458 if !(minimum..=8).contains(&devices.len()) {
1459 return Err(format!(
1460 "TP reference requires {minimum}..=8 devices, got {}",
1461 devices.len()
1462 )
1463 .into());
1464 }
1465 let mut unique = devices.to_vec();
1466 unique.sort_unstable();
1467 unique.dedup();
1468 if unique.len() != devices.len() {
1469 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1470 }
1471 let ranks = devices
1472 .iter()
1473 .map(|&device| Engine::new(device))
1474 .collect::<Result<Vec<_>, _>>()?;
1475 if native_p2p {
1476 configure_native_p2p(&ranks, devices)?;
1477 }
1478 if allow_single_rank {
1479 eprintln!(
1480 "[tp] canonical oracle transport=local device={} performance_claim=false",
1481 devices[0]
1482 );
1483 } else if native_p2p {
1484 if ep_device_arithmetic {
1485 eprintln!(
1486 "[tp] correctness transport=native-p2p devices={devices:?} \
1487 native_p2p=true activation=device-host-exact \
1488 accumulation=device-host-exact output=root-readback \
1489 bulk_p2p={bulk_p2p} performance_claim=false"
1490 );
1491 } else {
1492 eprintln!(
1493 "[tp] correctness transport=native-p2p devices={devices:?} \
1494 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1495 performance_claim=false"
1496 );
1497 }
1498 } else {
1499 eprintln!(
1500 "[tp] correctness transport=host-bounce devices={devices:?} \
1501 native_p2p=false performance_claim=false"
1502 );
1503 }
1504 Ok(Self {
1505 devices: devices.to_vec(),
1506 ranks,
1507 native_p2p,
1508 ep_device_arithmetic,
1509 bulk_p2p,
1510 decode_v2: std::sync::Mutex::new(Vec::new()),
1511 })
1512 }
1513
1514 pub fn devices(&self) -> &[usize] {
1515 &self.devices
1516 }
1517
1518 pub fn native_p2p(&self) -> bool {
1519 self.native_p2p
1520 }
1521
1522 pub fn bulk_p2p(&self) -> bool {
1523 self.bulk_p2p
1524 }
1525
1526 pub fn expert_activation_label(&self) -> &'static str {
1527 if self.ep_device_arithmetic {
1528 "device-host-exact"
1529 } else {
1530 "host-canonical"
1531 }
1532 }
1533
1534 pub fn expert_accumulation_label(&self) -> &'static str {
1535 self.expert_activation_label()
1536 }
1537
1538 pub fn expert_output_label(&self) -> &'static str {
1539 if self.ep_device_arithmetic {
1540 "root-readback"
1541 } else {
1542 "host-accumulated"
1543 }
1544 }
1545
1546 pub fn transport_label(&self) -> &'static str {
1547 if self.devices.len() == 1 {
1548 "local"
1549 } else if self.native_p2p {
1550 "native-p2p"
1551 } else {
1552 "host-bounce"
1553 }
1554 }
1555
1556 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1557 self.ranks
1558 .iter()
1559 .map(|rank| rank.ctx().name().map_err(Into::into))
1560 .collect()
1561 }
1562
1563 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1569 self.ranks.get(rank)
1570 }
1571
1572 pub fn allocate_tp_kv_cache(
1573 &self,
1574 kv_dim_k: usize,
1575 kv_dim_v: usize,
1576 capacity: usize,
1577 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1578 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1579 }
1580
1581 pub fn allocate_tp_swa_kv_cache(
1582 &self,
1583 kv_dim_k: usize,
1584 kv_dim_v: usize,
1585 capacity: usize,
1586 window: usize,
1587 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1588 if window == 0 {
1589 return Err("TP SWA KV window must be nonzero".into());
1590 }
1591 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1592 }
1593
1594 fn allocate_tp_kv_cache_inner(
1595 &self,
1596 kv_dim_k: usize,
1597 kv_dim_v: usize,
1598 capacity: usize,
1599 window: Option<usize>,
1600 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1601 if capacity == 0 || capacity > i32::MAX as usize {
1602 return Err(
1603 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1604 );
1605 }
1606 let tp = self.ranks.len();
1607 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1608 let physical_rows = window
1609 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1610 .unwrap_or(capacity);
1611 let k_plane_bytes = physical_rows
1612 .checked_mul(shape.k_token_bytes)
1613 .and_then(|bytes| bytes.checked_add(8))
1614 .ok_or("TP KV K plane-byte overflow")?;
1615 let v_plane_bytes = physical_rows
1616 .checked_mul(shape.v_token_bytes)
1617 .and_then(|bytes| bytes.checked_add(8))
1618 .ok_or("TP KV V plane-byte overflow")?;
1619 let mut ranks = Vec::with_capacity(tp);
1620 for engine in &self.ranks {
1621 let _main = engine.gpu.enter_main()?;
1622 ranks.push(ResidentTpKvCacheRank::new(
1623 engine.alloc_u8(k_plane_bytes)?,
1624 engine.alloc_u8(v_plane_bytes)?,
1625 engine.htod_i32(&[0])?,
1626 ));
1627 }
1628 Ok(match window {
1629 Some(window) => ResidentTpKvCache::new_swa(
1630 ranks,
1631 shape.kv_dim_k,
1632 shape.kv_dim_v,
1633 shape.k_token_bytes,
1634 shape.v_token_bytes,
1635 capacity,
1636 window,
1637 ),
1638 None => ResidentTpKvCache::new(
1639 ranks,
1640 shape.kv_dim_k,
1641 shape.kv_dim_v,
1642 shape.k_token_bytes,
1643 shape.v_token_bytes,
1644 capacity,
1645 ),
1646 })
1647 }
1648
1649 pub fn grow_tp_kv_cache(
1650 &self,
1651 source: &ResidentTpKvCache,
1652 target_capacity: usize,
1653 rows: usize,
1654 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1655 self.validate_tp_kv_cache(source)?;
1656 let plan = source.prepare_grow(target_capacity, rows)?;
1657 let ranks = self.ranks.len();
1658 let global_k = source
1659 .kv_dim_k()
1660 .checked_mul(ranks)
1661 .ok_or("TP KV grow global K dimension overflow")?;
1662 let global_v = source
1663 .kv_dim_v()
1664 .checked_mul(ranks)
1665 .ok_or("TP KV grow global V dimension overflow")?;
1666 let mut target = match source.ring_window() {
1667 Some(window) => {
1668 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1669 }
1670 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1671 };
1672 self.validate_tp_kv_cache(&target)?;
1673
1674 for (rank, engine) in self.ranks.iter().enumerate() {
1675 let _main = engine.gpu.enter_main()?;
1676 let src = source
1677 .rank(rank)
1678 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1679 let dst = target
1680 .rank_mut(rank)
1681 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1682 if plan.k_bytes() > 0 {
1683 engine.copy_u8_range_into(
1684 dst.k_mut(),
1685 0,
1686 src.k(),
1687 plan.source_row() * source.k_tok_bytes(),
1688 plan.k_bytes(),
1689 )?;
1690 }
1691 if plan.v_bytes() > 0 {
1692 engine.copy_u8_range_into(
1693 dst.v_mut(),
1694 0,
1695 src.v(),
1696 plan.source_row() * source.v_tok_bytes(),
1697 plan.v_bytes(),
1698 )?;
1699 }
1700 }
1701 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1702
1703 for engine in &self.ranks {
1706 let _main = engine.gpu.enter_main()?;
1707 engine.stream().synchronize()?;
1708 }
1709 let physical_copy_rows = plan.copy_rows();
1710 target.publish_grow(plan)?;
1711 eprintln!(
1712 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1713 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1714 rank_streams_synchronized=true generation_preserved=true",
1715 rows,
1716 source.capacity(),
1717 target_capacity,
1718 ranks,
1719 physical_copy_rows,
1720 source.ring_window(),
1721 );
1722 Ok(target)
1723 }
1724
1725 pub fn hydrate_tp_kv_cache(
1726 &self,
1727 cache: &mut ResidentTpKvCache,
1728 rows: usize,
1729 k_rows: &[u8],
1730 v_rows: &[u8],
1731 ) -> Result<(), Box<dyn std::error::Error>> {
1732 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1733 }
1734
1735 pub fn hydrate_tp_kv_cache_from(
1736 &self,
1737 cache: &mut ResidentTpKvCache,
1738 logical_len: usize,
1739 resident_start: usize,
1740 k_rows: &[u8],
1741 v_rows: &[u8],
1742 ) -> Result<(), Box<dyn std::error::Error>> {
1743 self.validate_tp_kv_cache(cache)?;
1744 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1745 return Err(format!(
1746 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1747 cache.committed_len(),
1748 cache.staged_len()
1749 )
1750 .into());
1751 }
1752 if resident_start > logical_len || logical_len > cache.capacity() {
1753 return Err(format!(
1754 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1755 cache.capacity(),
1756 )
1757 .into());
1758 }
1759 let rows = logical_len - resident_start;
1760 if rows > cache.physical_capacity() {
1761 return Err(format!(
1762 "TP KV hydration rows {rows} exceed physical capacity {}",
1763 cache.physical_capacity()
1764 )
1765 .into());
1766 }
1767 for rank in 0..self.ranks.len() {
1768 let k_rank =
1769 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1770 let v_rank =
1771 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1772 let engine = &self.ranks[rank];
1773 let _main = engine.gpu.enter_main()?;
1774 let rank_cache = cache
1775 .rank_mut(rank)
1776 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1777 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1778 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1779 }
1780 cache.publish_hydration(logical_len, resident_start)?;
1781 Ok(())
1782 }
1783
1784 pub fn append_tp_kv_transaction(
1785 &self,
1786 cache: &mut ResidentTpKvCache,
1787 transaction: TpKvTransaction,
1788 k_shards: &[CudaSlice<f32>],
1789 v_shards: &[CudaSlice<f32>],
1790 rows: usize,
1791 ) -> Result<(), Box<dyn std::error::Error>> {
1792 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1793 }
1794
1795 #[allow(clippy::too_many_arguments)]
1800 pub fn append_tp_kv_transaction_inner(
1801 &self,
1802 cache: &mut ResidentTpKvCache,
1803 transaction: TpKvTransaction,
1804 k_shards: &[CudaSlice<f32>],
1805 v_shards: &[CudaSlice<f32>],
1806 rows: usize,
1807 external_rank_appends: bool,
1808 ) -> Result<(), Box<dyn std::error::Error>> {
1809 self.validate_tp_kv_cache(cache)?;
1810 let plan = cache.prepare_append(transaction, rows)?;
1811 let target = plan.target();
1812 let expected_k = rows
1813 .checked_mul(cache.kv_dim_k())
1814 .ok_or("TP KV K append size overflow")?;
1815 let expected_v = rows
1816 .checked_mul(cache.kv_dim_v())
1817 .ok_or("TP KV V append size overflow")?;
1818 if !external_rank_appends
1821 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1822 {
1823 return Err(format!(
1824 "TP KV append shard counts k={} v={} != ranks {}",
1825 k_shards.len(),
1826 v_shards.len(),
1827 self.ranks.len()
1828 )
1829 .into());
1830 }
1831 let kv_dim_k = cache.kv_dim_k();
1832 let kv_dim_v = cache.kv_dim_v();
1833 let k_tok_bytes = cache.k_tok_bytes();
1834 let v_tok_bytes = cache.v_tok_bytes();
1835 if let Some(KvRingAppend::Rebase {
1836 src_row,
1837 keep_rows,
1838 new_base,
1839 ..
1840 }) = plan.ring_append()
1841 {
1842 for rank in 0..self.ranks.len() {
1843 let engine = &self.ranks[rank];
1844 let _main = engine.gpu.enter_main()?;
1845 let rank_cache = cache
1846 .rank_mut(rank)
1847 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1848 if keep_rows > 0 {
1849 let k_len = keep_rows
1850 .checked_mul(k_tok_bytes)
1851 .ok_or("TP KV K rebase-byte overflow")?;
1852 let v_len = keep_rows
1853 .checked_mul(v_tok_bytes)
1854 .ok_or("TP KV V rebase-byte overflow")?;
1855 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1856 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
1857 engine.copy_u8_range_into(
1858 &mut k_tmp,
1859 0,
1860 rank_cache.k(),
1861 src_row * k_tok_bytes,
1862 k_len,
1863 )?;
1864 engine.copy_u8_range_into(
1865 &mut v_tmp,
1866 0,
1867 rank_cache.v(),
1868 src_row * v_tok_bytes,
1869 v_len,
1870 )?;
1871 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
1872 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
1873 }
1874 if rank_cache.base_d().is_some() {
1878 let value = new_base as i32;
1879 let rank_cache = cache
1880 .rank_mut(rank)
1881 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1882 if let Some(base_d) = rank_cache.base_d_mut() {
1883 engine.set_i32_one(base_d, value)?;
1884 }
1885 }
1886 }
1887 }
1888 cache.publish_append_rebase(plan)?;
1889 let write_row = plan.write_row();
1890 for rank in 0..self.ranks.len() {
1891 if external_rank_appends {
1892 break;
1893 }
1894 let engine = &self.ranks[rank];
1895 let _main = engine.gpu.enter_main()?;
1896 if k_shards[rank].len() != expected_k
1897 || v_shards[rank].len() != expected_v
1898 || k_shards[rank].ordinal() != engine.ctx().ordinal()
1899 || v_shards[rank].ordinal() != engine.ctx().ordinal()
1900 {
1901 return Err(format!(
1902 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
1903 != expected {expected_k}/{expected_v} on device {}",
1904 k_shards[rank].len(),
1905 k_shards[rank].ordinal(),
1906 v_shards[rank].len(),
1907 v_shards[rank].ordinal(),
1908 engine.ctx().ordinal(),
1909 )
1910 .into());
1911 }
1912 let rank_cache = cache
1913 .rank_mut(rank)
1914 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1915 let (rank_k, rank_v) = rank_cache.planes_mut();
1916 engine.append_kv_quantized_rows(
1917 &k_shards[rank],
1918 &v_shards[rank],
1919 rank_k,
1920 rank_v,
1921 write_row,
1922 rows,
1923 kv_dim_k,
1924 kv_dim_v,
1925 k_tok_bytes,
1926 v_tok_bytes,
1927 Engine::kv_fp8_on(),
1928 )?;
1929 }
1930 if !external_rank_appends {
1931 self.set_tp_kv_len_mirrors(cache, target)?;
1934 }
1935 cache.publish_append_plan(plan)?;
1936 Ok(())
1937 }
1938
1939 pub fn commit_tp_kv_transaction(
1940 &self,
1941 cache: &mut ResidentTpKvCache,
1942 transaction: TpKvTransaction,
1943 accepted_rows: usize,
1944 ) -> Result<(), Box<dyn std::error::Error>> {
1945 self.validate_tp_kv_cache(cache)?;
1946 let target = cache.commit_target(transaction, accepted_rows)?;
1947 self.set_tp_kv_len_mirrors(cache, target)?;
1948 cache.publish_finalize(transaction, target)?;
1949 Ok(())
1950 }
1951
1952 pub fn commit_tp_kv_transaction_external(
1958 &self,
1959 cache: &mut ResidentTpKvCache,
1960 transaction: TpKvTransaction,
1961 accepted_rows: usize,
1962 ) -> Result<(), Box<dyn std::error::Error>> {
1963 self.validate_tp_kv_cache(cache)?;
1964 let target = cache.commit_target(transaction, accepted_rows)?;
1965 cache.publish_finalize(transaction, target)?;
1966 Ok(())
1967 }
1968
1969 pub fn rollback_tp_kv_transaction(
1970 &self,
1971 cache: &mut ResidentTpKvCache,
1972 transaction: TpKvTransaction,
1973 ) -> Result<(), Box<dyn std::error::Error>> {
1974 self.validate_tp_kv_cache(cache)?;
1975 cache.validate_transaction(transaction)?;
1976 let target = transaction.base_len();
1977 self.set_tp_kv_len_mirrors(cache, target)?;
1978 cache.publish_finalize(transaction, target)?;
1979 Ok(())
1980 }
1981
1982 pub fn tp_kv_device_lengths(
1983 &self,
1984 cache: &ResidentTpKvCache,
1985 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
1986 self.validate_tp_kv_cache(cache)?;
1987 let mut lengths = Vec::with_capacity(self.ranks.len());
1988 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
1989 let _main = engine.gpu.enter_main()?;
1990 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
1991 }
1992 Ok(lengths)
1993 }
1994
1995 fn set_tp_kv_len_mirrors(
1996 &self,
1997 cache: &mut ResidentTpKvCache,
1998 len: usize,
1999 ) -> Result<(), Box<dyn std::error::Error>> {
2000 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
2001 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
2002 let _main = engine.gpu.enter_main()?;
2003 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
2004 }
2005 Ok(())
2006 }
2007
2008 fn validate_tp_kv_cache(
2009 &self,
2010 cache: &ResidentTpKvCache,
2011 ) -> Result<(), Box<dyn std::error::Error>> {
2012 if cache.ranks_len() != self.ranks.len() {
2013 return Err(format!(
2014 "TP KV cache ranks {} != runtime ranks {}",
2015 cache.ranks_len(),
2016 self.ranks.len()
2017 )
2018 .into());
2019 }
2020 let expected_k = cache
2021 .physical_capacity()
2022 .checked_mul(cache.k_tok_bytes())
2023 .and_then(|bytes| bytes.checked_add(8))
2024 .ok_or("TP KV K plane validation overflow")?;
2025 let expected_v = cache
2026 .physical_capacity()
2027 .checked_mul(cache.v_tok_bytes())
2028 .and_then(|bytes| bytes.checked_add(8))
2029 .ok_or("TP KV V plane validation overflow")?;
2030 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2031 let device = engine.ctx().ordinal();
2032 if rank_cache.k().len() != expected_k
2033 || rank_cache.v().len() != expected_v
2034 || rank_cache.len_d().len() != 1
2035 || rank_cache.k().ordinal() != device
2036 || rank_cache.v().ordinal() != device
2037 || rank_cache.len_d().ordinal() != device
2038 {
2039 return Err(format!(
2040 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2041 )
2042 .into());
2043 }
2044 }
2045 Ok(())
2046 }
2047
2048 pub fn full(
2049 &self,
2050 matrix: E4m3BlockMatrix<'_>,
2051 activations: &[f32],
2052 tokens: usize,
2053 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2054 matrix.validate()?;
2055 validate_activations(activations, tokens, matrix.in_features)?;
2056 run_rank(&self.ranks[0], matrix, activations, tokens)
2057 }
2058
2059 pub fn column_parallel(
2063 &self,
2064 matrix: E4m3BlockMatrix<'_>,
2065 activations: &[f32],
2066 tokens: usize,
2067 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2068 matrix.validate()?;
2069 validate_activations(activations, tokens, matrix.in_features)?;
2070 let tp = self.ranks.len();
2071 if matrix.out_features % tp != 0 {
2072 return Err(format!(
2073 "column-parallel out_features {} is not divisible by TP={tp}",
2074 matrix.out_features
2075 )
2076 .into());
2077 }
2078 let local_out = matrix.out_features / tp;
2079 if local_out % FP8_BLOCK != 0 {
2080 return Err(format!(
2081 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2082 E4M3 scale block"
2083 )
2084 .into());
2085 }
2086
2087 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2088 let mut rank_outputs = Vec::with_capacity(tp);
2089 for (rank_index, rank) in self.ranks.iter().enumerate() {
2090 let shard = column_shard(matrix, tp, rank_index)?;
2091 let output = run_rank(rank, shard, activations, tokens)?;
2092 let row_start = rank_index * local_out;
2093 for token in 0..tokens {
2094 gathered[token * matrix.out_features + row_start
2095 ..token * matrix.out_features + row_start + local_out]
2096 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2097 }
2098 rank_outputs.push(output);
2099 }
2100 Ok(ColumnParallelResult {
2101 gathered,
2102 rank_outputs,
2103 })
2104 }
2105
2106 pub fn upload_column_parallel(
2107 &self,
2108 matrix: E4m3BlockMatrix<'_>,
2109 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2110 matrix.validate()?;
2111 let tp = self.ranks.len();
2112 validate_column_shape(matrix, tp)?;
2113 let mut ranks = Vec::with_capacity(tp);
2114 for (rank_index, engine) in self.ranks.iter().enumerate() {
2115 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2116 }
2117 Ok(ResidentColumnParallel {
2118 ranks,
2119 out_features: matrix.out_features,
2120 in_features: matrix.in_features,
2121 })
2122 }
2123
2124 pub fn column_parallel_resident(
2125 &self,
2126 matrix: &ResidentColumnParallel,
2127 activations: &[f32],
2128 tokens: usize,
2129 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2130 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2131 validate_activations(activations, tokens, matrix.in_features)?;
2132 let local_out = matrix.out_features / self.ranks.len();
2133 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2134 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2135 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2136 let output = run_resident_rank(engine, shard, activations, tokens)?;
2137 let row_start = rank_index * local_out;
2138 for token in 0..tokens {
2139 gathered[token * matrix.out_features + row_start
2140 ..token * matrix.out_features + row_start + local_out]
2141 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2142 }
2143 rank_outputs.push(output);
2144 }
2145 Ok(ColumnParallelResult {
2146 gathered,
2147 rank_outputs,
2148 })
2149 }
2150
2151 pub fn row_parallel(
2155 &self,
2156 matrix: E4m3BlockMatrix<'_>,
2157 activations: &[f32],
2158 tokens: usize,
2159 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2160 matrix.validate()?;
2161 validate_activations(activations, tokens, matrix.in_features)?;
2162 let tp = self.ranks.len();
2163 if matrix.in_features % tp != 0 {
2164 return Err(format!(
2165 "row-parallel in_features {} is not divisible by TP={tp}",
2166 matrix.in_features
2167 )
2168 .into());
2169 }
2170 let local_in = matrix.in_features / tp;
2171 if local_in % FP8_BLOCK != 0 {
2172 return Err(format!(
2173 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2174 E4M3 scale block"
2175 )
2176 .into());
2177 }
2178
2179 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2180 let mut rank_partials = Vec::with_capacity(tp);
2181 for (rank_index, rank) in self.ranks.iter().enumerate() {
2182 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2183 let local_activations =
2184 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2185 let shard = E4m3BlockMatrix {
2186 codes: &codes,
2187 scales: &scales,
2188 out_features: matrix.out_features,
2189 in_features: local_in,
2190 };
2191 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2192 for (sum, value) in reduced.iter_mut().zip(&partial) {
2193 *sum += *value;
2194 }
2195 rank_partials.push(partial);
2196 }
2197 Ok(RowParallelResult {
2198 reduced,
2199 rank_partials,
2200 })
2201 }
2202
2203 pub fn upload_row_parallel(
2204 &self,
2205 matrix: E4m3BlockMatrix<'_>,
2206 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2207 matrix.validate()?;
2208 let tp = self.ranks.len();
2209 validate_row_shape(matrix, tp)?;
2210 let local_in = matrix.in_features / tp;
2211 let mut ranks = Vec::with_capacity(tp);
2212 for (rank_index, engine) in self.ranks.iter().enumerate() {
2213 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2214 ranks.push(upload_rank(
2215 engine,
2216 E4m3BlockMatrix {
2217 codes: &codes,
2218 scales: &scales,
2219 out_features: matrix.out_features,
2220 in_features: local_in,
2221 },
2222 )?);
2223 }
2224 Ok(ResidentRowParallel {
2225 ranks,
2226 out_features: matrix.out_features,
2227 in_features: matrix.in_features,
2228 })
2229 }
2230
2231 pub fn row_parallel_resident(
2232 &self,
2233 matrix: &ResidentRowParallel,
2234 activations: &[f32],
2235 tokens: usize,
2236 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2237 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2238 validate_activations(activations, tokens, matrix.in_features)?;
2239 let tp = self.ranks.len();
2240 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2241 let mut rank_partials = Vec::with_capacity(tp);
2242 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2243 let local_activations =
2244 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2245 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2246 for (sum, value) in reduced.iter_mut().zip(&partial) {
2247 *sum += *value;
2248 }
2249 rank_partials.push(partial);
2250 }
2251 Ok(RowParallelResult {
2252 reduced,
2253 rank_partials,
2254 })
2255 }
2256
2257 pub fn upload_bf16_column_parallel(
2258 &self,
2259 matrix: Bf16Matrix<'_>,
2260 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2261 self.upload_bf16_column_parallel_inner(matrix, None, false)
2262 }
2263
2264 pub fn upload_step_bf16_column_parallel(
2266 &self,
2267 matrix: Bf16Matrix<'_>,
2268 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2269 self.upload_step_bf16_column_parallel_inner(matrix, false)
2270 }
2271
2272 pub fn upload_step_bf16_column_parallel_f32_mirror(
2277 &self,
2278 matrix: Bf16Matrix<'_>,
2279 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2280 self.upload_step_bf16_column_parallel_inner(matrix, true)
2281 }
2282
2283 fn upload_step_bf16_column_parallel_inner(
2284 &self,
2285 matrix: Bf16Matrix<'_>,
2286 f32_mirror: bool,
2287 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2288 let canonical_chunk_rows =
2289 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2290 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2291 }
2292
2293 fn upload_bf16_column_parallel_inner(
2294 &self,
2295 matrix: Bf16Matrix<'_>,
2296 canonical_chunk_rows: Option<usize>,
2297 f32_mirror: bool,
2298 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2299 matrix.validate()?;
2300 let tp = self.ranks.len();
2301 if matrix.out_features % tp != 0 {
2302 return Err(format!(
2303 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2304 matrix.out_features
2305 )
2306 .into());
2307 }
2308 let mut ranks = Vec::with_capacity(tp);
2309 for (rank, engine) in self.ranks.iter().enumerate() {
2310 ranks.push(upload_bf16_rank(
2311 engine,
2312 bf16_column_shard(matrix, tp, rank)?,
2313 f32_mirror,
2314 )?);
2315 }
2316 Ok(ResidentBf16ColumnParallel {
2317 ranks,
2318 out_features: matrix.out_features,
2319 in_features: matrix.in_features,
2320 canonical_chunk_rows,
2321 })
2322 }
2323
2324 pub fn bf16_column_parallel_resident(
2325 &self,
2326 matrix: &ResidentBf16ColumnParallel,
2327 activations: &[f32],
2328 tokens: usize,
2329 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2330 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2331 validate_activations(activations, tokens, matrix.in_features)?;
2332 let local_out = matrix.out_features / self.ranks.len();
2333 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2334 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2335 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2336 let output = run_resident_bf16_rank(
2337 engine,
2338 shard,
2339 activations,
2340 tokens,
2341 matrix.canonical_chunk_rows,
2342 )?;
2343 for token in 0..tokens {
2344 let src = &output[token * local_out..(token + 1) * local_out];
2345 let dst_start = token * matrix.out_features + rank * local_out;
2346 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2347 }
2348 rank_outputs.push(output);
2349 }
2350 Ok(ColumnParallelResult {
2351 gathered,
2352 rank_outputs,
2353 })
2354 }
2355
2356 pub fn bf16_column_parallel_resident_native(
2363 &self,
2364 matrix: &ResidentBf16ColumnParallel,
2365 activations: &[f32],
2366 tokens: usize,
2367 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2368 let rank_outputs =
2369 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2370 let local_out = matrix.out_features / self.ranks.len();
2371 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2372 }
2373
2374 pub fn bf16_column_parallel_resident_device_shards(
2381 &self,
2382 matrix: &ResidentBf16ColumnParallel,
2383 activations: &[f32],
2384 tokens: usize,
2385 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2386 if self.ranks.len() > 1 && !self.native_p2p {
2387 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2388 }
2389 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2390 validate_activations(activations, tokens, matrix.in_features)?;
2391
2392 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2393 let root_input = {
2394 let root = &self.ranks[0];
2395 let _main = root.gpu.enter_main()?;
2396 root.htod(activations)?
2397 };
2398 {
2404 let root = &self.ranks[0];
2405 let _main = root.gpu.enter_main()?;
2406 root.stream().synchronize()?;
2407 }
2408 rank_inputs.push(root_input);
2409 for engine in &self.ranks[1..] {
2410 let peer_input = {
2411 let _main = engine.gpu.enter_main()?;
2412 let mut peer_input = engine.uninit(activations.len())?;
2413 engine
2414 .stream()
2415 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2416 peer_input
2417 };
2418 rank_inputs.push(peer_input);
2419 }
2420
2421 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2422 for rank in 0..self.ranks.len() {
2423 rank_outputs.push(run_resident_bf16_rank_device(
2424 &self.ranks[rank],
2425 &matrix.ranks[rank],
2426 &rank_inputs[rank],
2427 tokens,
2428 matrix.canonical_chunk_rows,
2429 self.bulk_p2p,
2430 )?);
2431 }
2432 Ok(rank_outputs)
2433 }
2434
2435 pub fn allocate_replicated_device_rows(
2439 &self,
2440 tokens: usize,
2441 width: usize,
2442 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2443 if self.ranks.len() > 1 && !self.native_p2p {
2444 return Err("replicated device rows require native P2P ranks".into());
2445 }
2446 let values = tokens
2447 .checked_mul(width)
2448 .ok_or("replicated device row size overflow")?;
2449 let rank_lengths = vec![values; self.ranks.len()];
2450 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2451 let mut ranks = Vec::with_capacity(self.ranks.len());
2452 for engine in &self.ranks {
2453 let _main = engine.gpu.enter_main()?;
2454 ranks.push(engine.uninit(values)?);
2455 }
2456 Ok(ResidentReplicatedDeviceRows {
2457 ranks,
2458 tokens,
2459 width,
2460 })
2461 }
2462
2463 pub fn refresh_replicated_device_rows_from_root(
2465 &self,
2466 rows: &mut ResidentReplicatedDeviceRows,
2467 source: &CudaSlice<f32>,
2468 ) -> Result<(), Box<dyn std::error::Error>> {
2469 if self.ranks.len() > 1 && !self.native_p2p {
2470 return Err("replicated device rows require native P2P ranks".into());
2471 }
2472 validate_replicated_device_rows(&self.ranks, rows)?;
2473 let root = self
2474 .ranks
2475 .first()
2476 .ok_or("replicated rows have no root rank")?;
2477 let values = replicated_device_row_source_values(
2478 rows.tokens,
2479 rows.width,
2480 source.len(),
2481 source.ordinal(),
2482 root.ctx().ordinal(),
2483 )?;
2484 let (root_rows, peer_rows) = rows
2485 .ranks
2486 .split_first_mut()
2487 .ok_or("replicated rows have no root allocation")?;
2488 {
2489 let _main = root.gpu.enter_main()?;
2490 let mut destination = root_rows.slice_mut(0..values);
2491 root.stream()
2492 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2493 root.stream().synchronize()?;
2494 }
2495 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2496 let _main = engine.gpu.enter_main()?;
2497 let mut destination = peer_rows.slice_mut(0..values);
2498 engine
2499 .stream()
2500 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2501 }
2502 Ok(())
2503 }
2504
2505 pub fn upload_replicated_device_rows(
2507 &self,
2508 rows: &[f32],
2509 tokens: usize,
2510 width: usize,
2511 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2512 if self.ranks.len() > 1 && !self.native_p2p {
2513 return Err("replicated device rows require native P2P ranks".into());
2514 }
2515 validate_activations(rows, tokens, width)?;
2516 let root = self
2517 .ranks
2518 .first()
2519 .ok_or("replicated rows have no root rank")?;
2520 let root_rows = {
2521 let _main = root.gpu.enter_main()?;
2522 root.htod(rows)?
2523 };
2524 {
2525 let _main = root.gpu.enter_main()?;
2526 root.stream().synchronize()?;
2527 }
2528 let mut ranks = Vec::with_capacity(self.ranks.len());
2529 ranks.push(root_rows);
2530 for engine in self.ranks.iter().skip(1) {
2531 let _main = engine.gpu.enter_main()?;
2532 let mut peer_rows = engine.uninit(rows.len())?;
2533 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2534 ranks.push(peer_rows);
2535 }
2536 Ok(ResidentReplicatedDeviceRows {
2537 ranks,
2538 tokens,
2539 width,
2540 })
2541 }
2542
2543 pub fn bf16_column_parallel_resident_replicated_device_shards(
2545 &self,
2546 matrix: &ResidentBf16ColumnParallel,
2547 activations: &ResidentReplicatedDeviceRows,
2548 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2549 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2550 validate_replicated_device_rows(&self.ranks, activations)?;
2551 if activations.width != matrix.in_features {
2552 return Err(format!(
2553 "replicated BF16 column input width {} != matrix width {}",
2554 activations.width, matrix.in_features
2555 )
2556 .into());
2557 }
2558 let mut outputs = Vec::with_capacity(self.ranks.len());
2559 for rank in 0..self.ranks.len() {
2560 outputs.push(run_resident_bf16_rank_device(
2561 &self.ranks[rank],
2562 &matrix.ranks[rank],
2563 &activations.ranks[rank],
2564 activations.tokens,
2565 matrix.canonical_chunk_rows,
2566 self.bulk_p2p,
2567 )?);
2568 }
2569 Ok(outputs)
2570 }
2571
2572 #[allow(clippy::too_many_arguments)]
2574 pub fn upload_sigmoid_topk_router(
2575 &self,
2576 weight: Bf16Matrix<'_>,
2577 correction_bias: &[f32],
2578 active: Option<&[bool]>,
2579 experts_per_token: usize,
2580 scaling_factor: f32,
2581 route_norm: bool,
2582 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2583 weight.validate()?;
2584 if correction_bias.len() != weight.out_features
2585 || experts_per_token == 0
2586 || experts_per_token > weight.out_features
2587 || !correction_bias.iter().all(|value| value.is_finite())
2588 || !scaling_factor.is_finite()
2589 || scaling_factor <= 0.0
2590 {
2591 return Err(format!(
2592 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2593 weight.out_features,
2594 weight.in_features,
2595 correction_bias.len(),
2596 experts_per_token,
2597 )
2598 .into());
2599 }
2600 let active_row = active
2601 .map(|mask| {
2602 if mask.len() != weight.out_features {
2603 return Err(format!(
2604 "sigmoid router active mask {} != experts {}",
2605 mask.len(),
2606 weight.out_features
2607 ));
2608 }
2609 Ok(mask
2610 .iter()
2611 .map(|&enabled| u8::from(enabled))
2612 .collect::<Vec<_>>())
2613 })
2614 .transpose()?
2615 .unwrap_or_else(|| vec![1; weight.out_features]);
2616 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2617 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2618
2619 let root = self
2620 .ranks
2621 .first()
2622 .ok_or("sigmoid router runtime has no root rank")?;
2623 let _main = root.gpu.enter_main()?;
2624 let bf16 = root.htod_bytes(weight.bytes)?;
2625 let weight_f32 = root.bf16_to_f32(
2626 &bf16.slice(0..bf16.len()),
2627 weight.out_features * weight.in_features,
2628 )?;
2629 Ok(ResidentSigmoidTopKRouter {
2630 weight: weight_f32,
2631 correction_bias: root.htod(correction_bias)?,
2632 active: root.htod_bytes(&active_row)?,
2633 root_device: root.ctx().ordinal(),
2634 input_width: weight.in_features,
2635 expert_count: weight.out_features,
2636 experts_per_token,
2637 active_count,
2638 scaling_factor,
2639 route_norm,
2640 })
2641 }
2642
2643 pub fn sigmoid_topk_replicated_device_rows_host(
2648 &self,
2649 router: &ResidentSigmoidTopKRouter,
2650 input: &ResidentReplicatedDeviceRows,
2651 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2652 validate_replicated_device_rows(&self.ranks, input)?;
2653 if input.width != router.input_width {
2654 return Err(format!(
2655 "sigmoid router input width {} != resident width {}",
2656 input.width, router.input_width
2657 )
2658 .into());
2659 }
2660 let root = self
2661 .ranks
2662 .first()
2663 .ok_or("sigmoid router runtime has no root rank")?;
2664 let _main = root.gpu.enter_main()?;
2665 if root.ctx().ordinal() != router.root_device
2666 || router.weight.ordinal() != router.root_device
2667 || router.correction_bias.ordinal() != router.root_device
2668 || router.active.ordinal() != router.root_device
2669 {
2670 return Err("sigmoid router root residency changed".into());
2671 }
2672 let logits = root.router_gemv(
2673 &router.weight,
2674 &input.ranks[0],
2675 router.input_width,
2676 router.expert_count,
2677 input.tokens,
2678 )?;
2679 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2680 &logits,
2681 input.tokens,
2682 router.expert_count,
2683 router.experts_per_token,
2684 router.active_count,
2685 &router.correction_bias,
2686 &router.active,
2687 router.scaling_factor,
2688 router.route_norm,
2689 )?;
2690 Ok(SigmoidTopKHostOutput {
2691 logits: root.dtoh(&logits)?,
2692 selected,
2693 weights,
2694 })
2695 }
2696
2697 pub fn upload_replicated_bf16_swiglu(
2699 &self,
2700 gate: Bf16Matrix<'_>,
2701 up: Bf16Matrix<'_>,
2702 down: Bf16Matrix<'_>,
2703 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2704 gate.validate()?;
2705 up.validate()?;
2706 down.validate()?;
2707 if gate.in_features != up.in_features
2708 || gate.out_features != up.out_features
2709 || down.in_features != gate.out_features
2710 || down.out_features != gate.in_features
2711 {
2712 return Err(format!(
2713 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2714 gate.out_features,
2715 gate.in_features,
2716 up.out_features,
2717 up.in_features,
2718 down.out_features,
2719 down.in_features,
2720 )
2721 .into());
2722 }
2723 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2724 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2725 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2726 for engine in &self.ranks {
2727 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2728 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2729 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2730 }
2731 Ok(ResidentReplicatedBf16SwiGlu {
2732 gate: gate_ranks,
2733 up: up_ranks,
2734 down: down_ranks,
2735 input_width: gate.in_features,
2736 intermediate_width: gate.out_features,
2737 })
2738 }
2739
2740 pub fn replicated_bf16_swiglu_resident_device(
2742 &self,
2743 mlp: &ResidentReplicatedBf16SwiGlu,
2744 input: &ResidentReplicatedDeviceRows,
2745 activation_limit: Option<f32>,
2746 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2747 validate_step_expert_activation_limit(activation_limit)?;
2748 validate_replicated_device_rows(&self.ranks, input)?;
2749 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2750 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2751 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2752 if input.width != mlp.input_width
2753 || mlp.gate.len() != self.ranks.len()
2754 || mlp.up.len() != self.ranks.len()
2755 || mlp.down.len() != self.ranks.len()
2756 {
2757 return Err("replicated BF16 SwiGLU residency or input width changed".into());
2758 }
2759
2760 let mut outputs = Vec::with_capacity(self.ranks.len());
2761 for rank in 0..self.ranks.len() {
2762 let engine = &self.ranks[rank];
2763 let gate = run_resident_bf16_rank_device(
2764 engine,
2765 &mlp.gate[rank],
2766 &input.ranks[rank],
2767 input.tokens,
2768 None,
2769 self.bulk_p2p,
2770 )?;
2771 let up = run_resident_bf16_rank_device(
2772 engine,
2773 &mlp.up[rank],
2774 &input.ranks[rank],
2775 input.tokens,
2776 None,
2777 self.bulk_p2p,
2778 )?;
2779 let _main = engine.gpu.enter_main()?;
2780 let values = input
2781 .tokens
2782 .checked_mul(mlp.intermediate_width)
2783 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
2784 let mut activation = engine.uninit(values)?;
2785 if let Some(limit) = activation_limit {
2786 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
2787 } else {
2788 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
2789 }
2790 outputs.push(run_resident_bf16_rank_device(
2791 engine,
2792 &mlp.down[rank],
2793 &activation,
2794 input.tokens,
2795 None,
2796 self.bulk_p2p,
2797 )?);
2798 }
2799 Ok(ResidentReplicatedDeviceRows {
2800 ranks: outputs,
2801 tokens: input.tokens,
2802 width: mlp.input_width,
2803 })
2804 }
2805
2806 pub fn rms_norm_replicated_device_rows(
2808 &self,
2809 input: &ResidentReplicatedDeviceRows,
2810 weight: &[f32],
2811 eps: f32,
2812 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2813 validate_replicated_device_rows(&self.ranks, input)?;
2814 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
2815 return Err(format!(
2816 "replicated RMS norm weight/eps {}/{} != width {}",
2817 weight.len(),
2818 eps,
2819 input.width
2820 )
2821 .into());
2822 }
2823 let mut ranks = Vec::with_capacity(self.ranks.len());
2824 for (rank, engine) in self.ranks.iter().enumerate() {
2825 let _main = engine.gpu.enter_main()?;
2826 let weight = engine.htod(weight)?;
2827 let mut output = engine.uninit(input.tokens * input.width)?;
2828 engine.rms_norm(
2829 &input.ranks[rank],
2830 &weight,
2831 &mut output,
2832 input.width,
2833 input.tokens,
2834 eps,
2835 )?;
2836 ranks.push(output);
2837 }
2838 Ok(ResidentReplicatedDeviceRows {
2839 ranks,
2840 tokens: input.tokens,
2841 width: input.width,
2842 })
2843 }
2844
2845 pub fn add_rms_norm_replicated_device_rows(
2847 &self,
2848 input: &ResidentReplicatedDeviceRows,
2849 update: &ResidentReplicatedDeviceRows,
2850 weight: &[f32],
2851 eps: f32,
2852 ) -> Result<
2853 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
2854 Box<dyn std::error::Error>,
2855 > {
2856 validate_replicated_device_rows(&self.ranks, input)?;
2857 validate_replicated_device_rows(&self.ranks, update)?;
2858 if input.tokens != update.tokens
2859 || input.width != update.width
2860 || weight.len() != input.width
2861 || !eps.is_finite()
2862 || eps <= 0.0
2863 {
2864 return Err(format!(
2865 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
2866 input.tokens,
2867 input.width,
2868 update.tokens,
2869 update.width,
2870 weight.len(),
2871 )
2872 .into());
2873 }
2874 let values = input.tokens * input.width;
2875 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
2876 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
2877 for (rank, engine) in self.ranks.iter().enumerate() {
2878 let _main = engine.gpu.enter_main()?;
2879 let weight = engine.htod(weight)?;
2880 let mut residual = engine.uninit(values)?;
2881 let mut normalized = engine.uninit(values)?;
2882 engine.add_rms_norm(
2883 &input.ranks[rank],
2884 &update.ranks[rank],
2885 &weight,
2886 &mut residual,
2887 &mut normalized,
2888 input.width,
2889 input.tokens,
2890 eps,
2891 )?;
2892 residual_ranks.push(residual);
2893 normalized_ranks.push(normalized);
2894 }
2895 Ok((
2896 ResidentReplicatedDeviceRows {
2897 ranks: residual_ranks,
2898 tokens: input.tokens,
2899 width: input.width,
2900 },
2901 ResidentReplicatedDeviceRows {
2902 ranks: normalized_ranks,
2903 tokens: input.tokens,
2904 width: input.width,
2905 },
2906 ))
2907 }
2908
2909 pub fn collect_replicated_device_rows(
2910 &self,
2911 rows: &ResidentReplicatedDeviceRows,
2912 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
2913 validate_replicated_device_rows(&self.ranks, rows)?;
2914 let mut outputs = Vec::with_capacity(self.ranks.len());
2915 for (rank, engine) in self.ranks.iter().enumerate() {
2916 let _main = engine.gpu.enter_main()?;
2917 outputs.push(engine.dtoh(&rows.ranks[rank])?);
2918 }
2919 Ok(outputs)
2920 }
2921
2922 pub fn upload_bf16_row_parallel(
2923 &self,
2924 matrix: Bf16Matrix<'_>,
2925 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
2926 matrix.validate()?;
2927 let tp = self.ranks.len();
2928 if matrix.in_features % tp != 0 {
2929 return Err(format!(
2930 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
2931 matrix.in_features
2932 )
2933 .into());
2934 }
2935 let mut ranks = Vec::with_capacity(tp);
2936 for (rank, engine) in self.ranks.iter().enumerate() {
2937 let shard = bf16_row_shard(matrix, tp, rank)?;
2938 ranks.push(upload_bf16_rank(
2939 engine,
2940 Bf16Matrix {
2941 bytes: &shard,
2942 out_features: matrix.out_features,
2943 in_features: matrix.in_features / tp,
2944 },
2945 false,
2946 )?);
2947 }
2948 Ok(ResidentBf16RowParallel {
2949 ranks,
2950 out_features: matrix.out_features,
2951 in_features: matrix.in_features,
2952 })
2953 }
2954
2955 pub fn bf16_row_parallel_resident(
2956 &self,
2957 matrix: &ResidentBf16RowParallel,
2958 activations: &[f32],
2959 tokens: usize,
2960 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2961 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2962 validate_activations(activations, tokens, matrix.in_features)?;
2963 let tp = self.ranks.len();
2964 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2965 let mut rank_partials = Vec::with_capacity(tp);
2966 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2967 let local_activations =
2968 activation_shard(activations, tokens, matrix.in_features, tp, rank);
2969 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
2970 for (sum, value) in reduced.iter_mut().zip(&partial) {
2971 *sum += value;
2972 }
2973 rank_partials.push(partial);
2974 }
2975 Ok(RowParallelResult {
2976 reduced,
2977 rank_partials,
2978 })
2979 }
2980
2981 pub fn upload_step_bf16_row_parallel(
2983 &self,
2984 matrix: Bf16Matrix<'_>,
2985 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
2986 self.upload_step_bf16_row_parallel_inner(matrix, false)
2987 }
2988
2989 pub fn upload_step_bf16_row_parallel_f32_mirror(
2990 &self,
2991 matrix: Bf16Matrix<'_>,
2992 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
2993 self.upload_step_bf16_row_parallel_inner(matrix, true)
2994 }
2995
2996 fn upload_step_bf16_row_parallel_inner(
2997 &self,
2998 matrix: Bf16Matrix<'_>,
2999 f32_mirror: bool,
3000 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3001 matrix.validate()?;
3002 let tp = self.ranks.len();
3003 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3004 let local_in = matrix.in_features / tp;
3005 let blocks_per_rank = local_in / canonical_chunk_cols;
3006 let mut ranks = Vec::with_capacity(tp);
3007 for (rank, engine) in self.ranks.iter().enumerate() {
3008 let mut blocks = Vec::with_capacity(blocks_per_rank);
3009 for block in 0..blocks_per_rank {
3010 let global_block = rank * blocks_per_rank + block;
3011 let col_start = global_block * canonical_chunk_cols;
3012 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3013 blocks.push(upload_bf16_rank(
3014 engine,
3015 Bf16Matrix {
3016 bytes: &bytes,
3017 out_features: matrix.out_features,
3018 in_features: canonical_chunk_cols,
3019 },
3020 f32_mirror,
3021 )?);
3022 }
3023 ranks.push(blocks);
3024 }
3025 Ok(ResidentStepBf16RowParallel {
3026 ranks,
3027 out_features: matrix.out_features,
3028 in_features: matrix.in_features,
3029 canonical_chunk_cols,
3030 })
3031 }
3032
3033 pub fn step_bf16_row_parallel_resident(
3038 &self,
3039 matrix: &ResidentStepBf16RowParallel,
3040 activations: &[f32],
3041 tokens: usize,
3042 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3043 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3044 validate_activations(activations, tokens, matrix.in_features)?;
3045 let root = &self.ranks[0];
3046 let output_len = tokens
3047 .checked_mul(matrix.out_features)
3048 .ok_or("Step BF16 row output size overflow")?;
3049 let mut reduced = {
3050 let _main = root.gpu.enter_main()?;
3051 root.htod(&vec![0.0f32; output_len])?
3052 };
3053 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3054 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3055 for (block, resident) in blocks.iter().enumerate() {
3056 let global_block = rank * blocks_per_rank + block;
3057 let input = activation_shard(
3058 activations,
3059 tokens,
3060 matrix.in_features,
3061 PRODUCT_MAX_CARDS,
3062 global_block,
3063 );
3064 let partial =
3065 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3066 let next = {
3067 let _main = root.gpu.enter_main()?;
3068 let partial = root.htod(&partial)?;
3069 let mut next = root.uninit(output_len)?;
3070 root.add(&reduced, &partial, &mut next, output_len)?;
3071 next
3072 };
3073 reduced = next;
3074 }
3075 }
3076 let _main = root.gpu.enter_main()?;
3077 root.dtoh(&reduced)
3078 }
3079
3080 pub fn step_bf16_row_parallel_resident_native(
3086 &self,
3087 matrix: &ResidentStepBf16RowParallel,
3088 activations: &[f32],
3089 tokens: usize,
3090 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3091 if self.ranks.len() > 1 && !self.native_p2p {
3092 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3093 }
3094 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3095 validate_activations(activations, tokens, matrix.in_features)?;
3096 let root = &self.ranks[0];
3097 let root_input = {
3098 let _main = root.gpu.enter_main()?;
3099 root.htod(activations)?
3100 };
3101 let output_len = tokens
3102 .checked_mul(matrix.out_features)
3103 .ok_or("native Step BF16 row output size overflow")?;
3104 let mut reduced = {
3105 let _main = root.gpu.enter_main()?;
3106 root.htod(&vec![0.0f32; output_len])?
3107 };
3108 {
3111 let _main = root.gpu.enter_main()?;
3112 root.stream().synchronize()?;
3113 }
3114 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3115 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3116 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3117 let mut remote_partial_keepalive = Vec::new();
3118 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3119 for (block, resident) in blocks.iter().enumerate() {
3120 let global_block = rank * blocks_per_rank + block;
3121 let col_start = global_block * matrix.canonical_chunk_cols;
3122 let block_len = tokens
3123 .checked_mul(matrix.canonical_chunk_cols)
3124 .ok_or("native Step BF16 row block size overflow")?;
3125 let block_input = if self.bulk_p2p {
3126 let root_packed = {
3127 let _main = root.gpu.enter_main()?;
3128 let mut root_packed = root.uninit(block_len)?;
3129 root.copy_rows_strided(
3130 &root_input,
3131 &mut root_packed,
3132 matrix.canonical_chunk_cols,
3133 tokens,
3134 matrix.in_features,
3135 col_start,
3136 )?;
3137 root_packed
3138 };
3139 if rank == 0 {
3140 root_packed
3141 } else {
3142 {
3145 let _main = root.gpu.enter_main()?;
3146 root.stream().synchronize()?;
3147 }
3148 let engine = &self.ranks[rank];
3149 let _main = engine.gpu.enter_main()?;
3150 let mut block_input = engine.uninit(block_len)?;
3151 engine
3152 .stream()
3153 .memcpy_dtod(&root_packed, &mut block_input)?;
3154 root_packed_keepalive.push(root_packed);
3155 block_input
3156 }
3157 } else {
3158 let engine = &self.ranks[rank];
3159 let _main = engine.gpu.enter_main()?;
3160 let mut block_input = engine.uninit(block_len)?;
3161 for token in 0..tokens {
3162 let source_start = token * matrix.in_features + col_start;
3163 let source = root_input
3164 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3165 let destination_start = token * matrix.canonical_chunk_cols;
3166 let mut destination = block_input.slice_mut(
3167 destination_start..destination_start + matrix.canonical_chunk_cols,
3168 );
3169 engine.stream().memcpy_dtod(&source, &mut destination)?;
3170 }
3171 block_input
3172 };
3173 let partial = run_resident_bf16_rank_device(
3174 &self.ranks[rank],
3175 resident,
3176 &block_input,
3177 tokens,
3178 None,
3179 self.bulk_p2p,
3180 )?;
3181 block_input_keepalive.push(block_input);
3182 let root_partial = if rank == 0 {
3183 partial
3184 } else {
3185 {
3188 let engine = &self.ranks[rank];
3189 let _main = engine.gpu.enter_main()?;
3190 engine.stream().synchronize()?;
3191 }
3192 let _main = root.gpu.enter_main()?;
3193 let mut peer_partial = root.uninit(output_len)?;
3194 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3195 remote_partial_keepalive.push(partial);
3196 peer_partial
3197 };
3198 let next = {
3199 let _main = root.gpu.enter_main()?;
3200 let mut next = root.uninit(output_len)?;
3201 root.add(&reduced, &root_partial, &mut next, output_len)?;
3202 next
3203 };
3204 reduced = next;
3205 }
3206 }
3207 let output = {
3208 let _main = root.gpu.enter_main()?;
3209 root.dtoh(&reduced)?
3210 };
3211 drop(remote_partial_keepalive);
3212 drop(root_packed_keepalive);
3213 drop(block_input_keepalive);
3214 Ok(output)
3215 }
3216
3217 pub fn step_bf16_row_parallel_resident_root_device(
3220 &self,
3221 matrix: &ResidentStepBf16RowParallel,
3222 rank_activations: &[CudaSlice<f32>],
3223 tokens: usize,
3224 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3225 if self.ranks.len() > 1 && !self.native_p2p {
3226 return Err(
3227 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3228 );
3229 }
3230 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3231 let local_width = matrix.in_features / self.ranks.len();
3232 let shard_len = tokens
3233 .checked_mul(local_width)
3234 .ok_or("device Step BF16 row shard size overflow")?;
3235 if tokens == 0
3236 || rank_activations.len() != self.ranks.len()
3237 || rank_activations
3238 .iter()
3239 .zip(&self.ranks)
3240 .any(|(rows, engine)| {
3241 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3242 })
3243 {
3244 return Err("device Step BF16 row activation shard geometry changed".into());
3245 }
3246
3247 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3248 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3249 let mut partials = Vec::with_capacity(self.ranks.len());
3250 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3251 if blocks.len() != blocks_per_rank {
3252 return Err(format!(
3253 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3254 blocks.len()
3255 )
3256 .into());
3257 }
3258 let engine = &self.ranks[rank];
3259 let _main = engine.gpu.enter_main()?;
3260 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3261 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3262 for (block, resident) in blocks.iter().enumerate() {
3263 let block_len = tokens
3264 .checked_mul(matrix.canonical_chunk_cols)
3265 .ok_or("device Step BF16 row block size overflow")?;
3266 let mut block_input = engine.uninit(block_len)?;
3267 let local_col_start = block * matrix.canonical_chunk_cols;
3268 if self.bulk_p2p {
3269 engine.copy_rows_strided(
3270 &rank_activations[rank],
3271 &mut block_input,
3272 matrix.canonical_chunk_cols,
3273 tokens,
3274 local_width,
3275 local_col_start,
3276 )?;
3277 } else {
3278 for token in 0..tokens {
3279 let source_start = token * local_width + local_col_start;
3280 let source = rank_activations[rank]
3281 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3282 let destination_start = token * matrix.canonical_chunk_cols;
3283 let mut destination = block_input.slice_mut(
3284 destination_start..destination_start + matrix.canonical_chunk_cols,
3285 );
3286 engine.stream().memcpy_dtod(&source, &mut destination)?;
3287 }
3288 }
3289 let partial = run_resident_bf16_rank_device(
3290 engine,
3291 resident,
3292 &block_input,
3293 tokens,
3294 None,
3295 self.bulk_p2p,
3296 )?;
3297 rank_inputs.push(block_input);
3298 rank_partials.push(partial);
3299 }
3300 block_inputs.push(rank_inputs);
3301 partials.push(rank_partials);
3302 }
3303 for engine in self.ranks.iter().skip(1) {
3304 let _main = engine.gpu.enter_main()?;
3305 engine.stream().synchronize()?;
3306 }
3307
3308 let output_len = tokens
3309 .checked_mul(matrix.out_features)
3310 .ok_or("device Step BF16 row output size overflow")?;
3311 let root = &self.ranks[0];
3312 let _main = root.gpu.enter_main()?;
3313 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3314 let mut remote_partials = Vec::new();
3315 for (rank, rank_partials) in partials.into_iter().enumerate() {
3316 for partial in rank_partials {
3317 let root_partial = if rank == 0 {
3318 partial
3319 } else {
3320 let mut peer_partial = root.uninit(output_len)?;
3321 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3322 remote_partials.push(partial);
3323 peer_partial
3324 };
3325 let mut next = root.uninit(output_len)?;
3326 root.add(&reduced, &root_partial, &mut next, output_len)?;
3327 reduced = next;
3328 }
3329 }
3330 root.stream().synchronize()?;
3331 drop(remote_partials);
3332 drop(block_inputs);
3333 Ok(reduced)
3334 }
3335
3336 pub fn step_bf16_row_parallel_resident_replicated_device(
3338 &self,
3339 matrix: &ResidentStepBf16RowParallel,
3340 rank_activations: &[CudaSlice<f32>],
3341 tokens: usize,
3342 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3343 let reduced =
3344 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3345 let output_len = tokens
3346 .checked_mul(matrix.out_features)
3347 .ok_or("device Step BF16 row output size overflow")?;
3348 let mut ranks = Vec::with_capacity(self.ranks.len());
3349 ranks.push(reduced);
3350 for engine in self.ranks.iter().skip(1) {
3351 let _main = engine.gpu.enter_main()?;
3352 let mut peer_output = engine.uninit(output_len)?;
3353 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3354 ranks.push(peer_output);
3355 }
3356 Ok(ResidentReplicatedDeviceRows {
3357 ranks,
3358 tokens,
3359 width: matrix.out_features,
3360 })
3361 }
3362
3363 pub fn upload_expert(
3364 &self,
3365 gate: E4m3BlockMatrix<'_>,
3366 up: E4m3BlockMatrix<'_>,
3367 down: E4m3BlockMatrix<'_>,
3368 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3369 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3370 return Err("TP expert gate/up dimensions differ".into());
3371 }
3372 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3373 return Err(format!(
3374 "TP expert down {}x{} does not invert gate/up {}x{}",
3375 down.out_features, down.in_features, gate.out_features, gate.in_features
3376 )
3377 .into());
3378 }
3379 Ok(ResidentTpExpert {
3380 gate: self.upload_column_parallel(gate)?,
3381 up: self.upload_column_parallel(up)?,
3382 down: self.upload_row_parallel(down)?,
3383 input_width: gate.in_features,
3384 expert_width: gate.out_features,
3385 })
3386 }
3387
3388 pub fn run_expert(
3389 &self,
3390 expert: &ResidentTpExpert,
3391 input: &[f32],
3392 tokens: usize,
3393 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3394 validate_activations(input, tokens, expert.input_width)?;
3395 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3396 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3397 let activated: Vec<f32> = gate
3398 .gathered
3399 .iter()
3400 .zip(&up.gathered)
3401 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3402 .collect();
3403 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3404 Ok(self
3405 .row_parallel_resident(&expert.down, &activated, tokens)?
3406 .reduced)
3407 }
3408
3409 pub fn upload_expert_parallel(
3410 &self,
3411 gate: E4m3ExpertBank<'_>,
3412 up: E4m3ExpertBank<'_>,
3413 down: E4m3ExpertBank<'_>,
3414 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3415 gate.validate()?;
3416 up.validate()?;
3417 down.validate()?;
3418 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3419 return Err("EP gate/up/down expert counts differ".into());
3420 }
3421 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3422 return Err("EP gate/up dimensions differ".into());
3423 }
3424 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3425 return Err(format!(
3426 "EP down {}x{} does not invert gate/up {}x{}",
3427 down.out_features, down.in_features, gate.out_features, gate.in_features
3428 )
3429 .into());
3430 }
3431 if gate.expert_count % self.ranks.len() != 0 {
3432 return Err(format!(
3433 "EP expert count {} is not divisible by {} ranks",
3434 gate.expert_count,
3435 self.ranks.len()
3436 )
3437 .into());
3438 }
3439
3440 let per_rank = gate.expert_count / self.ranks.len();
3441 let mut ranks = Vec::with_capacity(self.ranks.len());
3442 for (rank, engine) in self.ranks.iter().enumerate() {
3443 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3444 ranks.push(ResidentEpRank {
3445 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3446 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3447 down: upload_expert_bank_rank(engine, down, expert_range)?,
3448 });
3449 }
3450 Ok(ResidentExpertParallel {
3451 ranks,
3452 expert_count: gate.expert_count,
3453 input_width: gate.in_features,
3454 expert_width: gate.out_features,
3455 })
3456 }
3457
3458 #[allow(clippy::too_many_arguments)]
3464 pub fn prepare_step_grouped_fp8_gate(
3465 &self,
3466 gate: E4m3ExpertBank<'_>,
3467 up: E4m3ExpertBank<'_>,
3468 down: E4m3ExpertBank<'_>,
3469 input: &[f32],
3470 tokens: usize,
3471 selected: &[usize],
3472 activation_limit: Option<f32>,
3473 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3474 gate.validate()?;
3475 up.validate()?;
3476 down.validate()?;
3477 validate_step_expert_activation_limit(activation_limit)?;
3478 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3479 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3480 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3481 {
3482 return Err(format!(
3483 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3484 got gate/up/down={}/{}/{}",
3485 gate.expert_count, up.expert_count, down.expert_count,
3486 )
3487 .into());
3488 }
3489 if gate.in_features != up.in_features
3490 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3491 || up.out_features != STEP_GROUPED_FP8_WIDTH
3492 || down.in_features != STEP_GROUPED_FP8_WIDTH
3493 || down.out_features != gate.in_features
3494 {
3495 return Err(format!(
3496 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3497 gate.out_features,
3498 gate.in_features,
3499 up.out_features,
3500 up.in_features,
3501 down.out_features,
3502 down.in_features,
3503 )
3504 .into());
3505 }
3506 validate_activations(input, tokens, gate.in_features)?;
3507 let pairs = tokens
3508 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3509 .ok_or("official Step grouped FP8 route count overflow")?;
3510 if selected.len() != pairs {
3511 return Err(format!(
3512 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3513 ({pairs})",
3514 selected.len()
3515 )
3516 .into());
3517 }
3518 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3519 let mut unique = routes.to_vec();
3520 unique.sort_unstable();
3521 unique.dedup();
3522 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3523 return Err(format!(
3524 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3525 {routes:?}"
3526 )
3527 .into());
3528 }
3529 }
3530
3531 let engine = self
3532 .ranks
3533 .first()
3534 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3535 let _main = engine.gpu.enter_main()?;
3536 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3537 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3538 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3539 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3540 let input = engine.htod(input)?;
3541 let route_csr = ExpertCsr::from_token_routes(
3542 STEP_GROUPED_FP8_EXPERTS,
3543 tokens,
3544 STEP_GROUPED_FP8_TOP_K,
3545 selected,
3546 )?
3547 .upload(engine)?;
3548 let pair_rows = (0..pairs).collect::<Vec<_>>();
3549 let down_csr =
3550 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3551 .upload(engine)?;
3552 let gate_workspace =
3553 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3554 let up_workspace =
3555 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3556 let down_workspace =
3557 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3558 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3559 Ok(PreparedStepGroupedFp8Gate {
3560 device: engine.ctx().ordinal(),
3561 gate,
3562 up,
3563 down,
3564 input,
3565 route_csr,
3566 down_csr,
3567 gate_workspace,
3568 up_workspace,
3569 down_workspace,
3570 activation,
3571 activation_limit,
3572 tokens,
3573 pairs,
3574 })
3575 }
3576
3577 pub fn run_step_grouped_fp8_gate(
3579 &self,
3580 plan: &mut PreparedStepGroupedFp8Gate,
3581 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3582 let engine = self
3583 .ranks
3584 .first()
3585 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3586 if engine.ctx().ordinal() != plan.device {
3587 return Err(format!(
3588 "official Step grouped FP8 plan device {} != rank-zero device {}",
3589 plan.device,
3590 engine.ctx().ordinal()
3591 )
3592 .into());
3593 }
3594 let _main = engine.gpu.enter_main()?;
3595
3596 plan.gate_workspace.quantize(engine, &plan.input)?;
3597 plan.gate_workspace.project(
3598 engine,
3599 &plan.gate.codes,
3600 &plan.gate.scales,
3601 &plan.route_csr,
3602 plan.gate.code_stride,
3603 plan.gate.scale_stride,
3604 1.0,
3605 )?;
3606 plan.up_workspace.quantize(engine, &plan.input)?;
3607 plan.up_workspace.project(
3608 engine,
3609 &plan.up.codes,
3610 &plan.up.scales,
3611 &plan.route_csr,
3612 plan.up.code_stride,
3613 plan.up.scale_stride,
3614 1.0,
3615 )?;
3616 if let Some(limit) = plan.activation_limit {
3617 engine.silu_clamped_mul_host_expf(
3618 plan.gate_workspace.output(),
3619 plan.up_workspace.output(),
3620 limit,
3621 &mut plan.activation,
3622 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3623 )?;
3624 } else {
3625 engine.silu_mul_host_expf(
3626 plan.gate_workspace.output(),
3627 plan.up_workspace.output(),
3628 &mut plan.activation,
3629 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3630 )?;
3631 }
3632 plan.down_workspace.quantize(engine, &plan.activation)?;
3633 plan.down_workspace.project(
3634 engine,
3635 &plan.down.codes,
3636 &plan.down.scales,
3637 &plan.down_csr,
3638 plan.down.code_stride,
3639 plan.down.scale_stride,
3640 1.0,
3641 )?;
3642
3643 Ok(StepGroupedFp8ProjectionOutput {
3644 gate: engine.dtoh(plan.gate_workspace.output())?,
3645 up: engine.dtoh(plan.up_workspace.output())?,
3646 down: engine.dtoh(plan.down_workspace.output())?,
3647 })
3648 }
3649
3650 pub fn prepare_step_grouped_expert_parallel_gate(
3651 &self,
3652 experts: &ResidentExpertParallel,
3653 input: &[f32],
3654 tokens: usize,
3655 selected: &[usize],
3656 activation_limit: Option<f32>,
3657 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3658 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3659 experts,
3660 input,
3661 tokens,
3662 selected,
3663 activation_limit,
3664 tokens,
3665 )
3666 }
3667
3668 #[allow(clippy::too_many_arguments)]
3669 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3670 &self,
3671 experts: &ResidentExpertParallel,
3672 input: &[f32],
3673 tokens: usize,
3674 selected: &[usize],
3675 activation_limit: Option<f32>,
3676 max_tokens: usize,
3677 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3678 if !self.native_p2p || !self.ep_device_arithmetic {
3679 return Err(
3680 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3681 );
3682 }
3683 validate_step_expert_activation_limit(activation_limit)?;
3684 validate_ep_residency(&self.ranks, experts)?;
3685 validate_activations(input, tokens, experts.input_width)?;
3686 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3687 return Err(format!(
3688 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3689 )
3690 .into());
3691 }
3692 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3693 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3694 {
3695 return Err(format!(
3696 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3697 STEP_GROUPED_FP8_EXPERTS,
3698 STEP_GROUPED_FP8_WIDTH,
3699 experts.expert_count,
3700 experts.expert_width,
3701 )
3702 .into());
3703 }
3704 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3705 let max_pairs = max_tokens
3706 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3707 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
3708 let input_capacity = max_tokens
3709 .checked_mul(experts.input_width)
3710 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
3711
3712 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3713 for engine in &self.ranks {
3714 let _main = engine.gpu.enter_main()?;
3715 rank_inputs.push(engine.uninit(input_capacity)?);
3716 }
3717
3718 let mut owners = Vec::with_capacity(self.ranks.len());
3719 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
3720 if rank.gate.expert_range != rank.up.expert_range
3721 || rank.gate.expert_range != rank.down.expert_range
3722 {
3723 return Err(format!(
3724 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
3725 owner_rank
3726 )
3727 .into());
3728 }
3729 let local_experts = rank.gate.expert_range.len();
3730 let engine = &self.ranks[owner_rank];
3731 let _main = engine.gpu.enter_main()?;
3732 let route_csr =
3733 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
3734 let down_csr =
3735 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
3736 let gate_workspace = Fp8GroupedWorkspace::new(
3737 engine,
3738 experts.input_width,
3739 experts.expert_width,
3740 max_tokens,
3741 max_pairs,
3742 )?;
3743 let up_workspace = Fp8GroupedWorkspace::new(
3744 engine,
3745 experts.input_width,
3746 experts.expert_width,
3747 max_tokens,
3748 max_pairs,
3749 )?;
3750 let down_workspace = Fp8GroupedWorkspace::new(
3751 engine,
3752 experts.expert_width,
3753 experts.input_width,
3754 max_pairs,
3755 max_pairs,
3756 )?;
3757 let activation = engine.uninit(
3758 max_pairs
3759 .checked_mul(experts.expert_width)
3760 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
3761 )?;
3762 owners.push(PreparedStepGroupedExpertOwner {
3763 rank: owner_rank,
3764 global_pairs: Vec::new(),
3765 route_csr,
3766 down_csr,
3767 gate_workspace,
3768 up_workspace,
3769 down_workspace,
3770 activation,
3771 });
3772 }
3773
3774 let mut plan = PreparedStepGroupedExpertParallelGate {
3775 rank_inputs,
3776 owners,
3777 activation_limit,
3778 tokens: 0,
3779 pairs: 0,
3780 max_tokens,
3781 max_pairs,
3782 input_width: experts.input_width,
3783 expert_width: experts.expert_width,
3784 generation: 0,
3785 executed_generation: None,
3786 ready: false,
3787 };
3788 self.refresh_step_grouped_expert_parallel_gate(
3789 experts, &mut plan, input, tokens, selected,
3790 )?;
3791 Ok(plan)
3792 }
3793
3794 fn prepare_step_grouped_expert_parallel_refresh(
3795 &self,
3796 experts: &ResidentExpertParallel,
3797 plan: &PreparedStepGroupedExpertParallelGate,
3798 tokens: usize,
3799 selected: &[usize],
3800 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
3801 {
3802 validate_ep_residency(&self.ranks, experts)?;
3803 if plan.rank_inputs.len() != self.ranks.len()
3804 || plan.owners.len() != self.ranks.len()
3805 || plan.input_width != experts.input_width
3806 || plan.expert_width != experts.expert_width
3807 || tokens > plan.max_tokens
3808 {
3809 return Err(format!(
3810 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
3811 input={}/{} expert={}/{} tokens={}/{}",
3812 plan.rank_inputs.len(),
3813 self.ranks.len(),
3814 plan.owners.len(),
3815 self.ranks.len(),
3816 plan.input_width,
3817 experts.input_width,
3818 plan.expert_width,
3819 experts.expert_width,
3820 tokens,
3821 plan.max_tokens,
3822 )
3823 .into());
3824 }
3825 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3826 if pairs > plan.max_pairs {
3827 return Err(format!(
3828 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
3829 plan.max_pairs
3830 )
3831 .into());
3832 }
3833 let next_generation = plan
3834 .generation
3835 .checked_add(1)
3836 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
3837 let owner_routes = partition_expert_owner_routes(
3838 experts.expert_count,
3839 self.ranks.len(),
3840 tokens,
3841 STEP_GROUPED_FP8_TOP_K,
3842 selected,
3843 )?;
3844 let mut schedules = Vec::with_capacity(self.ranks.len());
3845 for routes in owner_routes {
3846 if routes.selected.is_empty() {
3847 schedules.push(None);
3848 continue;
3849 }
3850 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
3851 let local_pairs = routes.selected.len();
3852 let route_csr = ExpertCsr::from_pair_rows(
3853 local_experts,
3854 tokens,
3855 &routes.selected,
3856 &routes.token_rows,
3857 )?;
3858 let down_rows = (0..local_pairs).collect::<Vec<_>>();
3859 let down_csr = ExpertCsr::from_pair_rows(
3860 local_experts,
3861 local_pairs,
3862 &routes.selected,
3863 &down_rows,
3864 )?;
3865 schedules.push(Some(StepGroupedExpertOwnerSchedule {
3866 global_pairs: routes.global_pairs,
3867 route_csr,
3868 down_csr,
3869 }));
3870 }
3871 Ok((pairs, next_generation, schedules))
3872 }
3873
3874 fn commit_step_grouped_expert_parallel_refresh(
3875 &self,
3876 plan: &mut PreparedStepGroupedExpertParallelGate,
3877 tokens: usize,
3878 pairs: usize,
3879 next_generation: u64,
3880 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
3881 ) -> Result<(), Box<dyn std::error::Error>> {
3882 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
3883 let engine = &self.ranks[owner.rank];
3884 let _main = engine.gpu.enter_main()?;
3885 if let Some(schedule) = schedule {
3886 owner.route_csr.refresh(engine, &schedule.route_csr)?;
3887 owner.down_csr.refresh(engine, &schedule.down_csr)?;
3888 owner.global_pairs = schedule.global_pairs;
3889 } else {
3890 owner.route_csr.clear();
3891 owner.down_csr.clear();
3892 owner.global_pairs.clear();
3893 }
3894 }
3895 plan.tokens = tokens;
3896 plan.pairs = pairs;
3897 plan.generation = next_generation;
3898 plan.ready = true;
3899 Ok(())
3900 }
3901
3902 pub fn refresh_step_grouped_expert_parallel_gate(
3903 &self,
3904 experts: &ResidentExpertParallel,
3905 plan: &mut PreparedStepGroupedExpertParallelGate,
3906 input: &[f32],
3907 tokens: usize,
3908 selected: &[usize],
3909 ) -> Result<(), Box<dyn std::error::Error>> {
3910 validate_activations(input, tokens, experts.input_width)?;
3911 let (pairs, next_generation, schedules) =
3912 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
3913
3914 plan.ready = false;
3915 plan.executed_generation = None;
3916 {
3917 let root = &self.ranks[0];
3918 let _main = root.gpu.enter_main()?;
3919 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
3920 root.stream().memcpy_htod(input, &mut destination)?;
3921 root.stream().synchronize()?;
3922 }
3923 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
3924 let root_input = &root_inputs[0];
3925 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
3926 let engine = &self.ranks[rank + 1];
3927 let _main = engine.gpu.enter_main()?;
3928 let mut destination = peer_input.slice_mut(0..input.len());
3929 engine
3930 .stream()
3931 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
3932 }
3933 self.commit_step_grouped_expert_parallel_refresh(
3934 plan,
3935 tokens,
3936 pairs,
3937 next_generation,
3938 schedules,
3939 )
3940 }
3941
3942 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
3947 &self,
3948 experts: &ResidentExpertParallel,
3949 plan: &mut PreparedStepGroupedExpertParallelGate,
3950 input: &CudaSlice<f32>,
3951 tokens: usize,
3952 selected: &[usize],
3953 ) -> Result<(), Box<dyn std::error::Error>> {
3954 let input_values = tokens
3955 .checked_mul(experts.input_width)
3956 .ok_or("Step owner-grouped FP8 input size overflow")?;
3957 let root = self
3958 .ranks
3959 .first()
3960 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
3961 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
3962 return Err(format!(
3963 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
3964 device {}",
3965 input.len(),
3966 input.ordinal(),
3967 input_values,
3968 root.ctx().ordinal(),
3969 )
3970 .into());
3971 }
3972 let (pairs, next_generation, schedules) =
3973 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
3974
3975 plan.ready = false;
3976 plan.executed_generation = None;
3977 {
3978 let _main = root.gpu.enter_main()?;
3979 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
3980 root.stream()
3981 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
3982 root.stream().synchronize()?;
3983 }
3984 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
3985 let root_input = &root_inputs[0];
3986 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
3987 let engine = &self.ranks[rank + 1];
3988 let _main = engine.gpu.enter_main()?;
3989 let mut destination = peer_input.slice_mut(0..input_values);
3990 engine
3991 .stream()
3992 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
3993 }
3994 self.commit_step_grouped_expert_parallel_refresh(
3995 plan,
3996 tokens,
3997 pairs,
3998 next_generation,
3999 schedules,
4000 )
4001 }
4002
4003 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4008 &self,
4009 experts: &ResidentExpertParallel,
4010 plan: &mut PreparedStepGroupedExpertParallelGate,
4011 input: &ResidentReplicatedDeviceRows,
4012 ) -> Result<(), Box<dyn std::error::Error>> {
4013 validate_ep_residency(&self.ranks, experts)?;
4014 validate_replicated_device_rows(&self.ranks, input)?;
4015 if !plan.ready
4016 || input.tokens != plan.tokens
4017 || input.width != plan.input_width
4018 || input.tokens > plan.max_tokens
4019 || plan.rank_inputs.len() != self.ranks.len()
4020 || plan.owners.len() != self.ranks.len()
4021 || plan.input_width != experts.input_width
4022 || plan.expert_width != experts.expert_width
4023 {
4024 return Err("Step owner-grouped replicated input geometry changed".into());
4025 }
4026 let values = input
4027 .tokens
4028 .checked_mul(input.width)
4029 .ok_or("Step owner-grouped replicated input size overflow")?;
4030 let next_generation = plan
4031 .generation
4032 .checked_add(1)
4033 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4034 plan.ready = false;
4035 plan.executed_generation = None;
4036 for (rank, engine) in self.ranks.iter().enumerate() {
4037 let _main = engine.gpu.enter_main()?;
4038 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4039 engine
4040 .stream()
4041 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4042 }
4043 plan.generation = next_generation;
4044 plan.ready = true;
4045 Ok(())
4046 }
4047
4048 pub fn execute_step_grouped_expert_parallel_gate(
4049 &self,
4050 experts: &ResidentExpertParallel,
4051 plan: &mut PreparedStepGroupedExpertParallelGate,
4052 ) -> Result<(), Box<dyn std::error::Error>> {
4053 validate_ep_residency(&self.ranks, experts)?;
4054 if !plan.ready
4055 || plan.rank_inputs.len() != self.ranks.len()
4056 || plan.owners.len() != self.ranks.len()
4057 || plan.input_width != experts.input_width
4058 || plan.expert_width != experts.expert_width
4059 {
4060 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4061 }
4062 plan.executed_generation = None;
4063
4064 for owner in &mut plan.owners {
4065 if owner.global_pairs.is_empty() {
4066 continue;
4067 }
4068 let engine = &self.ranks[owner.rank];
4069 let bank = &experts.ranks[owner.rank];
4070 let _main = engine.gpu.enter_main()?;
4071 let local_pairs = owner.global_pairs.len();
4072 owner.gate_workspace.quantize_for_shape(
4073 engine,
4074 &plan.rank_inputs[owner.rank],
4075 plan.tokens,
4076 local_pairs,
4077 )?;
4078 owner.gate_workspace.project(
4079 engine,
4080 &bank.gate.codes,
4081 &bank.gate.scales,
4082 &owner.route_csr,
4083 bank.gate.code_stride,
4084 bank.gate.scale_stride,
4085 1.0,
4086 )?;
4087 owner.up_workspace.quantize_for_shape(
4088 engine,
4089 &plan.rank_inputs[owner.rank],
4090 plan.tokens,
4091 local_pairs,
4092 )?;
4093 owner.up_workspace.project(
4094 engine,
4095 &bank.up.codes,
4096 &bank.up.scales,
4097 &owner.route_csr,
4098 bank.up.code_stride,
4099 bank.up.scale_stride,
4100 1.0,
4101 )?;
4102 }
4103 for owner in &mut plan.owners {
4104 if owner.global_pairs.is_empty() {
4105 continue;
4106 }
4107 let engine = &self.ranks[owner.rank];
4108 let _main = engine.gpu.enter_main()?;
4109 let values = owner.global_pairs.len() * plan.expert_width;
4110 if let Some(limit) = plan.activation_limit {
4111 engine.silu_clamped_mul_host_expf(
4112 owner.gate_workspace.output(),
4113 owner.up_workspace.output(),
4114 limit,
4115 &mut owner.activation,
4116 values,
4117 )?;
4118 } else {
4119 engine.silu_mul_host_expf(
4120 owner.gate_workspace.output(),
4121 owner.up_workspace.output(),
4122 &mut owner.activation,
4123 values,
4124 )?;
4125 }
4126 }
4127 for owner in &mut plan.owners {
4128 if owner.global_pairs.is_empty() {
4129 continue;
4130 }
4131 let engine = &self.ranks[owner.rank];
4132 let bank = &experts.ranks[owner.rank];
4133 let _main = engine.gpu.enter_main()?;
4134 let local_pairs = owner.global_pairs.len();
4135 owner.down_workspace.quantize_for_shape(
4136 engine,
4137 &owner.activation,
4138 local_pairs,
4139 local_pairs,
4140 )?;
4141 owner.down_workspace.project(
4142 engine,
4143 &bank.down.codes,
4144 &bank.down.scales,
4145 &owner.down_csr,
4146 bank.down.code_stride,
4147 bank.down.scale_stride,
4148 1.0,
4149 )?;
4150 }
4151 plan.executed_generation = Some(plan.generation);
4152 Ok(())
4153 }
4154
4155 pub fn collect_step_grouped_expert_parallel_gate(
4156 &self,
4157 plan: &PreparedStepGroupedExpertParallelGate,
4158 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4159 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4160 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4161 }
4162 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4163 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4164 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4165 for owner in &plan.owners {
4166 if owner.global_pairs.is_empty() {
4167 continue;
4168 }
4169 let engine = &self.ranks[owner.rank];
4170 let _main = engine.gpu.enter_main()?;
4171 let owner_gate = engine.dtoh_view(
4172 &owner
4173 .gate_workspace
4174 .output()
4175 .slice(0..owner.gate_workspace.output_len()),
4176 )?;
4177 let owner_up = engine.dtoh_view(
4178 &owner
4179 .up_workspace
4180 .output()
4181 .slice(0..owner.up_workspace.output_len()),
4182 )?;
4183 let owner_down = engine.dtoh_view(
4184 &owner
4185 .down_workspace
4186 .output()
4187 .slice(0..owner.down_workspace.output_len()),
4188 )?;
4189 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4190 let local_expert = local_pair * plan.expert_width;
4191 let global_expert = global_pair * plan.expert_width;
4192 gate[global_expert..global_expert + plan.expert_width]
4193 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4194 up[global_expert..global_expert + plan.expert_width]
4195 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4196
4197 let local_hidden = local_pair * plan.input_width;
4198 let global_hidden = global_pair * plan.input_width;
4199 down[global_hidden..global_hidden + plan.input_width]
4200 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4201 }
4202 }
4203 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4204 }
4205
4206 pub fn run_step_grouped_expert_parallel_gate(
4207 &self,
4208 experts: &ResidentExpertParallel,
4209 plan: &mut PreparedStepGroupedExpertParallelGate,
4210 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4211 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4212 self.collect_step_grouped_expert_parallel_gate(plan)
4213 }
4214
4215 pub fn prepare_step_grouped_expert_parallel_combine(
4216 &self,
4217 plan: &PreparedStepGroupedExpertParallelGate,
4218 route_weights: &[f32],
4219 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4220 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4221 return Err(
4222 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4223 );
4224 }
4225 let owner_pairs = plan
4226 .owners
4227 .iter()
4228 .map(|owner| owner.global_pairs.as_slice())
4229 .collect::<Vec<_>>();
4230 let shape = validate_weighted_route_combine(
4231 plan.input_width,
4232 STEP_GROUPED_FP8_TOP_K,
4233 plan.max_tokens,
4234 plan.tokens,
4235 &owner_pairs,
4236 route_weights,
4237 )?;
4238 if shape.max_pairs != plan.max_pairs {
4239 return Err(format!(
4240 "Step owner-grouped combine capacity {} != projection capacity {}",
4241 shape.max_pairs, plan.max_pairs
4242 )
4243 .into());
4244 }
4245 let root = self
4246 .ranks
4247 .first()
4248 .ok_or("Step owner-grouped combine has no root rank")?;
4249 let slot_values = shape
4250 .max_pairs
4251 .checked_mul(plan.input_width)
4252 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4253 let output_values = plan
4254 .max_tokens
4255 .checked_mul(plan.input_width)
4256 .ok_or("Step owner-grouped combine output capacity overflow")?;
4257 let (root_device, owners, peer_staging, slots, weights, output) = {
4258 let _main = root.gpu.enter_main()?;
4259 let mut owners = Vec::with_capacity(plan.owners.len());
4260 for _ in &plan.owners {
4261 owners.push(PreparedPeerWeightedRouteOwner {
4262 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4263 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4264 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4265 active_pairs: 0,
4266 });
4267 }
4268 (
4269 root.ctx().ordinal(),
4270 owners,
4271 root.uninit(slot_values)?,
4272 root.uninit(slot_values)?,
4273 root.uninit(shape.max_pairs)?,
4274 root.uninit(output_values)?,
4275 )
4276 };
4277 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4278 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4279 for engine in self.ranks.iter().skip(1) {
4280 let _main = engine.gpu.enter_main()?;
4281 peer_devices.push(engine.ctx().ordinal());
4282 peer_outputs.push(engine.uninit(output_values)?);
4283 }
4284 let mut combine = PreparedPeerWeightedRouteCombine {
4285 root_device,
4286 owners,
4287 peer_staging,
4288 slots,
4289 weights,
4290 output,
4291 peer_devices,
4292 peer_outputs,
4293 width: plan.input_width,
4294 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4295 max_tokens: plan.max_tokens,
4296 max_pairs: shape.max_pairs,
4297 tokens: 0,
4298 pairs: 0,
4299 projection_generation: 0,
4300 output_generation: None,
4301 broadcast_generation: None,
4302 ready: false,
4303 };
4304 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4305 Ok(combine)
4306 }
4307
4308 pub fn refresh_step_grouped_expert_parallel_combine(
4309 &self,
4310 plan: &PreparedStepGroupedExpertParallelGate,
4311 combine: &mut PreparedPeerWeightedRouteCombine,
4312 route_weights: &[f32],
4313 ) -> Result<(), Box<dyn std::error::Error>> {
4314 let output_capacity = combine
4315 .max_tokens
4316 .checked_mul(combine.width)
4317 .ok_or("Step owner-grouped combine output capacity overflow")?;
4318 if !plan.ready
4319 || combine.owners.len() != plan.owners.len()
4320 || combine.peer_devices.len() + 1 != self.ranks.len()
4321 || combine.peer_outputs.len() + 1 != self.ranks.len()
4322 || combine.width != plan.input_width
4323 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4324 || combine.max_tokens != plan.max_tokens
4325 || combine.max_pairs != plan.max_pairs
4326 || combine.output.len() < output_capacity
4327 || combine
4328 .peer_outputs
4329 .iter()
4330 .any(|output| output.len() < output_capacity)
4331 {
4332 return Err("Step owner-grouped combine/projection geometry changed".into());
4333 }
4334 if self
4335 .ranks
4336 .iter()
4337 .skip(1)
4338 .zip(&combine.peer_devices)
4339 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4340 {
4341 return Err("Step owner-grouped combine peer devices changed".into());
4342 }
4343 let owner_pairs = plan
4344 .owners
4345 .iter()
4346 .map(|owner| owner.global_pairs.as_slice())
4347 .collect::<Vec<_>>();
4348 let shape = validate_weighted_route_combine(
4349 combine.width,
4350 combine.experts_per_token,
4351 combine.max_tokens,
4352 plan.tokens,
4353 &owner_pairs,
4354 route_weights,
4355 )?;
4356 if shape.max_pairs != combine.max_pairs {
4357 return Err("Step owner-grouped combine capacity changed during refresh".into());
4358 }
4359 let metadata = owner_pairs
4360 .iter()
4361 .map(|pairs| {
4362 let token_rows = pairs
4363 .iter()
4364 .map(|&pair| (pair / combine.experts_per_token) as i32)
4365 .collect::<Vec<_>>();
4366 let slots = pairs
4367 .iter()
4368 .map(|&pair| (pair % combine.experts_per_token) as i32)
4369 .collect::<Vec<_>>();
4370 let weights = pairs
4371 .iter()
4372 .map(|&pair| route_weights[pair])
4373 .collect::<Vec<_>>();
4374 (token_rows, slots, weights)
4375 })
4376 .collect::<Vec<_>>();
4377
4378 combine.ready = false;
4379 combine.output_generation = None;
4380 combine.broadcast_generation = None;
4381 let root = self
4382 .ranks
4383 .first()
4384 .ok_or("Step owner-grouped combine has no root rank")?;
4385 let _main = root.gpu.enter_main()?;
4386 if root.ctx().ordinal() != combine.root_device {
4387 return Err(format!(
4388 "Step owner-grouped combine root device changed {} != {}",
4389 root.ctx().ordinal(),
4390 combine.root_device
4391 )
4392 .into());
4393 }
4394 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4395 if token_rows.is_empty() {
4396 owner.active_pairs = 0;
4397 continue;
4398 }
4399 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4400 root.htod_i32_into(&mut owner.slots, &slots)?;
4401 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4402 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4403 owner.active_pairs = token_rows.len();
4404 }
4405 combine.tokens = plan.tokens;
4406 combine.pairs = shape.pairs;
4407 combine.projection_generation = plan.generation;
4408 combine.ready = true;
4409 Ok(())
4410 }
4411
4412 pub fn execute_step_grouped_expert_parallel_combine(
4413 &self,
4414 plan: &PreparedStepGroupedExpertParallelGate,
4415 combine: &mut PreparedPeerWeightedRouteCombine,
4416 ) -> Result<(), Box<dyn std::error::Error>> {
4417 if !plan.ready
4418 || plan.executed_generation != Some(plan.generation)
4419 || !combine.ready
4420 || combine.tokens != plan.tokens
4421 || combine.pairs != plan.pairs
4422 || combine.width != plan.input_width
4423 || combine.owners.len() != plan.owners.len()
4424 || combine.projection_generation != plan.generation
4425 {
4426 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4427 }
4428 combine.output_generation = None;
4429 combine.broadcast_generation = None;
4430 for owner in &plan.owners {
4431 if owner.rank == 0 || owner.global_pairs.is_empty() {
4432 continue;
4433 }
4434 let engine = &self.ranks[owner.rank];
4435 let _main = engine.gpu.enter_main()?;
4436 engine.stream().synchronize()?;
4437 }
4438 let root = self
4439 .ranks
4440 .first()
4441 .ok_or("Step owner-grouped combine has no root rank")?;
4442 let _main = root.gpu.enter_main()?;
4443 if root.ctx().ordinal() != combine.root_device {
4444 return Err("Step owner-grouped combine is not resident on the root device".into());
4445 }
4446 for (index, owner) in plan.owners.iter().enumerate() {
4447 let metadata = &combine.owners[index];
4448 if owner.global_pairs.len() != metadata.active_pairs {
4449 return Err(format!(
4450 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4451 owner.global_pairs.len(),
4452 metadata.active_pairs
4453 )
4454 .into());
4455 }
4456 if metadata.active_pairs == 0 {
4457 continue;
4458 }
4459 let values = metadata
4460 .active_pairs
4461 .checked_mul(combine.width)
4462 .ok_or("Step owner-grouped combine peer value count overflow")?;
4463 if owner.rank == 0 {
4464 root.scatter_slot(
4465 owner.down_workspace.output(),
4466 &metadata.token_rows,
4467 &metadata.slots,
4468 &metadata.weights,
4469 &mut combine.slots,
4470 &mut combine.weights,
4471 combine.width,
4472 combine.experts_per_token,
4473 metadata.active_pairs,
4474 )?;
4475 } else {
4476 let source = owner.down_workspace.output().slice(0..values);
4477 let mut destination = combine.peer_staging.slice_mut(0..values);
4478 root.stream().memcpy_dtod(&source, &mut destination)?;
4479 root.scatter_slot(
4480 &combine.peer_staging,
4481 &metadata.token_rows,
4482 &metadata.slots,
4483 &metadata.weights,
4484 &mut combine.slots,
4485 &mut combine.weights,
4486 combine.width,
4487 combine.experts_per_token,
4488 metadata.active_pairs,
4489 )?;
4490 }
4491 }
4492 root.reduce_slots_host(
4493 &combine.slots,
4494 &combine.weights,
4495 &mut combine.output,
4496 combine.width,
4497 combine.experts_per_token,
4498 combine.tokens,
4499 )?;
4500 combine.output_generation = Some(plan.generation);
4501 Ok(())
4502 }
4503
4504 pub fn collect_step_grouped_expert_parallel_combine(
4505 &self,
4506 plan: &PreparedStepGroupedExpertParallelGate,
4507 combine: &PreparedPeerWeightedRouteCombine,
4508 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4509 if !plan.ready
4510 || combine.output_generation != Some(plan.generation)
4511 || combine.projection_generation != plan.generation
4512 {
4513 return Err("Step owner-grouped combine output is stale or has not executed".into());
4514 }
4515 let root = self
4516 .ranks
4517 .first()
4518 .ok_or("Step owner-grouped combine has no root rank")?;
4519 let _main = root.gpu.enter_main()?;
4520 if root.ctx().ordinal() != combine.root_device {
4521 return Err("Step owner-grouped combine is not resident on the root device".into());
4522 }
4523 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4524 }
4525
4526 pub fn copy_step_grouped_expert_parallel_combine_root(
4531 &self,
4532 plan: &PreparedStepGroupedExpertParallelGate,
4533 combine: &PreparedPeerWeightedRouteCombine,
4534 destination: &Engine,
4535 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4536 if !plan.ready
4537 || combine.output_generation != Some(plan.generation)
4538 || combine.projection_generation != plan.generation
4539 {
4540 return Err("Step owner-grouped combine output is stale or has not executed".into());
4541 }
4542 let root = self
4543 .ranks
4544 .first()
4545 .ok_or("Step owner-grouped combine has no root rank")?;
4546 if root.ctx().ordinal() != combine.root_device
4547 || destination.ctx().ordinal() != combine.root_device
4548 {
4549 return Err(format!(
4550 "Step owner-grouped combine root/destination devices {}/{} != {}",
4551 root.ctx().ordinal(),
4552 destination.ctx().ordinal(),
4553 combine.root_device,
4554 )
4555 .into());
4556 }
4557 let values = combine
4558 .tokens
4559 .checked_mul(combine.width)
4560 .ok_or("Step owner-grouped combine copy size overflow")?;
4561 {
4562 let _main = root.gpu.enter_main()?;
4563 root.stream().synchronize()?;
4564 }
4565 let _main = destination.gpu.enter_main()?;
4566 let mut output = destination.uninit(values)?;
4567 destination
4568 .stream()
4569 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4570 Ok(output)
4571 }
4572
4573 pub fn broadcast_step_grouped_expert_parallel_combine(
4574 &self,
4575 plan: &PreparedStepGroupedExpertParallelGate,
4576 combine: &mut PreparedPeerWeightedRouteCombine,
4577 ) -> Result<(), Box<dyn std::error::Error>> {
4578 if !plan.ready
4579 || combine.output_generation != Some(plan.generation)
4580 || combine.projection_generation != plan.generation
4581 || combine.peer_devices.len() + 1 != self.ranks.len()
4582 || combine.peer_outputs.len() + 1 != self.ranks.len()
4583 {
4584 return Err("Step owner-grouped combine output cannot be broadcast".into());
4585 }
4586 combine.broadcast_generation = None;
4587 let values = combine
4588 .tokens
4589 .checked_mul(combine.width)
4590 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4591 {
4592 let root = self
4593 .ranks
4594 .first()
4595 .ok_or("Step owner-grouped combine has no root rank")?;
4596 let _main = root.gpu.enter_main()?;
4597 if root.ctx().ordinal() != combine.root_device {
4598 return Err("Step owner-grouped combine root device changed".into());
4599 }
4600 root.stream().synchronize()?;
4601 }
4602 let source = &combine.output;
4603 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4604 let engine = &self.ranks[index + 1];
4605 let _main = engine.gpu.enter_main()?;
4606 if engine.ctx().ordinal() != combine.peer_devices[index] {
4607 return Err(format!(
4608 "Step owner-grouped combine peer {} device changed",
4609 index + 1
4610 )
4611 .into());
4612 }
4613 let mut destination = destination_buffer.slice_mut(0..values);
4614 engine
4615 .stream()
4616 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4617 }
4618 combine.broadcast_generation = Some(plan.generation);
4619 Ok(())
4620 }
4621
4622 pub fn collect_step_grouped_expert_parallel_broadcast(
4623 &self,
4624 plan: &PreparedStepGroupedExpertParallelGate,
4625 combine: &PreparedPeerWeightedRouteCombine,
4626 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4627 if !plan.ready
4628 || combine.output_generation != Some(plan.generation)
4629 || combine.broadcast_generation != Some(plan.generation)
4630 || combine.peer_outputs.len() + 1 != self.ranks.len()
4631 {
4632 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4633 }
4634 let values = combine
4635 .tokens
4636 .checked_mul(combine.width)
4637 .ok_or("Step owner-grouped combine collection size overflow")?;
4638 let mut outputs = Vec::with_capacity(self.ranks.len());
4639 {
4640 let root = &self.ranks[0];
4641 let _main = root.gpu.enter_main()?;
4642 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4643 }
4644 for (index, output) in combine.peer_outputs.iter().enumerate() {
4645 let engine = &self.ranks[index + 1];
4646 let _main = engine.gpu.enter_main()?;
4647 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4648 }
4649 Ok(outputs)
4650 }
4651
4652 pub fn finish_step_grouped_expert_parallel_layer(
4654 &self,
4655 plan: &PreparedStepGroupedExpertParallelGate,
4656 combine: &PreparedPeerWeightedRouteCombine,
4657 shared: &ResidentReplicatedDeviceRows,
4658 residual: &ResidentReplicatedDeviceRows,
4659 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4660 validate_replicated_device_rows(&self.ranks, shared)?;
4661 validate_replicated_device_rows(&self.ranks, residual)?;
4662 if !plan.ready
4663 || plan.executed_generation != Some(plan.generation)
4664 || combine.output_generation != Some(plan.generation)
4665 || combine.broadcast_generation != Some(plan.generation)
4666 || combine.projection_generation != plan.generation
4667 || combine.peer_outputs.len() + 1 != self.ranks.len()
4668 || shared.tokens != combine.tokens
4669 || residual.tokens != combine.tokens
4670 || shared.width != combine.width
4671 || residual.width != combine.width
4672 {
4673 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4674 }
4675 let values = combine
4676 .tokens
4677 .checked_mul(combine.width)
4678 .ok_or("Step full-layer output size overflow")?;
4679 let mut ranks = Vec::with_capacity(self.ranks.len());
4680 for rank in 0..self.ranks.len() {
4681 let engine = &self.ranks[rank];
4682 let _main = engine.gpu.enter_main()?;
4683 let routed = if rank == 0 {
4684 &combine.output
4685 } else {
4686 &combine.peer_outputs[rank - 1]
4687 };
4688 let mut ffn = engine.uninit(values)?;
4689 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4690 let mut output = engine.uninit(values)?;
4691 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4692 ranks.push(output);
4693 }
4694 Ok(ResidentReplicatedDeviceRows {
4695 ranks,
4696 tokens: combine.tokens,
4697 width: combine.width,
4698 })
4699 }
4700
4701 pub fn run_step_grouped_expert_parallel_combine(
4702 &self,
4703 plan: &PreparedStepGroupedExpertParallelGate,
4704 combine: &mut PreparedPeerWeightedRouteCombine,
4705 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4706 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
4707 self.collect_step_grouped_expert_parallel_combine(plan, combine)
4708 }
4709
4710 pub fn upload_tensor_parallel(
4711 &self,
4712 gate: E4m3ExpertBank<'_>,
4713 up: E4m3ExpertBank<'_>,
4714 down: E4m3ExpertBank<'_>,
4715 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
4716 gate.validate()?;
4717 up.validate()?;
4718 down.validate()?;
4719 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4720 return Err("TP gate/up/down expert counts differ".into());
4721 }
4722 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4723 return Err("TP gate/up dimensions differ".into());
4724 }
4725 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4726 return Err(format!(
4727 "TP down {}x{} does not invert gate/up {}x{}",
4728 down.out_features, down.in_features, gate.out_features, gate.in_features
4729 )
4730 .into());
4731 }
4732 let tp = self.ranks.len();
4733 validate_column_bank_shape(gate, tp)?;
4734 validate_column_bank_shape(up, tp)?;
4735 validate_row_bank_shape(down, tp)?;
4736
4737 let mut gate_ranks = Vec::with_capacity(tp);
4738 let mut up_ranks = Vec::with_capacity(tp);
4739 let mut down_ranks = Vec::with_capacity(tp);
4740 for (rank, engine) in self.ranks.iter().enumerate() {
4741 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
4742 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
4743 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
4744 }
4745 Ok(ResidentTensorParallel {
4746 bank: ResidentTpExpertBank {
4747 gate: gate_ranks,
4748 up: up_ranks,
4749 down: down_ranks,
4750 expert_count: gate.expert_count,
4751 input_width: gate.in_features,
4752 expert_width: gate.out_features,
4753 },
4754 })
4755 }
4756
4757 pub fn run_tensor_parallel_routes(
4758 &self,
4759 experts: &ResidentTensorParallel,
4760 input: &[f32],
4761 tokens: usize,
4762 selected: &[usize],
4763 route_weights: &[f32],
4764 experts_per_token: usize,
4765 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4766 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
4767 validate_activations(input, tokens, experts.bank.input_width)?;
4768 let pairs = tokens
4769 .checked_mul(experts_per_token)
4770 .ok_or("TP route count overflow")?;
4771 if selected.len() != pairs || route_weights.len() != pairs {
4772 return Err(format!(
4773 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
4774 {experts_per_token} ({pairs})",
4775 selected.len(),
4776 route_weights.len(),
4777 )
4778 .into());
4779 }
4780 if !route_weights.iter().all(|weight| weight.is_finite()) {
4781 return Err("TP route weights contain a non-finite value".into());
4782 }
4783
4784 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
4785 for token in 0..tokens {
4786 let input_row =
4787 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
4788 for slot in 0..experts_per_token {
4789 let pair = token * experts_per_token + slot;
4790 let expert = selected[pair];
4791 if expert >= experts.bank.expert_count {
4792 return Err(format!(
4793 "TP selected expert {expert} outside 0..{}",
4794 experts.bank.expert_count
4795 )
4796 .into());
4797 }
4798 let down = if self.native_p2p {
4799 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
4800 } else {
4801 let gate =
4802 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
4803 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
4804 let activated: Vec<f32> = gate
4805 .iter()
4806 .zip(&up)
4807 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4808 .collect();
4809 debug_assert_eq!(activated.len(), experts.bank.expert_width);
4810 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
4811 };
4812 let weight = route_weights[pair];
4813 for (sum, value) in output
4814 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
4815 .iter_mut()
4816 .zip(down)
4817 {
4818 *sum += weight * value;
4819 }
4820 }
4821 }
4822 Ok(output)
4823 }
4824
4825 fn run_column_bank_expert(
4826 &self,
4827 ranks: &[ResidentE4m3ExpertBankRank],
4828 expert: usize,
4829 input: &[f32],
4830 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4831 let local_out = ranks
4832 .first()
4833 .ok_or("TP column bank has no ranks")?
4834 .out_features;
4835 let mut gathered = vec![0.0f32; local_out * ranks.len()];
4836 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
4837 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
4838 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
4839 }
4840 Ok(gathered)
4841 }
4842
4843 fn run_row_bank_expert(
4844 &self,
4845 ranks: &[ResidentE4m3ExpertBankRank],
4846 expert: usize,
4847 input: &[f32],
4848 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4849 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
4850 if input.len() != local_in * ranks.len() {
4851 return Err(format!(
4852 "TP row input {} != {} ranks x {local_in}",
4853 input.len(),
4854 ranks.len()
4855 )
4856 .into());
4857 }
4858 let out_features = ranks[0].out_features;
4859 let mut reduced = vec![0.0f32; out_features];
4860 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
4861 let blocks = bank
4862 .k_blocks
4863 .ok_or("TP row bank is not packed in native K-block order")?;
4864 if blocks * FP8_BLOCK != local_in {
4865 return Err(format!(
4866 "TP row bank has {blocks} blocks but local input width is {local_in}"
4867 )
4868 .into());
4869 }
4870 for block in 0..blocks {
4871 let global_start = rank * local_in + block * FP8_BLOCK;
4872 let partial = run_resident_bank_expert_block(
4873 engine,
4874 bank,
4875 expert,
4876 block,
4877 &input[global_start..global_start + FP8_BLOCK],
4878 )?;
4879 for (sum, value) in reduced.iter_mut().zip(partial) {
4880 *sum += value;
4881 }
4882 }
4883 }
4884 Ok(reduced)
4885 }
4886
4887 fn run_tensor_parallel_expert_native(
4888 &self,
4889 bank: &ResidentTpExpertBank,
4890 expert: usize,
4891 input: &[f32],
4892 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4893 if !self.native_p2p || self.ranks.len() < 2 {
4894 return Err("native TP expert execution requires at least two P2P ranks".into());
4895 }
4896 let local_out = bank
4897 .gate
4898 .first()
4899 .ok_or("native TP gate bank has no ranks")?
4900 .out_features;
4901 if local_out * self.ranks.len() != bank.expert_width {
4902 return Err(format!(
4903 "native TP gate shards {}x{local_out} != expert width {}",
4904 self.ranks.len(),
4905 bank.expert_width
4906 )
4907 .into());
4908 }
4909
4910 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4913 let root_input = {
4914 let root = &self.ranks[0];
4915 let _main = root.gpu.enter_main()?;
4916 root.htod(input)?
4917 };
4918 rank_inputs.push(root_input);
4919 for engine in &self.ranks[1..] {
4920 let peer_input = {
4921 let _main = engine.gpu.enter_main()?;
4922 let mut peer_input = engine.uninit(input.len())?;
4923 engine
4924 .stream()
4925 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
4926 peer_input
4927 };
4928 rank_inputs.push(peer_input);
4929 }
4930
4931 let mut gate_shards = Vec::with_capacity(self.ranks.len());
4932 let mut up_shards = Vec::with_capacity(self.ranks.len());
4933 for rank in 0..self.ranks.len() {
4934 gate_shards.push(run_resident_bank_expert_device(
4935 &self.ranks[rank],
4936 &bank.gate[rank],
4937 expert,
4938 &rank_inputs[rank],
4939 1,
4940 )?);
4941 up_shards.push(run_resident_bank_expert_device(
4942 &self.ranks[rank],
4943 &bank.up[rank],
4944 expert,
4945 &rank_inputs[rank],
4946 1,
4947 )?);
4948 }
4949
4950 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
4954 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
4955 let activated = gate
4956 .iter()
4957 .zip(&up)
4958 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4959 .collect::<Vec<_>>();
4960 debug_assert_eq!(activated.len(), bank.expert_width);
4961
4962 let root_activated = {
4963 let root = &self.ranks[0];
4964 let _main = root.gpu.enter_main()?;
4965 root.htod(&activated)?
4966 };
4967 let mut rank_activated = Vec::with_capacity(self.ranks.len());
4968 for (rank, engine) in self.ranks.iter().enumerate() {
4969 let start = rank * local_out;
4970 let source = root_activated.slice(start..start + local_out);
4971 let local = {
4972 let _main = engine.gpu.enter_main()?;
4973 let mut local = engine.uninit(local_out)?;
4974 engine.stream().memcpy_dtod(&source, &mut local)?;
4975 local
4976 };
4977 rank_activated.push(local);
4978 }
4979
4980 let out_features = bank
4981 .down
4982 .first()
4983 .ok_or("native TP down bank has no ranks")?
4984 .out_features;
4985 let mut reduced = {
4986 let root = &self.ranks[0];
4987 let _main = root.gpu.enter_main()?;
4988 root.htod(&vec![0.0f32; out_features])?
4989 };
4990 let mut remote_partial_keepalive = Vec::new();
4991 for rank in 0..self.ranks.len() {
4992 let down = &bank.down[rank];
4993 let blocks = down
4994 .k_blocks
4995 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
4996 if blocks * FP8_BLOCK != local_out {
4997 return Err(format!(
4998 "native TP rank {rank} has {blocks} blocks but local activation width is \
4999 {local_out}"
5000 )
5001 .into());
5002 }
5003 for block in 0..blocks {
5004 let start = block * FP8_BLOCK;
5005 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5006 let partial = run_resident_bank_expert_block_device(
5007 &self.ranks[rank],
5008 down,
5009 expert,
5010 block,
5011 &input_block,
5012 )?;
5013 let root_partial = if rank == 0 {
5014 partial
5015 } else {
5016 let root = &self.ranks[0];
5017 let _main = root.gpu.enter_main()?;
5018 let mut peer_partial = root.uninit(out_features)?;
5019 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5020 remote_partial_keepalive.push(partial);
5021 peer_partial
5022 };
5023 let next = {
5024 let root = &self.ranks[0];
5025 let _main = root.gpu.enter_main()?;
5026 let mut next = root.uninit(out_features)?;
5027 root.add(&reduced, &root_partial, &mut next, out_features)?;
5028 next
5029 };
5030 reduced = next;
5031 }
5032 }
5033 let output = {
5034 let root = &self.ranks[0];
5035 let _main = root.gpu.enter_main()?;
5036 root.dtoh(&reduced)?
5037 };
5038 drop(remote_partial_keepalive);
5039 Ok(output)
5040 }
5041
5042 pub fn gather_native_column_shards_device(
5044 &self,
5045 shards: &[CudaSlice<f32>],
5046 tokens: usize,
5047 local_out: usize,
5048 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5049 let shard_len = tokens
5050 .checked_mul(local_out)
5051 .ok_or("native TP gather shard size overflow")?;
5052 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5053 return Err("native TP gather shard geometry mismatch".into());
5054 }
5055 for engine in &self.ranks[1..] {
5059 let _main = engine.gpu.enter_main()?;
5060 engine.stream().synchronize()?;
5061 }
5062 let root = &self.ranks[0];
5063 let _main = root.gpu.enter_main()?;
5064 let global_out = shards
5065 .len()
5066 .checked_mul(local_out)
5067 .ok_or("native TP gather output width overflow")?;
5068 let gathered_len = tokens
5069 .checked_mul(global_out)
5070 .ok_or("native TP gather output size overflow")?;
5071 let mut gathered = root.uninit(gathered_len)?;
5072 if self.bulk_p2p {
5073 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5074 if shards.len() > 1 {
5075 let mut staging = root.uninit(shard_len)?;
5076 for (rank, shard) in shards.iter().enumerate().skip(1) {
5077 root.stream().memcpy_dtod(shard, &mut staging)?;
5078 root.place_rows_strided(
5079 &staging,
5080 &mut gathered,
5081 local_out,
5082 tokens,
5083 global_out,
5084 rank * local_out,
5085 )?;
5086 }
5087 }
5088 } else {
5089 for token in 0..tokens {
5090 for (rank, shard) in shards.iter().enumerate() {
5091 let source = shard.slice(token * local_out..(token + 1) * local_out);
5092 let start = token * global_out + rank * local_out;
5093 let mut destination = gathered.slice_mut(start..start + local_out);
5094 root.stream().memcpy_dtod(&source, &mut destination)?;
5095 }
5096 }
5097 }
5098 Ok(gathered)
5099 }
5100
5101 pub fn gather_native_column_shards(
5102 &self,
5103 shards: &[CudaSlice<f32>],
5104 tokens: usize,
5105 local_out: usize,
5106 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5107 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5108 let root = &self.ranks[0];
5109 let _main = root.gpu.enter_main()?;
5110 root.dtoh(&gathered)
5111 }
5112
5113 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5114 &self.decode_v2
5115 }
5116
5117 pub(crate) fn decode_v2_ensure(
5126 &self,
5127 e: &Engine,
5128 q_m: &ResidentBf16ColumnParallel,
5129 k_m: &ResidentBf16ColumnParallel,
5130 v_m: &ResidentBf16ColumnParallel,
5131 o_m: &ResidentStepBf16RowParallel,
5132 heads: usize,
5133 ) -> Result<usize, Box<dyn std::error::Error>> {
5134 if self.ranks.len() > 1 && !self.native_p2p {
5135 return Err("step TP decode v2 requires native P2P ranks".into());
5136 }
5137 let ranks = self.ranks.len();
5138 let fused_door = step_tp_qkv_fused_enabled()?;
5142 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5143 ResidentBf16Weight::F32(_) => true,
5144 ResidentBf16Weight::Bf16(_) => fused_door,
5145 };
5146 for matrix in [q_m, k_m, v_m] {
5147 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5148 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5149 return Err("step TP decode v2 QKV geometry mismatch".into());
5150 }
5151 for rank in &matrix.ranks {
5152 if !arm_ok(&rank.weight) {
5153 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5154 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5155 .into());
5156 }
5157 }
5158 }
5159 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5160 for blocks in &o_m.ranks {
5161 for block in blocks {
5162 if !arm_ok(&block.weight) {
5163 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5164 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5165 .into());
5166 }
5167 }
5168 }
5169 if v_m.out_features != k_m.out_features
5170 || o_m.in_features != q_m.out_features
5171 || heads == 0
5172 || heads % ranks != 0
5173 {
5174 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5175 }
5176 let local_q_dim = q_m.out_features / ranks;
5177 let local_kv_dim = k_m.out_features / ranks;
5178 let o_out = o_m.out_features;
5179 let o_block_cols = o_m.canonical_chunk_cols;
5180 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5181 if blocks_per_rank == 0
5182 || o_m
5183 .ranks
5184 .iter()
5185 .any(|blocks| blocks.len() != blocks_per_rank)
5186 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5187 {
5188 return Err("step TP decode v2 O canonical block grid mismatch".into());
5189 }
5190
5191 let mut guard = self
5192 .decode_v2
5193 .lock()
5194 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5195 if let Some(index) = guard.iter().position(|ws| {
5196 ws.local_q_dim == local_q_dim
5197 && ws.local_kv_dim == local_kv_dim
5198 && ws.heads == heads
5199 && ws.o_out == o_out
5200 && ws.o_block_cols == o_block_cols
5201 && ws.blocks_per_rank == blocks_per_rank
5202 && ws.e_device == e.ctx().ordinal()
5203 && ws.q.len() == ranks
5204 }) {
5205 return Ok(index);
5206 }
5207
5208 let mut q_raw = Vec::with_capacity(ranks);
5209 let mut k_raw = Vec::with_capacity(ranks);
5210 let mut v_raw = Vec::with_capacity(ranks);
5211 let mut q = Vec::with_capacity(ranks);
5212 let mut k = Vec::with_capacity(ranks);
5213 let mut pos = Vec::with_capacity(ranks);
5214 let mut gate = Vec::with_capacity(ranks);
5215 let mut attn_out = Vec::with_capacity(ranks);
5216 let mut gated = Vec::with_capacity(ranks);
5217 let mut fuse_ctr = Vec::with_capacity(ranks);
5218 let mut o_partials = Vec::with_capacity(ranks);
5219 let mut ev_rank = Vec::with_capacity(ranks);
5220 let direct_join = oproj_direct_on();
5221 for (rank, engine) in self.ranks.iter().enumerate() {
5222 let _main = engine.gpu.enter_main()?;
5223 q_raw.push(engine.uninit(local_q_dim)?);
5224 k_raw.push(engine.uninit(local_kv_dim)?);
5225 v_raw.push(engine.uninit(local_kv_dim)?);
5226 q.push(engine.uninit(local_q_dim)?);
5227 k.push(engine.uninit(local_kv_dim)?);
5228 pos.push(engine.htod_i32(&[0])?);
5229 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5230 gate.push(engine.uninit(heads / ranks)?);
5231 attn_out.push(engine.uninit(local_q_dim)?);
5232 gated.push(engine.uninit(local_q_dim)?);
5233 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5234 for _ in 0..blocks_per_rank {
5235 if direct_join && rank != 0 {
5238 let root = &self.ranks[0];
5239 let _root_main = root.gpu.enter_main()?;
5240 rank_partials.push(root.uninit(o_out)?);
5241 } else {
5242 rank_partials.push(engine.uninit(o_out)?);
5243 }
5244 }
5245 o_partials.push(rank_partials);
5246 ev_rank.push(engine.ctx().new_event(None)?);
5247 }
5248 let root = &self.ranks[0];
5249 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5250 let _main = root.gpu.enter_main()?;
5251 (
5252 root.uninit(o_out)?,
5253 root.uninit(o_out)?,
5254 root.uninit(o_out)?,
5255 root.htod(&vec![0.0f32; o_out])?,
5256 root.uninit(ranks * local_kv_dim)?,
5257 root.uninit(ranks * local_kv_dim)?,
5258 root.ctx().new_event(None)?,
5259 root.ctx().new_event(None)?,
5260 )
5261 };
5262 let (gate_e, ev_entry) = {
5263 let _main = e.gpu.enter_main()?;
5264 (e.uninit(heads)?, e.ctx().new_event(None)?)
5265 };
5266 let raw_attn_in = Vec::new();
5267 let raw_pos = Vec::new();
5268 guard.push(StepTpDecodeV2Ws {
5269 tcol_q: Vec::new(),
5270 tcol_k: Vec::new(),
5271 tcol_v: Vec::new(),
5272 tcol_g: Vec::new(),
5273 tcol_in: Vec::new(),
5274 tcol_cap: 0,
5275 fa2_q: Vec::new(),
5276 fa2_gate: Vec::new(),
5277 fa2_gated: Vec::new(),
5278 fa2_cap: 0,
5279 tcol_gated: Vec::new(),
5280 tcol_opart: Vec::new(),
5281 tcol_opeer: None,
5282 tcol_omix: None,
5283 tcol_ocap: 0,
5284 q_raw,
5285 k_raw,
5286 v_raw,
5287 q,
5288 k,
5289 pos,
5290 fuse_ctr,
5291 gate,
5292 attn_out,
5293 gated,
5294 o_partials,
5295 ev_rank,
5296 peer_partial,
5297 reduce_a,
5298 reduce_b,
5299 zeros,
5300 k_shadow,
5301 v_shadow,
5302 ev_refresh,
5303 ev_oproj,
5304 gate_e,
5305 attn_in: Vec::new(),
5306 h_stage: None,
5307 pos_stage: None,
5308 raw_h_stage: 0,
5309 raw_pos_stage: 0,
5310 raw_attn_in,
5311 raw_pos,
5312 raw_o_partial1: 0,
5313 raw_peer_partial: 0,
5314 raw_k1: 0,
5315 raw_v1: 0,
5316 raw_k_shadow: 0,
5317 raw_v_shadow: 0,
5318 raw_mixed_stage_e: 0,
5319 raw_reduce_a: 0,
5320 raw_shadow_stage_e: (0, 0),
5321 ev_entry,
5322 e_device: e.ctx().ordinal(),
5323 local_q_dim,
5324 local_kv_dim,
5325 heads,
5326 o_out,
5327 o_block_cols,
5328 blocks_per_rank,
5329 });
5330 eprintln!(
5331 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5332 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5333 residency=persistent ordering=evented performance_claim=false"
5334 );
5335 Ok(guard.len() - 1)
5336 }
5337
5338 #[allow(clippy::too_many_arguments)]
5346 #[allow(clippy::too_many_arguments)]
5351 pub fn decode_v2_input_qkv_tcol(
5352 &self,
5353 ws_index: usize,
5354 e: &Engine,
5355 h_t: &CudaSlice<f32>,
5356 t: usize,
5357 q_m: &ResidentBf16ColumnParallel,
5358 k_m: &ResidentBf16ColumnParallel,
5359 v_m: &ResidentBf16ColumnParallel,
5360 gate_shards: Option<StepTpGateShards<'_>>,
5361 ) -> Result<(), Box<dyn std::error::Error>> {
5362 let ranks = self.ranks.len();
5363 let mut guard = self
5364 .decode_v2
5365 .lock()
5366 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5367 let ws = guard
5368 .get_mut(ws_index)
5369 .ok_or("step TP decode v2 workspace index out of range")?;
5370 let in_f = q_m.in_features;
5371 if h_t.len() < t * in_f || t == 0 || t > 8 {
5372 return Err("decode_v2_input_qkv_tcol geometry".into());
5373 }
5374 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5376 ws.tcol_q.clear();
5377 ws.tcol_k.clear();
5378 ws.tcol_v.clear();
5379 ws.tcol_g.clear();
5380 ws.tcol_in.clear();
5381 for engine in &self.ranks {
5382 let _m = engine.gpu.enter_main()?;
5383 ws.tcol_q.push(engine.uninit(8 * ws.local_q_dim)?);
5384 ws.tcol_k.push(engine.uninit(8 * ws.local_kv_dim)?);
5385 ws.tcol_v.push(engine.uninit(8 * ws.local_kv_dim)?);
5386 ws.tcol_g
5387 .push(engine.uninit(8 * (ws.heads / ranks).max(1))?);
5388 ws.tcol_in.push(engine.uninit(8 * in_f)?);
5389 }
5390 ws.tcol_cap = 8;
5391 }
5392 use cudarc::driver::DevicePtr;
5394 let raw_src = {
5395 let _main = e.gpu.enter_main()?;
5396 let stream = e.stream();
5397 let (p, _g) = h_t.device_ptr(&stream);
5398 ws.ev_entry.record(&stream)?;
5399 p as u64
5400 };
5401 for rank in 0..ranks {
5402 let engine = &self.ranks[rank];
5403 let _main = engine.gpu.enter_main()?;
5404 engine.stream().wait(&ws.ev_entry)?;
5405 let raw_dst = {
5406 let stream = engine.stream();
5407 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5408 p as u64
5409 };
5410 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5411 let out_g = match &gate_shards {
5412 Some(_) => ws.heads / ranks,
5413 None => 0,
5414 };
5415 match (
5416 &q_m.ranks[rank].weight,
5417 &k_m.ranks[rank].weight,
5418 &v_m.ranks[rank].weight,
5419 ) {
5420 (
5421 ResidentBf16Weight::Bf16(wq),
5422 ResidentBf16Weight::Bf16(wk),
5423 ResidentBf16Weight::Bf16(wv),
5424 ) => {
5425 let wg = match &gate_shards {
5426 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5427 Some(StepTpGateShards::F32(_)) => {
5428 return Err(
5429 "tcol verify: gate shard class does not match bf16 QKV".into()
5430 );
5431 }
5432 None => wq,
5433 };
5434 let StepTpDecodeV2Ws {
5435 tcol_q,
5436 tcol_k,
5437 tcol_v,
5438 tcol_g,
5439 tcol_in,
5440 local_q_dim,
5441 local_kv_dim,
5442 ..
5443 } = &mut *ws;
5444 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5447 let refk = *REFK
5448 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5449 if refk {
5450 let lq = *local_q_dim;
5451 let lkv = *local_kv_dim;
5452 let mut hrow = engine.uninit(in_f)?;
5453 let mut qr = engine.uninit(lq)?;
5454 let mut kr = engine.uninit(lkv)?;
5455 let mut vr = engine.uninit(lkv)?;
5456 let mut gr = engine.uninit(out_g.max(1))?;
5457 for c in 0..t {
5458 {
5459 let mut dst = hrow.slice_mut(0..in_f);
5460 engine.stream().memcpy_dtod(
5461 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5462 &mut dst,
5463 )?;
5464 }
5465 engine.matvec_bf16_qkvg_into(
5466 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5467 lq, lkv, out_g,
5468 )?;
5469 let stream = engine.stream();
5470 {
5471 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5472 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5473 }
5474 {
5475 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5476 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5477 }
5478 {
5479 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5480 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5481 }
5482 if out_g > 0 {
5483 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5484 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5485 }
5486 }
5487 } else {
5488 engine.matvec_bf16_qkvg_tcol_into(
5489 wq,
5490 wk,
5491 wv,
5492 wg,
5493 &tcol_in[rank],
5494 &mut tcol_q[rank],
5495 &mut tcol_k[rank],
5496 &mut tcol_v[rank],
5497 &mut tcol_g[rank],
5498 in_f,
5499 *local_q_dim,
5500 *local_kv_dim,
5501 out_g,
5502 t,
5503 )?;
5504 }
5505 }
5506 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5507 }
5508 }
5509 Ok(())
5510 }
5511
5512 pub(crate) fn decode_v2_oproj_tcol_eligible(
5516 &self,
5517 ws: &StepTpDecodeV2Ws,
5518 o_m: &ResidentStepBf16RowParallel,
5519 ) -> bool {
5520 self.ranks.len() == 2
5521 && ws.blocks_per_rank == 4
5522 && step_tp_qkv_fused_enabled().unwrap_or(false)
5523 && no_local_shadow_on()
5524 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5525 && o_m
5526 .ranks
5527 .iter()
5528 .flatten()
5529 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5530 }
5531
5532 pub(crate) fn decode_v2_stash_fa2(
5537 &self,
5538 ws: &mut StepTpDecodeV2Ws,
5539 e: &Engine,
5540 col: usize,
5541 ) -> Result<(), Box<dyn std::error::Error>> {
5542 let ranks = self.ranks.len();
5543 if col >= 2 {
5544 return Err("decode_v2_stash_fa2 column out of range".into());
5545 }
5546 let lq = ws.local_q_dim;
5547 let lg = (ws.heads / ranks).max(1);
5548 if ws.fa2_cap == 0 || ws.fa2_q.len() != ranks {
5549 ws.fa2_q.clear();
5550 ws.fa2_gate.clear();
5551 ws.fa2_gated.clear();
5552 for engine in &self.ranks {
5553 let _m = engine.gpu.enter_main()?;
5554 ws.fa2_q.push(engine.uninit(2 * lq)?);
5555 ws.fa2_gate.push(engine.uninit(2 * lg)?);
5556 ws.fa2_gated.push(engine.uninit(2 * lq)?);
5557 }
5558 ws.fa2_cap = 2;
5559 }
5560 for rank in 0..ranks {
5561 let engine = &self.ranks[rank];
5562 let _main = engine.gpu.enter_main()?;
5563 {
5564 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5565 engine
5566 .stream()
5567 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5568 }
5569 {
5570 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5571 engine
5572 .stream()
5573 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5574 }
5575 ws.ev_rank[rank].record(&engine.stream())?;
5576 }
5577 {
5578 let _main = e.gpu.enter_main()?;
5579 for ev in ws.ev_rank.iter() {
5580 e.stream().wait(ev)?;
5581 }
5582 }
5583 Ok(())
5584 }
5585
5586 #[allow(clippy::too_many_arguments)]
5593 pub(crate) fn decode_v2_spec_fa2_join(
5594 &self,
5595 ws_index: usize,
5596 e: &Engine,
5597 o_m: &ResidentStepBf16RowParallel,
5598 kv: &ResidentTpKvCache,
5599 head_dim: usize,
5600 window: usize,
5601 bucket_max: usize,
5602 scale: f32,
5603 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5604 let ranks = self.ranks.len();
5605 static ONCE: std::sync::Once = std::sync::Once::new();
5607 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
5608 {
5609 let mut guard = self
5610 .decode_v2
5611 .lock()
5612 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5613 let ws = guard
5614 .get_mut(ws_index)
5615 .ok_or("step TP decode v2 workspace index out of range")?;
5616 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
5617 return Err("spec fa2 join without stashed columns".into());
5618 }
5619 let lq = ws.local_q_dim;
5620 let local_heads = (ws.heads / ranks).max(1);
5621 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
5622 let capacity = kv.physical_capacity();
5623 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
5624 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
5626 ws.tcol_gated.clear();
5627 ws.tcol_opart.clear();
5628 for engine in &self.ranks {
5629 let _m = engine.gpu.enter_main()?;
5630 ws.tcol_gated.push(engine.uninit(8 * lq)?);
5631 ws.tcol_opart.push(engine.uninit(8 * ws.o_out)?);
5632 }
5633 let root = &self.ranks[0];
5634 let _m = root.gpu.enter_main()?;
5635 ws.tcol_opeer = Some(root.uninit(8 * ws.o_out)?);
5636 ws.tcol_omix = Some(root.uninit(8 * ws.o_out)?);
5637 ws.tcol_ocap = 8;
5638 }
5639 for rank in 0..ranks {
5640 let engine = &self.ranks[rank];
5641 let _main = engine.gpu.enter_main()?;
5642 let rank_cache = kv
5643 .rank(rank)
5644 .ok_or("spec fa2 join lost its KV cache rank")?;
5645 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
5646 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
5647 {
5648 let StepTpDecodeV2Ws {
5649 fa2_q,
5650 fa2_gate,
5651 fa2_gated,
5652 ..
5653 } = &mut *ws;
5654 engine.fa_decode_dcw2(
5655 &fa2_q[rank],
5656 &k_ring,
5657 &v_ring,
5658 &mut fa2_gated[rank],
5659 head_dim,
5660 local_heads,
5661 local_kv_heads,
5662 rank_cache.len_d(),
5663 rank_cache.base_d(),
5664 window,
5665 bucket_max,
5666 scale,
5667 k_tok_bytes,
5668 v_tok_bytes,
5669 &fa2_gate[rank],
5670 )?;
5671 }
5672 let StepTpDecodeV2Ws {
5675 fa2_gated,
5676 tcol_gated,
5677 ..
5678 } = &mut *ws;
5679 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
5680 engine
5681 .stream()
5682 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
5683 }
5684 }
5685 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
5686 }
5687
5688 pub(crate) fn decode_v2_stash_gated(
5693 &self,
5694 ws: &mut StepTpDecodeV2Ws,
5695 e: &Engine,
5696 col: usize,
5697 ) -> Result<(), Box<dyn std::error::Error>> {
5698 let ranks = self.ranks.len();
5699 if col >= 8 {
5700 return Err("decode_v2_stash_gated column out of range".into());
5701 }
5702 let lq = ws.local_q_dim;
5703 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
5704 ws.tcol_gated.clear();
5705 ws.tcol_opart.clear();
5706 for engine in &self.ranks {
5707 let _m = engine.gpu.enter_main()?;
5708 ws.tcol_gated.push(engine.uninit(8 * lq)?);
5709 ws.tcol_opart.push(engine.uninit(8 * ws.o_out)?);
5710 }
5711 let root = &self.ranks[0];
5712 let _m = root.gpu.enter_main()?;
5713 ws.tcol_opeer = Some(root.uninit(8 * ws.o_out)?);
5714 ws.tcol_omix = Some(root.uninit(8 * ws.o_out)?);
5715 ws.tcol_ocap = 8;
5716 }
5717 for rank in 0..ranks {
5718 let engine = &self.ranks[rank];
5719 let _main = engine.gpu.enter_main()?;
5720 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
5721 engine
5722 .stream()
5723 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
5724 ws.ev_rank[rank].record(&engine.stream())?;
5728 }
5729 {
5730 let _main = e.gpu.enter_main()?;
5731 for ev in ws.ev_rank.iter() {
5732 e.stream().wait(ev)?;
5733 }
5734 }
5735 Ok(())
5736 }
5737
5738 pub(crate) fn decode_v2_oproj_tcol(
5744 &self,
5745 ws_index: usize,
5746 e: &Engine,
5747 o_m: &ResidentStepBf16RowParallel,
5748 t: usize,
5749 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5750 let ranks = self.ranks.len();
5751 let mut guard = self
5752 .decode_v2
5753 .lock()
5754 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5755 let ws = guard
5756 .get_mut(ws_index)
5757 .ok_or("step TP decode v2 workspace index out of range")?;
5758 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 8 || ws.tcol_ocap < t {
5759 return Err("decode_v2_oproj_tcol geometry".into());
5760 }
5761 for rank in 0..ranks {
5762 let engine = &self.ranks[rank];
5763 let _main = engine.gpu.enter_main()?;
5764 let mut weights = Vec::with_capacity(4);
5765 for block in 0..4 {
5766 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
5767 return Err("tcol o_proj requires bf16-resident O blocks".into());
5768 };
5769 weights.push(weight);
5770 }
5771 {
5772 let StepTpDecodeV2Ws {
5773 tcol_gated,
5774 tcol_opart,
5775 local_q_dim,
5776 o_block_cols,
5777 o_out,
5778 ..
5779 } = &mut *ws;
5780 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5783 let refk = *REFK
5784 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
5785 if refk {
5786 let lq = *local_q_dim;
5787 let mut xr = engine.uninit(lq)?;
5788 let mut yr = engine.uninit(*o_out)?;
5789 for c in 0..t {
5790 {
5791 let mut dst = xr.slice_mut(0..lq);
5792 engine.stream().memcpy_dtod(
5793 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
5794 &mut dst,
5795 )?;
5796 }
5797 engine.matvec_bf16_b4_into(
5798 [weights[0], weights[1], weights[2], weights[3]],
5799 &xr,
5800 &mut yr,
5801 *o_block_cols,
5802 *o_out,
5803 )?;
5804 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
5805 engine
5806 .stream()
5807 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
5808 }
5809 } else {
5810 engine.matvec_bf16_b4_tcol_into(
5811 [weights[0], weights[1], weights[2], weights[3]],
5812 &tcol_gated[rank],
5813 &mut tcol_opart[rank],
5814 *o_block_cols,
5815 *o_out,
5816 t,
5817 )?;
5818 }
5819 }
5820 if rank != 0 {
5821 ws.ev_rank[rank].record(&engine.stream())?;
5822 }
5823 }
5824 let root = &self.ranks[0];
5825 {
5826 let _main = root.gpu.enter_main()?;
5827 for ev in ws.ev_rank.iter().skip(1) {
5828 root.stream().wait(ev)?;
5829 }
5830 {
5831 let StepTpDecodeV2Ws {
5832 tcol_opart,
5833 tcol_opeer,
5834 tcol_omix,
5835 o_out,
5836 ..
5837 } = &mut *ws;
5838 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
5839 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
5840 {
5841 let mut dst = opeer.slice_mut(0..t * *o_out);
5842 root.stream()
5843 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
5844 }
5845 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
5848 }
5849 ws.ev_oproj.record(&root.stream())?;
5850 }
5851 let _main = e.gpu.enter_main()?;
5852 e.stream().wait(&ws.ev_oproj)?;
5853 let mut out = e.uninit(t * ws.o_out)?;
5854 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
5855 e.stream().memcpy_dtod(
5856 &omix.slice(0..t * ws.o_out),
5857 &mut out.slice_mut(0..t * ws.o_out),
5858 )?;
5859 Ok(out)
5860 }
5861
5862 pub(crate) fn decode_v2_input_qkv(
5863 &self,
5864 ws: &mut StepTpDecodeV2Ws,
5865 e: &Engine,
5866 h: &CudaSlice<f32>,
5867 pos_d: &CudaSlice<i32>,
5868 gate_raw: Option<&CudaSlice<f32>>,
5869 gate_shards: Option<StepTpGateShards<'_>>,
5870 decode_input: &mut ResidentReplicatedDeviceRows,
5871 q_m: &ResidentBf16ColumnParallel,
5872 k_m: &ResidentBf16ColumnParallel,
5873 v_m: &ResidentBf16ColumnParallel,
5874 q_norm: &[CudaSlice<f32>],
5875 k_norm: &[CudaSlice<f32>],
5876 head_dim: usize,
5877 n_rot: usize,
5878 rope_base: f32,
5879 rope_freqs: &[Option<&CudaSlice<f32>>],
5880 rms_eps: f32,
5881 defer_norm_rope: bool,
5882 tcol_col: Option<usize>,
5883 ) -> Result<(), Box<dyn std::error::Error>> {
5884 let ranks = self.ranks.len();
5885 validate_replicated_device_rows(&self.ranks, decode_input)?;
5886 if decode_input.tokens != 1
5887 || decode_input.width != q_m.in_features
5888 || pos_d.len() != 1
5889 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
5890 || gate_raw.is_none() != gate_shards.is_some()
5891 || gate_shards.as_ref().is_some_and(|shards| match shards {
5892 StepTpGateShards::F32(shards) => shards.len() != ranks,
5893 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
5894 })
5895 || q_norm.len() != ranks
5896 || k_norm.len() != ranks
5897 || rope_freqs.len() != ranks
5898 || e.ctx().ordinal() != ws.e_device
5899 {
5900 return Err("step TP decode v2 input geometry mismatch".into());
5901 }
5902
5903 let qkv_fused = step_tp_qkv_fused_enabled()?;
5904 if gate_shards.is_some() && !qkv_fused {
5905 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
5906 }
5907 let values = decode_input.width;
5908 if h.len() != values {
5909 return Err(format!(
5910 "step TP decode v2 hidden width {} != replicated width {values}",
5911 h.len()
5912 )
5913 .into());
5914 }
5915
5916 if qkv_fused {
5917 if ws.h_stage.is_none() {
5921 use cudarc::driver::DevicePtr;
5922 let _main = e.gpu.enter_main()?;
5923 let h_stage = e.uninit(values)?;
5924 let pos_stage = e.htod_i32(&[0])?;
5925 {
5926 let stream = e.stream();
5927 let (hp, _g0) = h_stage.device_ptr(&stream);
5928 let (pp, _g1) = pos_stage.device_ptr(&stream);
5929 ws.raw_h_stage = hp as u64;
5930 ws.raw_pos_stage = pp as u64;
5931 }
5932 ws.h_stage = Some(h_stage);
5933 ws.pos_stage = Some(pos_stage);
5934 for rank in 0..ranks {
5935 use cudarc::driver::DevicePtr;
5936 let engine = &self.ranks[rank];
5937 let _rmain = engine.gpu.enter_main()?;
5938 let attn_in = engine.uninit(values)?;
5939 let (dp, pp) = {
5940 let stream = engine.stream();
5941 let (dp, _g2) = attn_in.device_ptr(&stream);
5942 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
5943 (dp as u64, pp as u64)
5944 };
5945 ws.raw_attn_in.push(dp);
5946 ws.raw_pos.push(pp);
5947 ws.attn_in.push(attn_in);
5948 }
5949 {
5950 use cudarc::driver::DevicePtr;
5951 let root = &self.ranks[0];
5952 let _rmain = root.gpu.enter_main()?;
5953 let stream = root.stream();
5954 let (a, _g) = ws.peer_partial.device_ptr(&stream);
5955 let (b, _g) = ws.k_shadow.device_ptr(&stream);
5956 let (c, _g) = ws.v_shadow.device_ptr(&stream);
5957 ws.raw_peer_partial = a as u64;
5958 ws.raw_k_shadow = b as u64;
5959 ws.raw_v_shadow = c as u64;
5960 }
5961 {
5962 use cudarc::driver::DevicePtr;
5963 let rank1 = &self.ranks[1];
5964 let _rmain = rank1.gpu.enter_main()?;
5965 let stream = rank1.stream();
5966 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
5967 let (b, _g) = ws.k[1].device_ptr(&stream);
5968 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
5969 ws.raw_o_partial1 = a as u64;
5970 ws.raw_k1 = b as u64;
5971 ws.raw_v1 = c as u64;
5972 }
5973 }
5974 {
5975 let _main = e.gpu.enter_main()?;
5976 {
5977 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
5980 let mut dst = h_stage.slice_mut(0..values);
5981 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
5982 }
5983 {
5984 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
5985 let mut dst = pos_stage.slice_mut(0..1);
5986 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
5987 }
5988 ws.ev_entry.record(&e.stream())?;
5989 }
5990 for rank in 0..ranks {
5991 let engine = &self.ranks[rank];
5992 let _main = engine.gpu.enter_main()?;
5993 engine.stream().wait(&ws.ev_entry)?;
5994 }
5995 } else {
5996 {
5998 let _main = e.gpu.enter_main()?;
5999 if let Some(gate_raw) = gate_raw {
6000 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6001 e.stream()
6002 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6003 }
6004 ws.ev_entry.record(&e.stream())?;
6005 }
6006 {
6007 let root = &self.ranks[0];
6008 let _main = root.gpu.enter_main()?;
6009 root.stream().wait(&ws.ev_entry)?;
6010 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6011 root.stream()
6012 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6013 ws.ev_refresh.record(&root.stream())?;
6014 }
6015 for rank in 1..ranks {
6016 let engine = &self.ranks[rank];
6017 let _main = engine.gpu.enter_main()?;
6018 engine.stream().wait(&ws.ev_refresh)?;
6019 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6020 let mut destination = peer_rows[0].slice_mut(0..values);
6021 engine
6022 .stream()
6023 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6024 }
6025 }
6026 for rank in 0..ranks {
6027 self.decode_v2_input_qkv_rank(
6028 ws,
6029 pos_d,
6030 decode_input,
6031 q_m,
6032 k_m,
6033 v_m,
6034 q_norm,
6035 k_norm,
6036 head_dim,
6037 n_rot,
6038 rope_base,
6039 rope_freqs,
6040 rms_eps,
6041 gate_shards.as_ref(),
6042 qkv_fused,
6043 defer_norm_rope,
6044 rank,
6045 tcol_col,
6046 )?;
6047 }
6048 Ok(())
6049 }
6050
6051 #[allow(clippy::too_many_arguments)]
6054 pub(crate) fn decode_v2_input_qkv_rank(
6055 &self,
6056 ws: &mut StepTpDecodeV2Ws,
6057 pos_d: &CudaSlice<i32>,
6058 decode_input: &mut ResidentReplicatedDeviceRows,
6059 q_m: &ResidentBf16ColumnParallel,
6060 k_m: &ResidentBf16ColumnParallel,
6061 v_m: &ResidentBf16ColumnParallel,
6062 q_norm: &[CudaSlice<f32>],
6063 k_norm: &[CudaSlice<f32>],
6064 head_dim: usize,
6065 n_rot: usize,
6066 rope_base: f32,
6067 rope_freqs: &[Option<&CudaSlice<f32>>],
6068 rms_eps: f32,
6069 gate_shards: Option<&StepTpGateShards<'_>>,
6070 qkv_fused: bool,
6071 defer_norm_rope: bool,
6072 rank: usize,
6073 tcol_col: Option<usize>,
6074 ) -> Result<(), Box<dyn std::error::Error>> {
6075 let ranks = self.ranks.len();
6076 let local_heads = ws.local_q_dim / head_dim;
6077 let local_kv_heads = ws.local_kv_dim / head_dim;
6078 let engine = &self.ranks[rank];
6079 let _main = engine.gpu.enter_main()?;
6080 let ws_e_device = ws.e_device;
6081 if qkv_fused && tcol_col.is_some() {
6086 let c = tcol_col.expect("checked");
6087 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6088 return Err("tcol select without precompute".into());
6089 }
6090 if engine.ctx().ordinal() != ws_e_device {
6094 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6095 }
6096 let StepTpDecodeV2Ws {
6097 tcol_q,
6098 tcol_k,
6099 tcol_v,
6100 tcol_g,
6101 q_raw,
6102 k_raw,
6103 v_raw,
6104 gate,
6105 local_q_dim,
6106 local_kv_dim,
6107 heads,
6108 ..
6109 } = &mut *ws;
6110 let lg = *heads / ranks;
6111 let stream = engine.stream();
6112 {
6113 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6114 stream.memcpy_dtod(
6115 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6116 &mut dst,
6117 )?;
6118 }
6119 {
6120 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6121 stream.memcpy_dtod(
6122 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6123 &mut dst,
6124 )?;
6125 }
6126 {
6127 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6128 stream.memcpy_dtod(
6129 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6130 &mut dst,
6131 )?;
6132 }
6133 if lg > 0 {
6134 let mut dst = gate[rank].slice_mut(0..lg);
6135 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6136 }
6137 if !defer_norm_rope {
6138 } else {
6142 return Ok(());
6143 }
6144 }
6145 if qkv_fused {
6146 let same_dev = engine.ctx().ordinal() == ws.e_device;
6151 if !same_dev {
6152 raw_copy_bytes(
6153 ws.raw_attn_in[rank],
6154 ws.raw_h_stage,
6155 q_m.in_features * 4,
6156 engine,
6157 )?;
6158 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6159 }
6160 let StepTpDecodeV2Ws {
6161 q_raw,
6162 k_raw,
6163 v_raw,
6164 gate,
6165 gate_e,
6166 attn_in,
6167 h_stage,
6168 heads,
6169 local_q_dim,
6170 local_kv_dim,
6171 ..
6172 } = &mut *ws;
6173 let input_ref: &CudaSlice<f32> = if same_dev {
6174 h_stage
6175 .as_ref()
6176 .ok_or("step TP decode v2 stage not armed")?
6177 } else {
6178 &attn_in[rank]
6179 };
6180 match (
6181 &q_m.ranks[rank].weight,
6182 &k_m.ranks[rank].weight,
6183 &v_m.ranks[rank].weight,
6184 ) {
6185 (
6186 ResidentBf16Weight::F32(wq),
6187 ResidentBf16Weight::F32(wk),
6188 ResidentBf16Weight::F32(wv),
6189 ) => {
6190 let (wg, out_g) = match &gate_shards {
6191 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6192 Some(StepTpGateShards::Bf16(_)) => {
6193 return Err("step TP decode v2 gate shard class does not \
6194 match the F32 projections"
6195 .into());
6196 }
6197 None => (&*gate_e, 0),
6199 };
6200 engine.matvec_f32_qkv_into(
6201 wq,
6202 wk,
6203 wv,
6204 wg,
6205 input_ref,
6206 &mut q_raw[rank],
6207 &mut k_raw[rank],
6208 &mut v_raw[rank],
6209 &mut gate[rank],
6210 q_m.in_features,
6211 *local_q_dim,
6212 *local_kv_dim,
6213 out_g,
6214 )?;
6215 }
6216 (
6217 ResidentBf16Weight::Bf16(wq),
6218 ResidentBf16Weight::Bf16(wk),
6219 ResidentBf16Weight::Bf16(wv),
6220 ) => {
6221 let (wg, out_g) = match &gate_shards {
6222 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6223 Some(StepTpGateShards::F32(_)) => {
6224 return Err("step TP decode v2 gate shard class does not \
6225 match the bf16 projections"
6226 .into());
6227 }
6228 None => (wq, 0),
6229 };
6230 engine.matvec_bf16_qkvg_into(
6231 wq,
6232 wk,
6233 wv,
6234 wg,
6235 input_ref,
6236 &mut q_raw[rank],
6237 &mut k_raw[rank],
6238 &mut v_raw[rank],
6239 &mut gate[rank],
6240 q_m.in_features,
6241 *local_q_dim,
6242 *local_kv_dim,
6243 out_g,
6244 )?;
6245 }
6246 _ => {
6247 return Err("step TP decode v2 QKV projections mix residency classes".into());
6248 }
6249 }
6250 } else {
6251 for (matrix, local_out, raw) in [
6252 (q_m, ws.local_q_dim, &mut ws.q_raw),
6253 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6254 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6255 ] {
6256 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6257 return Err("step TP decode v2 lost its F32 projection residency".into());
6258 };
6259 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6260 engine.linear_f32_resident_canonical_rows_t1_into(
6261 &decode_input.ranks[rank],
6262 values_w,
6263 &mut raw[rank],
6264 matrix.in_features,
6265 local_out,
6266 chunk_rows,
6267 )?;
6268 }
6269 }
6270 if qkv_fused && defer_norm_rope {
6271 } else if qkv_fused {
6273 let StepTpDecodeV2Ws {
6276 q_raw,
6277 k_raw,
6278 q,
6279 k,
6280 pos,
6281 pos_stage,
6282 ..
6283 } = &mut *ws;
6284 let same_dev = engine.ctx().ordinal() == ws_e_device;
6285 let pos_ref: &CudaSlice<i32> = if same_dev {
6286 pos_stage
6287 .as_ref()
6288 .ok_or("step TP decode v2 pos stage not armed")?
6289 } else {
6290 &pos[rank]
6291 };
6292 engine.qk_norm_rope_into(
6293 &q_raw[rank],
6294 &k_raw[rank],
6295 &q_norm[rank],
6296 &k_norm[rank],
6297 &mut q[rank],
6298 &mut k[rank],
6299 pos_ref,
6300 head_dim,
6301 n_rot,
6302 local_heads,
6303 local_kv_heads,
6304 rms_eps,
6305 rope_base,
6306 1.0,
6307 rope_freqs[rank],
6308 )?;
6309 } else {
6310 engine.rms_norm(
6311 &ws.q_raw[rank],
6312 &q_norm[rank],
6313 &mut ws.q[rank],
6314 head_dim,
6315 local_heads,
6316 rms_eps,
6317 )?;
6318 engine.rms_norm(
6319 &ws.k_raw[rank],
6320 &k_norm[rank],
6321 &mut ws.k[rank],
6322 head_dim,
6323 local_kv_heads,
6324 rms_eps,
6325 )?;
6326 {
6327 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6328 engine
6329 .stream()
6330 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6331 }
6332 engine.rope_neox2(
6333 &mut ws.q[rank],
6334 &mut ws.k[rank],
6335 &ws.pos[rank],
6336 head_dim,
6337 n_rot,
6338 local_heads,
6339 local_kv_heads,
6340 1,
6341 rope_base,
6342 1.0,
6343 rope_freqs[rank],
6344 )?;
6345 }
6346 if gate_shards.is_none() {
6347 let gate_start = rank * (ws.heads / ranks);
6348 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6349 engine.stream().memcpy_dtod(
6350 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6351 &mut gate_dst,
6352 )?;
6353 }
6354 Ok(())
6355 }
6356
6357 pub(crate) fn decode_v2_finish_rank_partial(
6361 &self,
6362 ws: &mut StepTpDecodeV2Ws,
6363 o_m: &ResidentStepBf16RowParallel,
6364 o_fused: bool,
6365 rank: usize,
6366 ) -> Result<(), Box<dyn std::error::Error>> {
6367 let engine = &self.ranks[rank];
6368 let _main = engine.gpu.enter_main()?;
6369 if o_fused {
6370 let StepTpDecodeV2Ws {
6371 gated,
6372 o_partials,
6373 o_block_cols,
6374 o_out,
6375 ..
6376 } = &mut *ws;
6377 let all_f32 = o_m.ranks[rank]
6378 .iter()
6379 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6380 if all_f32 {
6381 let mut weights = Vec::with_capacity(4);
6382 for block in 0..4 {
6383 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6384 unreachable!("all_f32 checked above");
6385 };
6386 weights.push(weight);
6387 }
6388 engine.matvec_f32_b4_into(
6389 [weights[0], weights[1], weights[2], weights[3]],
6390 &gated[rank],
6391 &mut o_partials[rank][0],
6392 *o_block_cols,
6393 *o_out,
6394 )?;
6395 } else {
6396 let mut weights = Vec::with_capacity(4);
6397 for block in 0..4 {
6398 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6399 return Err("step TP decode v2 O projections mix residency classes".into());
6400 };
6401 weights.push(weight);
6402 }
6403 engine.matvec_bf16_b4_into(
6404 [weights[0], weights[1], weights[2], weights[3]],
6405 &gated[rank],
6406 &mut o_partials[rank][0],
6407 *o_block_cols,
6408 *o_out,
6409 )?;
6410 }
6411 } else {
6412 for block in 0..ws.blocks_per_rank {
6413 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6414 return Err("step TP decode v2 lost its F32 O residency".into());
6415 };
6416 let x =
6417 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
6418 let w = weight.slice(0..weight.len());
6419 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
6420 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
6421 }
6422 }
6423 Ok(())
6424 }
6425
6426 pub(crate) fn decode_v2_finish(
6434 &self,
6435 ws: &mut StepTpDecodeV2Ws,
6436 e: &Engine,
6437 o_m: &ResidentStepBf16RowParallel,
6438 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6439 let ranks = self.ranks.len();
6440 if e.ctx().ordinal() != ws.e_device {
6441 return Err("step TP decode v2 finish engine changed".into());
6442 }
6443 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
6448
6449 for rank in 0..ranks {
6452 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
6453 if rank == 0 {
6454 continue;
6457 }
6458 let engine = &self.ranks[rank];
6459 let _main = engine.gpu.enter_main()?;
6460 ws.ev_rank[rank].record(&engine.stream())?;
6461 }
6462
6463 let root = &self.ranks[0];
6465 #[allow(unused_assignments)]
6466 let mut final_in_a = false;
6467 {
6468 let _main = root.gpu.enter_main()?;
6469 for ev in ws.ev_rank.iter().skip(1) {
6470 root.stream().wait(ev)?;
6471 }
6472 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
6473 ws.ev_oproj.record(&root.stream())?;
6479 let _main = e.gpu.enter_main()?;
6480 e.stream().wait(&ws.ev_oproj)?;
6481 let mut output = e.uninit(ws.o_out)?;
6482 if oproj_tail_on() && oproj_tail_eligible() {
6483 use cudarc::driver::DevicePtr;
6486 let stream = e.stream();
6487 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
6488 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
6489 set_oproj_tail((p0 as u64, p1 as u64));
6490 return Ok(output);
6491 }
6492 e.add(
6493 &ws.o_partials[0][0],
6494 &ws.o_partials[1][0],
6495 &mut output,
6496 ws.o_out,
6497 )?;
6498 return Ok(output);
6499 }
6500 if o_fused {
6501 self.decode_v2_finish_root_fused(ws)?;
6502 ws.ev_oproj.record(&root.stream())?;
6503 let _main = e.gpu.enter_main()?;
6504 e.stream().wait(&ws.ev_oproj)?;
6505 let mut output = e.uninit(ws.o_out)?;
6506 e.stream().memcpy_dtod(
6507 &ws.reduce_a.slice(0..ws.o_out),
6508 &mut output.slice_mut(0..ws.o_out),
6509 )?;
6510 return Ok(output);
6511 }
6512 let mut first = true;
6513 let mut current_is_a = false;
6514 for rank in 0..ranks {
6515 for block in 0..ws.blocks_per_rank {
6516 let use_peer = rank != 0;
6517 if use_peer {
6518 root.stream()
6519 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
6520 }
6521 match (first, current_is_a, use_peer) {
6523 (true, _, true) => {
6524 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6525 }
6526 (true, _, false) => root.add(
6527 &ws.zeros,
6528 &ws.o_partials[0][block],
6529 &mut ws.reduce_a,
6530 ws.o_out,
6531 )?,
6532 (false, true, true) => {
6533 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
6534 }
6535 (false, true, false) => root.add(
6536 &ws.reduce_a,
6537 &ws.o_partials[0][block],
6538 &mut ws.reduce_b,
6539 ws.o_out,
6540 )?,
6541 (false, false, true) => {
6542 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6543 }
6544 (false, false, false) => root.add(
6545 &ws.reduce_b,
6546 &ws.o_partials[0][block],
6547 &mut ws.reduce_a,
6548 ws.o_out,
6549 )?,
6550 }
6551 current_is_a = first || !current_is_a;
6552 first = false;
6553 }
6554 }
6555 final_in_a = current_is_a;
6556
6557 for rank in 0..ranks {
6558 let start = rank * ws.local_kv_dim;
6559 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
6560 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
6561 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
6562 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
6563 }
6564 ws.ev_oproj.record(&root.stream())?;
6565 }
6566
6567 let _main = e.gpu.enter_main()?;
6571 e.stream().wait(&ws.ev_oproj)?;
6572 let mut output = e.uninit(ws.o_out)?;
6573 let source = if final_in_a {
6574 &ws.reduce_a
6575 } else {
6576 &ws.reduce_b
6577 };
6578 e.stream().memcpy_dtod(
6579 &source.slice(0..ws.o_out),
6580 &mut output.slice_mut(0..ws.o_out),
6581 )?;
6582 Ok(output)
6583 }
6584
6585 pub fn run_routed_experts(
6586 &self,
6587 experts: &ResidentExpertParallel,
6588 input: &[f32],
6589 tokens: usize,
6590 selected: &[usize],
6591 route_weights: &[f32],
6592 experts_per_token: usize,
6593 activation_limit: Option<f32>,
6594 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6595 validate_step_expert_activation_limit(activation_limit)?;
6596 validate_ep_residency(&self.ranks, experts)?;
6597 validate_activations(input, tokens, experts.input_width)?;
6598 let pairs = tokens
6599 .checked_mul(experts_per_token)
6600 .ok_or("EP route count overflow")?;
6601 if selected.len() != pairs || route_weights.len() != pairs {
6602 return Err(format!(
6603 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
6604 {experts_per_token} ({pairs})",
6605 selected.len(),
6606 route_weights.len(),
6607 )
6608 .into());
6609 }
6610 if !route_weights.iter().all(|weight| weight.is_finite()) {
6611 return Err("EP route weights contain a non-finite value".into());
6612 }
6613 if self.native_p2p {
6614 return self.run_routed_experts_native(
6615 experts,
6616 input,
6617 tokens,
6618 selected,
6619 route_weights,
6620 experts_per_token,
6621 activation_limit,
6622 );
6623 }
6624
6625 let mut output = vec![0.0f32; tokens * experts.input_width];
6626 let per_rank = experts.expert_count / experts.ranks.len();
6627 for token in 0..tokens {
6628 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6629 for slot in 0..experts_per_token {
6630 let pair = token * experts_per_token + slot;
6631 let expert = selected[pair];
6632 if expert >= experts.expert_count {
6633 return Err(format!(
6634 "EP selected expert {expert} outside 0..{}",
6635 experts.expert_count
6636 )
6637 .into());
6638 }
6639 let owner = expert / per_rank;
6640 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6641 let rank = &experts.ranks[owner];
6642 let engine = &self.ranks[owner];
6643 let gate =
6644 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
6645 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
6646 let activated: Vec<f32> = gate
6647 .iter()
6648 .zip(&up)
6649 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6650 .collect();
6651 debug_assert_eq!(activated.len(), experts.expert_width);
6652 let down =
6653 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
6654 let weight = route_weights[pair];
6655 for (sum, value) in output
6656 [token * experts.input_width..(token + 1) * experts.input_width]
6657 .iter_mut()
6658 .zip(down)
6659 {
6660 *sum += weight * value;
6661 }
6662 }
6663 }
6664 Ok(output)
6665 }
6666
6667 fn run_routed_experts_native(
6668 &self,
6669 experts: &ResidentExpertParallel,
6670 input: &[f32],
6671 tokens: usize,
6672 selected: &[usize],
6673 route_weights: &[f32],
6674 experts_per_token: usize,
6675 activation_limit: Option<f32>,
6676 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6677 if !self.native_p2p || self.ranks.len() < 2 {
6678 return Err("native EP execution requires at least two P2P ranks".into());
6679 }
6680 if self.ep_device_arithmetic {
6681 return self.run_routed_experts_native_device(
6682 experts,
6683 input,
6684 tokens,
6685 selected,
6686 route_weights,
6687 experts_per_token,
6688 activation_limit,
6689 );
6690 }
6691 let mut output = vec![0.0f32; tokens * experts.input_width];
6692 let per_rank = experts.expert_count / experts.ranks.len();
6693 for token in 0..tokens {
6694 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6695 let mut rank_inputs = (0..self.ranks.len())
6696 .map(|_| None)
6697 .collect::<Vec<Option<CudaSlice<f32>>>>();
6698 rank_inputs[0] = Some({
6699 let root = &self.ranks[0];
6700 let _main = root.gpu.enter_main()?;
6701 root.htod(input_row)?
6702 });
6703
6704 for slot in 0..experts_per_token {
6705 let pair = token * experts_per_token + slot;
6706 let expert = selected[pair];
6707 if expert >= experts.expert_count {
6708 return Err(format!(
6709 "EP selected expert {expert} outside 0..{}",
6710 experts.expert_count
6711 )
6712 .into());
6713 }
6714 let owner = expert / per_rank;
6715 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6716 if rank_inputs[owner].is_none() {
6717 let peer_input = {
6718 let root_input = rank_inputs[0]
6719 .as_ref()
6720 .ok_or("native EP lost its root input")?;
6721 let engine = &self.ranks[owner];
6722 let _main = engine.gpu.enter_main()?;
6723 let mut peer_input = engine.uninit(experts.input_width)?;
6724 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6725 peer_input
6726 };
6727 rank_inputs[owner] = Some(peer_input);
6728 }
6729
6730 let rank = &experts.ranks[owner];
6731 let engine = &self.ranks[owner];
6732 let owner_input = rank_inputs[owner]
6733 .as_ref()
6734 .ok_or("native EP owner input is absent after dispatch")?;
6735 let gate = run_resident_bank_expert_device(
6736 engine,
6737 &rank.gate,
6738 local_expert,
6739 owner_input,
6740 1,
6741 )?;
6742 let up = run_resident_bank_expert_device(
6743 engine,
6744 &rank.up,
6745 local_expert,
6746 owner_input,
6747 1,
6748 )?;
6749 let (gate, up) = {
6750 let _main = engine.gpu.enter_main()?;
6751 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
6752 };
6753 let activated = gate
6754 .iter()
6755 .zip(&up)
6756 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6757 .collect::<Vec<_>>();
6758 debug_assert_eq!(activated.len(), experts.expert_width);
6759 let activated = {
6760 let _main = engine.gpu.enter_main()?;
6761 engine.htod(&activated)?
6762 };
6763 let down = run_resident_bank_expert_device(
6764 engine,
6765 &rank.down,
6766 local_expert,
6767 &activated,
6768 1,
6769 )?;
6770 let down = if owner == 0 {
6771 let _main = engine.gpu.enter_main()?;
6772 engine.dtoh(&down)?
6773 } else {
6774 let root = &self.ranks[0];
6775 let _main = root.gpu.enter_main()?;
6776 let mut root_down = root.uninit(experts.input_width)?;
6777 root.stream().memcpy_dtod(&down, &mut root_down)?;
6778 root.dtoh(&root_down)?
6779 };
6780 let weight = route_weights[pair];
6781 for (sum, value) in output
6782 [token * experts.input_width..(token + 1) * experts.input_width]
6783 .iter_mut()
6784 .zip(down)
6785 {
6786 *sum += weight * value;
6787 }
6788 }
6789 }
6790 Ok(output)
6791 }
6792
6793 fn run_routed_experts_native_device(
6794 &self,
6795 experts: &ResidentExpertParallel,
6796 input: &[f32],
6797 tokens: usize,
6798 selected: &[usize],
6799 route_weights: &[f32],
6800 experts_per_token: usize,
6801 activation_limit: Option<f32>,
6802 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6803 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
6804 return Err(
6805 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
6806 );
6807 }
6808 let mut output = Vec::with_capacity(tokens * experts.input_width);
6809 let per_rank = experts.expert_count / experts.ranks.len();
6810 let root = &self.ranks[0];
6811 for token in 0..tokens {
6812 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6813 let mut rank_inputs = (0..self.ranks.len())
6814 .map(|_| None)
6815 .collect::<Vec<Option<CudaSlice<f32>>>>();
6816 rank_inputs[0] = Some({
6817 let _main = root.gpu.enter_main()?;
6818 root.htod(input_row)?
6819 });
6820 let mut root_output = {
6821 let _main = root.gpu.enter_main()?;
6822 root.zeros(experts.input_width)?
6823 };
6824 let mut remote_down_keepalive = Vec::new();
6825
6826 for slot in 0..experts_per_token {
6827 let pair = token * experts_per_token + slot;
6828 let expert = selected[pair];
6829 if expert >= experts.expert_count {
6830 return Err(format!(
6831 "EP selected expert {expert} outside 0..{}",
6832 experts.expert_count
6833 )
6834 .into());
6835 }
6836 let owner = expert / per_rank;
6837 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6838 if rank_inputs[owner].is_none() {
6839 let peer_input = {
6840 let root_input = rank_inputs[0]
6841 .as_ref()
6842 .ok_or("native EP lost its root input")?;
6843 let engine = &self.ranks[owner];
6844 let _main = engine.gpu.enter_main()?;
6845 let mut peer_input = engine.uninit(experts.input_width)?;
6846 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6847 peer_input
6848 };
6849 rank_inputs[owner] = Some(peer_input);
6850 }
6851
6852 let rank = &experts.ranks[owner];
6853 let engine = &self.ranks[owner];
6854 let owner_input = rank_inputs[owner]
6855 .as_ref()
6856 .ok_or("native EP owner input is absent after dispatch")?;
6857 let gate = run_resident_bank_expert_device(
6858 engine,
6859 &rank.gate,
6860 local_expert,
6861 owner_input,
6862 1,
6863 )?;
6864 let up = run_resident_bank_expert_device(
6865 engine,
6866 &rank.up,
6867 local_expert,
6868 owner_input,
6869 1,
6870 )?;
6871 let activated = {
6872 let _main = engine.gpu.enter_main()?;
6873 let mut activated = engine.uninit(experts.expert_width)?;
6874 if let Some(limit) = activation_limit {
6875 engine.silu_clamped_mul_host_expf(
6876 &gate,
6877 &up,
6878 limit,
6879 &mut activated,
6880 experts.expert_width,
6881 )?;
6882 } else {
6883 engine.silu_mul_host_expf(
6884 &gate,
6885 &up,
6886 &mut activated,
6887 experts.expert_width,
6888 )?;
6889 }
6890 activated
6891 };
6892 let down = run_resident_bank_expert_device(
6893 engine,
6894 &rank.down,
6895 local_expert,
6896 &activated,
6897 1,
6898 )?;
6899 let root_down = if owner == 0 {
6900 down
6901 } else {
6902 let _main = root.gpu.enter_main()?;
6903 let mut root_down = root.uninit(experts.input_width)?;
6904 root.stream().memcpy_dtod(&down, &mut root_down)?;
6905 remote_down_keepalive.push(down);
6909 root_down
6910 };
6911 let _main = root.gpu.enter_main()?;
6912 let mut destination = root_output.slice_mut(0..experts.input_width);
6913 root.axpy_host_into(
6914 &root_down.slice(0..root_down.len()),
6915 route_weights[pair],
6916 &mut destination,
6917 experts.input_width,
6918 )?;
6919 }
6920
6921 let _main = root.gpu.enter_main()?;
6922 let root_output = root.dtoh(&root_output)?;
6923 drop(remote_down_keepalive);
6924 output.extend(root_output);
6925 }
6926 Ok(output)
6927 }
6928}
6929
6930fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
6931 if matrix.out_features % tp != 0 {
6932 return Err(format!(
6933 "column-parallel out_features {} is not divisible by TP={tp}",
6934 matrix.out_features
6935 ));
6936 }
6937 let local_out = matrix.out_features / tp;
6938 if local_out % FP8_BLOCK != 0 {
6939 return Err(format!(
6940 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
6941 E4M3 scale block"
6942 ));
6943 }
6944 Ok(())
6945}
6946
6947fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
6948 if !matches!(tp, 1 | 2 | 4 | 8) {
6949 return Err(format!(
6950 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
6951 ));
6952 }
6953 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
6954 return Err(format!(
6955 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
6956 ));
6957 }
6958 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
6959 let local_out = out_features / tp;
6960 if local_out % canonical_rows != 0 {
6961 return Err(format!(
6962 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
6963 {canonical_rows}-row chunks"
6964 ));
6965 }
6966 Ok(canonical_rows)
6967}
6968
6969fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
6970 if !matches!(tp, 1 | 2 | 4 | 8) {
6971 return Err(format!(
6972 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
6973 ));
6974 }
6975 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
6976 return Err(format!(
6977 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
6978 ));
6979 }
6980 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
6981 let local_in = in_features / tp;
6982 if local_in % canonical_cols != 0 {
6983 return Err(format!(
6984 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
6985 {canonical_cols}-column chunks"
6986 ));
6987 }
6988 Ok(canonical_cols)
6989}
6990
6991fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
6992 if matrix.in_features % tp != 0 {
6993 return Err(format!(
6994 "row-parallel in_features {} is not divisible by TP={tp}",
6995 matrix.in_features
6996 ));
6997 }
6998 let local_in = matrix.in_features / tp;
6999 if local_in % FP8_BLOCK != 0 {
7000 return Err(format!(
7001 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7002 E4M3 scale block"
7003 ));
7004 }
7005 Ok(())
7006}
7007
7008fn upload_rank(
7009 engine: &Engine,
7010 matrix: E4m3BlockMatrix<'_>,
7011) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7012 let _main = engine.gpu.enter_main()?;
7013 matrix.validate()?;
7014 Ok(ResidentE4m3Rank {
7015 codes: engine.htod_bytes(matrix.codes)?,
7016 scales: engine.htod(matrix.scales)?,
7017 out_features: matrix.out_features,
7018 in_features: matrix.in_features,
7019 })
7020}
7021
7022fn upload_bf16_rank(
7023 engine: &Engine,
7024 matrix: Bf16Matrix<'_>,
7025 f32_mirror: bool,
7026) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7027 let _main = engine.gpu.enter_main()?;
7028 matrix.validate()?;
7029 let bytes = engine.htod_bytes(matrix.bytes)?;
7030 let weight = if f32_mirror {
7031 let values = matrix
7032 .out_features
7033 .checked_mul(matrix.in_features)
7034 .ok_or("resident BF16 mirror element count overflow")?;
7035 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7036 } else {
7037 ResidentBf16Weight::Bf16(bytes)
7038 };
7039 Ok(ResidentBf16Rank {
7040 weight,
7041 out_features: matrix.out_features,
7042 in_features: matrix.in_features,
7043 })
7044}
7045
7046fn upload_expert_bank_rank(
7047 engine: &Engine,
7048 bank: E4m3ExpertBank<'_>,
7049 expert_range: Range<usize>,
7050) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7051 let _main = engine.gpu.enter_main()?;
7052 bank.validate()?;
7053 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7054 return Err(format!(
7055 "invalid EP expert range {expert_range:?} for {} experts",
7056 bank.expert_count
7057 )
7058 .into());
7059 }
7060 let code_stride = bank.out_features * bank.in_features;
7061 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7062 Ok(ResidentE4m3ExpertBankRank {
7063 codes: engine.htod_bytes(
7064 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7065 )?,
7066 scales: engine.htod(
7067 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7068 )?,
7069 expert_range,
7070 out_features: bank.out_features,
7071 in_features: bank.in_features,
7072 code_stride,
7073 scale_stride,
7074 k_blocks: None,
7075 })
7076}
7077
7078fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7079 if bank.out_features % tp != 0 {
7080 return Err(format!(
7081 "TP expert output width {} is not divisible by TP={tp}",
7082 bank.out_features
7083 ));
7084 }
7085 let local_out = bank.out_features / tp;
7086 if local_out % FP8_BLOCK != 0 {
7087 return Err(format!(
7088 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7089 ));
7090 }
7091 Ok(())
7092}
7093
7094fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7095 if bank.in_features % tp != 0 {
7096 return Err(format!(
7097 "TP expert input width {} is not divisible by TP={tp}",
7098 bank.in_features
7099 ));
7100 }
7101 let local_in = bank.in_features / tp;
7102 if local_in % FP8_BLOCK != 0 {
7103 return Err(format!(
7104 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7105 ));
7106 }
7107 Ok(())
7108}
7109
7110fn upload_column_bank_rank(
7111 engine: &Engine,
7112 bank: E4m3ExpertBank<'_>,
7113 tp: usize,
7114 rank: usize,
7115) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7116 let _main = engine.gpu.enter_main()?;
7117 let packed = pack_column_bank_rank(bank, tp, rank)?;
7118 Ok(ResidentE4m3ExpertBankRank {
7119 codes: engine.htod_bytes(&packed.codes)?,
7120 scales: engine.htod(&packed.scales)?,
7121 expert_range: packed.expert_range,
7122 out_features: packed.out_features,
7123 in_features: packed.in_features,
7124 code_stride: packed.code_stride,
7125 scale_stride: packed.scale_stride,
7126 k_blocks: packed.k_blocks,
7127 })
7128}
7129
7130fn pack_column_bank_rank(
7131 bank: E4m3ExpertBank<'_>,
7132 tp: usize,
7133 rank: usize,
7134) -> Result<PackedE4m3ExpertBankRank, String> {
7135 bank.validate()?;
7136 validate_column_bank_shape(bank, tp)?;
7137 if rank >= tp {
7138 return Err(format!("TP rank {rank} outside 0..{tp}"));
7139 }
7140 let local_out = bank.out_features / tp;
7141 let full_code_stride = bank.out_features * bank.in_features;
7142 let local_code_stride = local_out * bank.in_features;
7143 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7144 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7145 let local_scale_rows = local_out / FP8_BLOCK;
7146 let local_scale_stride = local_scale_rows * scale_cols;
7147 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7148 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7149 let row_start = rank * local_out;
7150 let scale_row_start = rank * local_scale_rows;
7151 for expert in 0..bank.expert_count {
7152 let code_start = expert * full_code_stride + row_start * bank.in_features;
7153 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7154 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7155 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7156 }
7157 Ok(PackedE4m3ExpertBankRank {
7158 codes,
7159 scales,
7160 expert_range: 0..bank.expert_count,
7161 out_features: local_out,
7162 in_features: bank.in_features,
7163 code_stride: local_code_stride,
7164 scale_stride: local_scale_stride,
7165 k_blocks: None,
7166 })
7167}
7168
7169fn upload_row_bank_rank(
7170 engine: &Engine,
7171 bank: E4m3ExpertBank<'_>,
7172 tp: usize,
7173 rank: usize,
7174) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7175 let _main = engine.gpu.enter_main()?;
7176 let packed = pack_row_bank_rank(bank, tp, rank)?;
7177 Ok(ResidentE4m3ExpertBankRank {
7178 codes: engine.htod_bytes(&packed.codes)?,
7179 scales: engine.htod(&packed.scales)?,
7180 expert_range: packed.expert_range,
7181 out_features: packed.out_features,
7182 in_features: packed.in_features,
7183 code_stride: packed.code_stride,
7184 scale_stride: packed.scale_stride,
7185 k_blocks: packed.k_blocks,
7186 })
7187}
7188
7189fn pack_row_bank_rank(
7190 bank: E4m3ExpertBank<'_>,
7191 tp: usize,
7192 rank: usize,
7193) -> Result<PackedE4m3ExpertBankRank, String> {
7194 bank.validate()?;
7195 validate_row_bank_shape(bank, tp)?;
7196 if rank >= tp {
7197 return Err(format!("TP rank {rank} outside 0..{tp}"));
7198 }
7199 let local_in = bank.in_features / tp;
7200 let full_code_stride = bank.out_features * bank.in_features;
7201 let local_code_stride = bank.out_features * local_in;
7202 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7203 let local_scale_cols = local_in / FP8_BLOCK;
7204 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7205 let full_scale_stride = scale_rows * full_scale_cols;
7206 let local_scale_stride = scale_rows * local_scale_cols;
7207 let global_block_start = rank * local_scale_cols;
7208 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7209 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7210 for expert in 0..bank.expert_count {
7211 let expert_code_start = expert * full_code_stride;
7212 let expert_scale_start = expert * full_scale_stride;
7213 for local_block in 0..local_scale_cols {
7214 let global_block = global_block_start + local_block;
7215 let column_start = global_block * FP8_BLOCK;
7216 for row in 0..bank.out_features {
7217 let start = expert_code_start + row * bank.in_features + column_start;
7218 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7219 }
7220 for row in 0..scale_rows {
7221 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7222 }
7223 }
7224 }
7225 Ok(PackedE4m3ExpertBankRank {
7226 codes,
7227 scales,
7228 expert_range: 0..bank.expert_count,
7229 out_features: bank.out_features,
7230 in_features: local_in,
7231 code_stride: local_code_stride,
7232 scale_stride: local_scale_stride,
7233 k_blocks: Some(local_scale_cols),
7234 })
7235}
7236
7237fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7238 if engines.len() != ranks.len() {
7239 return Err(format!(
7240 "resident TP rank count {} != runtime rank count {}",
7241 ranks.len(),
7242 engines.len()
7243 ));
7244 }
7245 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7246 let device = engine.ctx().ordinal();
7247 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7248 return Err(format!(
7249 "resident TP rank {rank} is not owned by runtime device {device}"
7250 ));
7251 }
7252 }
7253 Ok(())
7254}
7255
7256fn validate_tp_bank_residency(
7257 engines: &[Engine],
7258 experts: &ResidentTpExpertBank,
7259) -> Result<(), String> {
7260 if engines.len() != experts.gate.len()
7261 || engines.len() != experts.up.len()
7262 || engines.len() != experts.down.len()
7263 {
7264 return Err(format!(
7265 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7266 experts.gate.len(),
7267 experts.up.len(),
7268 experts.down.len(),
7269 engines.len()
7270 ));
7271 }
7272 for (rank, engine) in engines.iter().enumerate() {
7273 let device = engine.ctx().ordinal();
7274 for (projection, bank) in [
7275 ("gate", &experts.gate[rank]),
7276 ("up", &experts.up[rank]),
7277 ("down", &experts.down[rank]),
7278 ] {
7279 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7280 return Err(format!(
7281 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7282 {device}"
7283 ));
7284 }
7285 }
7286 }
7287 Ok(())
7288}
7289
7290fn validate_ep_residency(
7291 engines: &[Engine],
7292 experts: &ResidentExpertParallel,
7293) -> Result<(), String> {
7294 if engines.len() != experts.ranks.len() {
7295 return Err(format!(
7296 "resident EP rank count {} != runtime rank count {}",
7297 experts.ranks.len(),
7298 engines.len()
7299 ));
7300 }
7301 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7302 let device = engine.ctx().ordinal();
7303 for (projection, bank) in [
7304 ("gate", &resident.gate),
7305 ("up", &resident.up),
7306 ("down", &resident.down),
7307 ] {
7308 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7309 return Err(format!(
7310 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7311 {device}"
7312 ));
7313 }
7314 }
7315 }
7316 Ok(())
7317}
7318
7319fn run_rank(
7320 engine: &Engine,
7321 matrix: E4m3BlockMatrix<'_>,
7322 activations: &[f32],
7323 tokens: usize,
7324) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7325 let _main = engine.gpu.enter_main()?;
7326 let codes = engine.htod_bytes(matrix.codes)?;
7327 let scales = engine.htod(matrix.scales)?;
7328 let activations = engine.htod(activations)?;
7329 let output = engine.qmatvec_mmq_fp8_blk(
7330 &codes,
7331 &scales,
7332 &activations,
7333 tokens,
7334 matrix.in_features,
7335 matrix.out_features,
7336 )?;
7337 engine.dtoh(&output)
7338}
7339
7340fn run_resident_rank(
7341 engine: &Engine,
7342 matrix: &ResidentE4m3Rank,
7343 activations: &[f32],
7344 tokens: usize,
7345) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7346 let _main = engine.gpu.enter_main()?;
7347 let activations = engine.htod(activations)?;
7348 let output = engine.qmatvec_mmq_fp8_blk(
7349 &matrix.codes,
7350 &matrix.scales,
7351 &activations,
7352 tokens,
7353 matrix.in_features,
7354 matrix.out_features,
7355 )?;
7356 engine.dtoh(&output)
7357}
7358
7359fn run_resident_bf16_rank(
7360 engine: &Engine,
7361 matrix: &ResidentBf16Rank,
7362 activations: &[f32],
7363 tokens: usize,
7364 canonical_chunk_rows: Option<usize>,
7365) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7366 let _main = engine.gpu.enter_main()?;
7367 let activations = engine.htod(activations)?;
7368 let output = run_resident_bf16_rank_device(
7369 engine,
7370 matrix,
7371 &activations,
7372 tokens,
7373 canonical_chunk_rows,
7374 false,
7375 )?;
7376 engine.dtoh(&output)
7377}
7378
7379fn run_resident_bf16_rank_device(
7380 engine: &Engine,
7381 matrix: &ResidentBf16Rank,
7382 activations: &CudaSlice<f32>,
7383 tokens: usize,
7384 canonical_chunk_rows: Option<usize>,
7385 strided_chunk_output: bool,
7386) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7387 let _main = engine.gpu.enter_main()?;
7388 if activations.ordinal() != engine.ctx().ordinal() {
7389 return Err(format!(
7390 "resident BF16 activation device {} != rank device {}",
7391 activations.ordinal(),
7392 engine.ctx().ordinal()
7393 )
7394 .into());
7395 }
7396 if activations.len() != tokens * matrix.in_features {
7397 return Err(format!(
7398 "resident BF16 activation count {} != {tokens}x{}",
7399 activations.len(),
7400 matrix.in_features
7401 )
7402 .into());
7403 }
7404 match (&matrix.weight, canonical_chunk_rows) {
7405 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
7406 .linear_bf16_resident_canonical_rows(
7407 activations,
7408 bytes,
7409 tokens,
7410 matrix.in_features,
7411 matrix.out_features,
7412 rows,
7413 ),
7414 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
7415 activations,
7416 bytes,
7417 tokens,
7418 matrix.in_features,
7419 matrix.out_features,
7420 ),
7421 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
7422 .linear_f32_resident_canonical_rows_strided(
7423 activations,
7424 values,
7425 tokens,
7426 matrix.in_features,
7427 matrix.out_features,
7428 rows,
7429 ),
7430 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
7431 activations,
7432 values,
7433 tokens,
7434 matrix.in_features,
7435 matrix.out_features,
7436 rows,
7437 ),
7438 (ResidentBf16Weight::F32(values), None) => engine.linear(
7439 activations,
7440 values,
7441 tokens,
7442 matrix.in_features,
7443 matrix.out_features,
7444 ),
7445 }
7446}
7447
7448fn validate_resident_bf16_ranks(
7449 engines: &[Engine],
7450 ranks: &[ResidentBf16Rank],
7451) -> Result<(), String> {
7452 if engines.len() != ranks.len() {
7453 return Err(format!(
7454 "resident BF16 TP rank count {} != runtime rank count {}",
7455 ranks.len(),
7456 engines.len(),
7457 ));
7458 }
7459 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7460 let device = engine.ctx().ordinal();
7461 if matrix.weight.ordinal() != device {
7462 return Err(format!(
7463 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
7464 ));
7465 }
7466 }
7467 Ok(())
7468}
7469
7470fn validate_step_bf16_row_residency(
7471 engines: &[Engine],
7472 matrix: &ResidentStepBf16RowParallel,
7473) -> Result<(), String> {
7474 if engines.len() != matrix.ranks.len() {
7475 return Err(format!(
7476 "resident Step BF16 row rank count {} != runtime rank count {}",
7477 matrix.ranks.len(),
7478 engines.len(),
7479 ));
7480 }
7481 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
7482 if matrix.canonical_chunk_cols != canonical_cols {
7483 return Err(format!(
7484 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
7485 matrix.canonical_chunk_cols
7486 ));
7487 }
7488 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
7489 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
7490 if blocks.len() != blocks_per_rank {
7491 return Err(format!(
7492 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
7493 blocks.len()
7494 ));
7495 }
7496 let device = engine.ctx().ordinal();
7497 for (block, resident) in blocks.iter().enumerate() {
7498 if resident.weight.ordinal() != device
7499 || resident.in_features != canonical_cols
7500 || resident.out_features != matrix.out_features
7501 {
7502 return Err(format!(
7503 "resident Step BF16 row rank {rank} block {block} has inconsistent \
7504 device or geometry"
7505 ));
7506 }
7507 }
7508 }
7509 Ok(())
7510}
7511
7512fn validate_replicated_device_rows(
7513 engines: &[Engine],
7514 rows: &ResidentReplicatedDeviceRows,
7515) -> Result<(), String> {
7516 let rank_lengths = rows
7517 .ranks
7518 .iter()
7519 .map(|rank_rows| rank_rows.len())
7520 .collect::<Vec<_>>();
7521 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
7522 if rows
7523 .ranks
7524 .iter()
7525 .zip(engines)
7526 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
7527 {
7528 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
7529 }
7530 Ok(())
7531}
7532
7533fn replicated_device_row_values(
7534 tokens: usize,
7535 width: usize,
7536 expected_ranks: usize,
7537 rank_lengths: &[usize],
7538) -> Result<usize, String> {
7539 let values = tokens
7540 .checked_mul(width)
7541 .ok_or("replicated device row size overflow")?;
7542 if tokens == 0
7543 || width == 0
7544 || expected_ranks == 0
7545 || rank_lengths.len() != expected_ranks
7546 || rank_lengths.iter().any(|&rank_len| rank_len != values)
7547 {
7548 return Err(format!(
7549 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
7550 tokens,
7551 width,
7552 rank_lengths.len(),
7553 expected_ranks
7554 ));
7555 }
7556 Ok(values)
7557}
7558
7559fn replicated_device_row_source_values(
7560 tokens: usize,
7561 width: usize,
7562 source_len: usize,
7563 source_device: usize,
7564 root_device: usize,
7565) -> Result<usize, String> {
7566 let values = tokens
7567 .checked_mul(width)
7568 .ok_or("replicated device row size overflow")?;
7569 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
7570 return Err(format!(
7571 "replicated device row source has inconsistent geometry/device \
7572 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
7573 ));
7574 }
7575 Ok(values)
7576}
7577
7578fn bf16_column_shard(
7579 matrix: Bf16Matrix<'_>,
7580 tp: usize,
7581 rank: usize,
7582) -> Result<Bf16Matrix<'_>, String> {
7583 matrix.validate()?;
7584 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
7585 return Err(format!(
7586 "invalid BF16 column shard out={} TP={tp} rank={rank}",
7587 matrix.out_features
7588 ));
7589 }
7590 let local_out = matrix.out_features / tp;
7591 let row_bytes = matrix.in_features * 2;
7592 let start = rank * local_out * row_bytes;
7593 Ok(Bf16Matrix {
7594 bytes: &matrix.bytes[start..start + local_out * row_bytes],
7595 out_features: local_out,
7596 in_features: matrix.in_features,
7597 })
7598}
7599
7600fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
7601 matrix.validate()?;
7602 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
7603 return Err(format!(
7604 "invalid BF16 row shard in={} TP={tp} rank={rank}",
7605 matrix.in_features
7606 ));
7607 }
7608 let local_in = matrix.in_features / tp;
7609 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
7610 for row in 0..matrix.out_features {
7611 let start = (row * matrix.in_features + rank * local_in) * 2;
7612 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
7613 }
7614 Ok(bytes)
7615}
7616
7617fn bf16_row_block(
7618 matrix: Bf16Matrix<'_>,
7619 col_start: usize,
7620 block_cols: usize,
7621) -> Result<Vec<u8>, String> {
7622 matrix.validate()?;
7623 let col_end = col_start
7624 .checked_add(block_cols)
7625 .ok_or("BF16 row block column overflow")?;
7626 if block_cols == 0 || col_end > matrix.in_features {
7627 return Err(format!(
7628 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
7629 matrix.in_features
7630 ));
7631 }
7632 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
7633 for row in 0..matrix.out_features {
7634 let start = (row * matrix.in_features + col_start) * 2;
7635 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
7636 }
7637 Ok(bytes)
7638}
7639
7640fn run_resident_bank_expert(
7641 engine: &Engine,
7642 bank: &ResidentE4m3ExpertBankRank,
7643 local_expert: usize,
7644 activations: &[f32],
7645 tokens: usize,
7646) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7647 let _main = engine.gpu.enter_main()?;
7648 if bank.k_blocks.is_some() {
7649 return Err("block-major TP row bank requires canonical block execution".into());
7650 }
7651 let local_count = bank.expert_range.end - bank.expert_range.start;
7652 if local_expert >= local_count {
7653 return Err(format!(
7654 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
7655 bank.expert_range
7656 )
7657 .into());
7658 }
7659 validate_activations(activations, tokens, bank.in_features)?;
7660 let activations = engine.htod(activations)?;
7661 let weight = bank
7662 .codes
7663 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7664 let scales = bank
7665 .scales
7666 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7667 let input = activations.slice(0..activations.len());
7668 let output = engine.qmatvec_mmq_fp8_blk_view(
7669 &weight,
7670 &scales,
7671 &input,
7672 tokens,
7673 bank.in_features,
7674 bank.out_features,
7675 )?;
7676 engine.dtoh(&output)
7677}
7678
7679fn run_resident_bank_expert_block(
7680 engine: &Engine,
7681 bank: &ResidentE4m3ExpertBankRank,
7682 local_expert: usize,
7683 block: usize,
7684 activations: &[f32],
7685) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7686 let _main = engine.gpu.enter_main()?;
7687 let local_count = bank.expert_range.end - bank.expert_range.start;
7688 if local_expert >= local_count {
7689 return Err(format!(
7690 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7691 bank.expert_range
7692 )
7693 .into());
7694 }
7695 let blocks = bank
7696 .k_blocks
7697 .ok_or("TP row bank is not packed in native K-block order")?;
7698 if block >= blocks {
7699 return Err(format!("TP row block {block} outside 0..{blocks}").into());
7700 }
7701 validate_activations(activations, 1, FP8_BLOCK)?;
7702 let block_code_stride = bank.out_features * FP8_BLOCK;
7703 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7704 if bank.in_features != blocks * FP8_BLOCK
7705 || bank.code_stride != blocks * block_code_stride
7706 || bank.scale_stride != blocks * block_scale_stride
7707 {
7708 return Err("TP row bank block-major geometry is inconsistent".into());
7709 }
7710
7711 let expert_code_start = local_expert * bank.code_stride;
7712 let expert_scale_start = local_expert * bank.scale_stride;
7713 let weight = bank.codes.slice(
7714 expert_code_start + block * block_code_stride
7715 ..expert_code_start + (block + 1) * block_code_stride,
7716 );
7717 let scales = bank.scales.slice(
7718 expert_scale_start + block * block_scale_stride
7719 ..expert_scale_start + (block + 1) * block_scale_stride,
7720 );
7721 let activations = engine.htod(activations)?;
7722 let input = activations.slice(0..activations.len());
7723 let output = engine.qmatvec_mmq_fp8_blk_view(
7724 &weight,
7725 &scales,
7726 &input,
7727 1,
7728 FP8_BLOCK,
7729 bank.out_features,
7730 )?;
7731 engine.dtoh(&output)
7732}
7733
7734fn run_resident_bank_expert_device(
7735 engine: &Engine,
7736 bank: &ResidentE4m3ExpertBankRank,
7737 local_expert: usize,
7738 activations: &CudaSlice<f32>,
7739 tokens: usize,
7740) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7741 let _main = engine.gpu.enter_main()?;
7742 if bank.k_blocks.is_some() {
7743 return Err("block-major TP row bank requires canonical block execution".into());
7744 }
7745 let local_count = bank.expert_range.end - bank.expert_range.start;
7746 if local_expert >= local_count {
7747 return Err(format!(
7748 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7749 bank.expert_range
7750 )
7751 .into());
7752 }
7753 let expected = tokens
7754 .checked_mul(bank.in_features)
7755 .ok_or("native TP activation size overflow")?;
7756 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
7757 return Err(format!(
7758 "native TP activation len/device {}/{} != expected {expected}/{}",
7759 activations.len(),
7760 activations.ordinal(),
7761 engine.ctx().ordinal()
7762 )
7763 .into());
7764 }
7765 let weight = bank
7766 .codes
7767 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7768 let scales = bank
7769 .scales
7770 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7771 let input = activations.slice(0..activations.len());
7772 engine.qmatvec_mmq_fp8_blk_view(
7773 &weight,
7774 &scales,
7775 &input,
7776 tokens,
7777 bank.in_features,
7778 bank.out_features,
7779 )
7780}
7781
7782fn run_resident_bank_expert_block_device(
7783 engine: &Engine,
7784 bank: &ResidentE4m3ExpertBankRank,
7785 local_expert: usize,
7786 block: usize,
7787 activations: &cudarc::driver::CudaView<'_, f32>,
7788) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7789 let _main = engine.gpu.enter_main()?;
7790 let local_count = bank.expert_range.end - bank.expert_range.start;
7791 if local_expert >= local_count {
7792 return Err(format!(
7793 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7794 bank.expert_range
7795 )
7796 .into());
7797 }
7798 let blocks = bank
7799 .k_blocks
7800 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
7801 if block >= blocks {
7802 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
7803 }
7804 let activation_device = activations.stream().context().ordinal();
7805 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
7806 return Err(format!(
7807 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
7808 activations.len(),
7809 activation_device,
7810 engine.ctx().ordinal()
7811 )
7812 .into());
7813 }
7814 let block_code_stride = bank.out_features * FP8_BLOCK;
7815 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7816 if bank.in_features != blocks * FP8_BLOCK
7817 || bank.code_stride != blocks * block_code_stride
7818 || bank.scale_stride != blocks * block_scale_stride
7819 {
7820 return Err("native TP row bank block-major geometry is inconsistent".into());
7821 }
7822 let expert_code_start = local_expert * bank.code_stride;
7823 let expert_scale_start = local_expert * bank.scale_stride;
7824 let weight = bank.codes.slice(
7825 expert_code_start + block * block_code_stride
7826 ..expert_code_start + (block + 1) * block_code_stride,
7827 );
7828 let scales = bank.scales.slice(
7829 expert_scale_start + block * block_scale_stride
7830 ..expert_scale_start + (block + 1) * block_scale_stride,
7831 );
7832 engine.qmatvec_mmq_fp8_blk_view(
7833 &weight,
7834 &scales,
7835 activations,
7836 1,
7837 FP8_BLOCK,
7838 bank.out_features,
7839 )
7840}
7841
7842fn configure_native_p2p(
7843 ranks: &[Engine],
7844 devices: &[usize],
7845) -> Result<(), Box<dyn std::error::Error>> {
7846 if ranks.len() != devices.len() || ranks.len() < 2 {
7847 return Err("native TP P2P setup requires matching multi-rank devices".into());
7848 }
7849 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
7850 if engine.ctx().ordinal() != device {
7851 return Err(format!(
7852 "native TP rank {rank} context device {} != requested device {device}",
7853 engine.ctx().ordinal()
7854 )
7855 .into());
7856 }
7857 }
7858
7859 for src in 0..ranks.len() {
7860 for dst in 0..ranks.len() {
7861 if src == dst {
7862 continue;
7863 }
7864 let mut can_access = 0;
7865 unsafe {
7866 cudarc::driver::sys::cuDeviceCanAccessPeer(
7867 &mut can_access,
7868 ranks[src].ctx().cu_device(),
7869 ranks[dst].ctx().cu_device(),
7870 )
7871 .result()?;
7872 }
7873 if can_access == 0 {
7874 return Err(format!(
7875 "native TP requires P2P, but dev{} cannot access dev{}",
7876 devices[src], devices[dst]
7877 )
7878 .into());
7879 }
7880 ranks[src].ctx().bind_to_thread()?;
7881 let rc =
7882 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
7883 use cudarc::driver::sys::cudaError_enum as E;
7884 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
7885 return Err(format!(
7886 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
7887 devices[src], devices[dst]
7888 )
7889 .into());
7890 }
7891 }
7892 }
7893
7894 for &owner in devices {
7895 for &accessor in devices {
7896 if owner == accessor {
7897 continue;
7898 }
7899 let device = cudarc::driver::result::device::get(owner as i32)?;
7900 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
7901 unsafe {
7902 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
7903 }
7904 let desc = cudarc::driver::sys::CUmemAccessDesc {
7905 location: cudarc::driver::sys::CUmemLocation {
7906 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
7907 id: accessor as i32,
7908 },
7909 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
7910 };
7911 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
7912 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
7913 return Err(format!(
7914 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
7915 {rc:?}"
7916 )
7917 .into());
7918 }
7919 }
7920 }
7921
7922 for src in 0..ranks.len() {
7923 for dst in 0..ranks.len() {
7924 if src == dst {
7925 continue;
7926 }
7927 let expected = (0..NATIVE_P2P_PROBE_WORDS)
7928 .map(|index| {
7929 (index as u32)
7930 .wrapping_mul(0x9e37_79b9)
7931 .wrapping_add(((src as u32) << 16) | dst as u32)
7932 })
7933 .collect::<Vec<_>>();
7934 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
7935 let source = ranks[src].htod_u32_v(&expected)?;
7936 let mut destination = ranks[dst].htod_u32_v(&poison)?;
7937 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
7938 let actual = ranks[dst].dtoh_u32(&destination)?;
7939 if actual != expected {
7940 let mismatches = actual
7941 .iter()
7942 .zip(&expected)
7943 .filter(|(actual, expected)| actual != expected)
7944 .count();
7945 return Err(format!(
7946 "native TP peer probe dev{}->dev{} failed: {mismatches}/{} words differ",
7947 devices[src],
7948 devices[dst],
7949 expected.len()
7950 )
7951 .into());
7952 }
7953 }
7954 }
7955 ranks[0].ctx().bind_to_thread()?;
7956 eprintln!(
7957 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
7958 directions={} bytes={} mismatches=0",
7959 ranks.len() * (ranks.len() - 1),
7960 NATIVE_P2P_PROBE_WORDS * std::mem::size_of::<u32>(),
7961 );
7962 Ok(())
7963}
7964
7965fn validate_activations(
7966 activations: &[f32],
7967 tokens: usize,
7968 in_features: usize,
7969) -> Result<(), String> {
7970 let expected = tokens
7971 .checked_mul(in_features)
7972 .ok_or_else(|| "activation size overflow".to_string())?;
7973 if activations.len() != expected {
7974 return Err(format!(
7975 "activation count {} != {tokens}x{in_features} ({expected})",
7976 activations.len()
7977 ));
7978 }
7979 if !activations.iter().all(|value| value.is_finite()) {
7980 return Err("activations contain a non-finite value".to_string());
7981 }
7982 Ok(())
7983}
7984
7985fn column_shard(
7986 matrix: E4m3BlockMatrix<'_>,
7987 tp: usize,
7988 rank: usize,
7989) -> Result<E4m3BlockMatrix<'_>, String> {
7990 let local_out = matrix.out_features / tp;
7991 let row_start = rank * local_out;
7992 let code_start = row_start * matrix.in_features;
7993 let code_end = code_start + local_out * matrix.in_features;
7994 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
7995 let local_scale_rows = local_out / FP8_BLOCK;
7996 let scale_start = rank * local_scale_rows * scale_cols;
7997 let scale_end = scale_start + local_scale_rows * scale_cols;
7998 Ok(E4m3BlockMatrix {
7999 codes: &matrix.codes[code_start..code_end],
8000 scales: &matrix.scales[scale_start..scale_end],
8001 out_features: local_out,
8002 in_features: matrix.in_features,
8003 })
8004}
8005
8006fn row_shard(
8007 matrix: E4m3BlockMatrix<'_>,
8008 tp: usize,
8009 rank: usize,
8010) -> Result<(Vec<u8>, Vec<f32>), String> {
8011 let local_in = matrix.in_features / tp;
8012 let col_start = rank * local_in;
8013 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8014 for row in 0..matrix.out_features {
8015 let start = row * matrix.in_features + col_start;
8016 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8017 }
8018
8019 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8020 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8021 let local_scale_cols = local_in / FP8_BLOCK;
8022 let scale_col_start = rank * local_scale_cols;
8023 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8024 for row in 0..scale_rows {
8025 let start = row * scale_cols + scale_col_start;
8026 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8027 }
8028 Ok((codes, scales))
8029}
8030
8031fn activation_shard(
8032 activations: &[f32],
8033 tokens: usize,
8034 in_features: usize,
8035 tp: usize,
8036 rank: usize,
8037) -> Vec<f32> {
8038 let local_in = in_features / tp;
8039 let col_start = rank * local_in;
8040 let mut shard = Vec::with_capacity(tokens * local_in);
8041 for token in 0..tokens {
8042 let start = token * in_features + col_start;
8043 shard.extend_from_slice(&activations[start..start + local_in]);
8044 }
8045 shard
8046}
8047
8048#[derive(Clone, Copy)]
8068pub struct Nvfp4BlockMatrix<'a> {
8069 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8073 pub in_features: usize,
8074}
8075
8076impl Nvfp4BlockMatrix<'_> {
8077 pub fn validate(&self) -> Result<(), String> {
8078 if self.in_features == 0 || self.out_features == 0 {
8079 return Err("NVFP4 matrix has a zero dimension".to_string());
8080 }
8081 if self.in_features % 64 != 0 {
8082 return Err(format!(
8083 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8084 self.in_features
8085 ));
8086 }
8087 if self.codes.len() != self.out_features * self.in_features / 2 {
8088 return Err(format!(
8089 "NVFP4 code bytes {} != {}x{}/2",
8090 self.codes.len(),
8091 self.out_features,
8092 self.in_features
8093 ));
8094 }
8095 if self.scales.len() != self.out_features * self.in_features / 16 {
8096 return Err(format!(
8097 "NVFP4 scale bytes {} != {}x{}/16",
8098 self.scales.len(),
8099 self.out_features,
8100 self.in_features
8101 ));
8102 }
8103 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8104 return Err(format!(
8105 "NVFP4 macro scale {} is not finite-positive",
8106 self.macro_scale
8107 ));
8108 }
8109 Ok(())
8110 }
8111}
8112
8113#[derive(Clone, Copy)]
8115pub struct Nvfp4ExpertBank<'a> {
8116 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8120 pub out_features: usize,
8121 pub in_features: usize,
8122}
8123
8124impl Nvfp4ExpertBank<'_> {
8125 pub fn validate(&self) -> Result<(), String> {
8126 if self.expert_count == 0 {
8127 return Err("NVFP4 expert bank is empty".to_string());
8128 }
8129 if self.macros.len() != self.expert_count {
8130 return Err(format!(
8131 "NVFP4 bank macros {} != expert count {}",
8132 self.macros.len(),
8133 self.expert_count
8134 ));
8135 }
8136 self.expert(0).map(|_| ())
8137 }
8138
8139 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8140 if expert >= self.expert_count {
8141 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8142 }
8143 let code_stride = self.out_features * self.in_features / 2;
8144 let scale_stride = self.out_features * self.in_features / 16;
8145 if self.codes.len() != self.expert_count * code_stride
8146 || self.scales.len() != self.expert_count * scale_stride
8147 {
8148 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8149 }
8150 let matrix = Nvfp4BlockMatrix {
8151 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8152 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8153 macro_scale: self.macros[expert],
8154 out_features: self.out_features,
8155 in_features: self.in_features,
8156 };
8157 matrix.validate()?;
8158 Ok(matrix)
8159 }
8160}
8161
8162pub struct ResidentNvfp4Rank {
8164 blocks: crate::CudaSlice<u8>,
8165 macro_scale: f32,
8166 out_features: usize,
8167 in_features: usize,
8168 row_bytes: usize,
8169}
8170
8171pub struct ResidentNvfp4ColumnParallel {
8172 ranks: Vec<ResidentNvfp4Rank>,
8173 pub out_features: usize,
8174 pub in_features: usize,
8175}
8176
8177pub struct ResidentNvfp4RowParallel {
8178 ranks: Vec<ResidentNvfp4Rank>,
8179 pub out_features: usize,
8180 pub in_features: usize,
8181}
8182
8183pub struct ResidentTpNvfp4Expert {
8184 gate: ResidentNvfp4ColumnParallel,
8185 up: ResidentNvfp4ColumnParallel,
8186 down: ResidentNvfp4RowParallel,
8187 pub input_width: usize,
8188 pub expert_width: usize,
8189}
8190
8191pub struct ResidentNvfp4ColumnBankRank {
8195 bank: crate::CudaSlice<u8>,
8199 expert_bytes: usize,
8200 local_out: usize,
8201 in_features: usize,
8202 row_bytes: usize,
8203}
8204
8205impl ResidentNvfp4ColumnBankRank {
8206 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8207 self.bank
8208 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8209 }
8210}
8211
8212pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8218
8219pub struct ResidentNvfp4RowBankRank {
8220 bank: crate::CudaSlice<u8>,
8222 expert_bytes: usize,
8223 device_rank: usize, out_features: usize,
8225 local_in: usize,
8226 row_bytes: usize,
8227}
8228
8229impl ResidentNvfp4RowBankRank {
8230 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8231 self.bank
8232 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8233 }
8234}
8235
8236impl ResidentNvfp4TensorParallel {
8237 pub(crate) fn device_workspace_handle(
8238 &self,
8239 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8240 &self.device_workspace
8241 }
8242}
8243
8244pub struct ResidentNvfp4TensorParallel {
8245 gate: Vec<ResidentNvfp4ColumnBankRank>,
8246 up: Vec<ResidentNvfp4ColumnBankRank>,
8247 down: Vec<ResidentNvfp4RowBankRank>,
8248 macros_gate: Vec<f32>,
8249 macros_up: Vec<f32>,
8250 macros_down: Vec<f32>,
8251 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8255 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8256 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8257 pub expert_count: usize,
8258 pub input_width: usize,
8259 pub expert_width: usize,
8260 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8263 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8267 pub(crate) ep2: bool,
8271}
8272
8273pub struct Nvfp4T2Workspace {
8277 input2: Vec<crate::CudaSlice<f32>>,
8278 in_q2: Vec<crate::CudaSlice<i8>>,
8279 in_d2: Vec<crate::CudaSlice<f32>>,
8280 sel2: Vec<crate::CudaSlice<i32>>,
8281 route_w2: Vec<crate::CudaSlice<f32>>,
8282 gate_out2: Vec<crate::CudaSlice<f32>>,
8283 up_out2: Vec<crate::CudaSlice<f32>>,
8284 act_q2: Vec<crate::CudaSlice<i8>>,
8285 act_d2: Vec<crate::CudaSlice<f32>>,
8286 partial2: Vec<crate::CudaSlice<f32>>,
8287 acc_a: Vec<crate::CudaSlice<f32>>,
8289 acc_b: Vec<crate::CudaSlice<f32>>,
8290 peer_a: crate::CudaSlice<f32>,
8292 peer_b: crate::CudaSlice<f32>,
8293 omix_a: crate::CudaSlice<f32>,
8294 omix_b: crate::CudaSlice<f32>,
8295 ev_entry: CudaEvent,
8296 ev_rank: Vec<CudaEvent>,
8297 ev_root: CudaEvent,
8298 n_sel: usize,
8299 e_device: usize,
8300}
8301
8302struct RoutesGraph {
8309 exec: cudarc::driver::sys::CUgraphExec,
8310 parent: cudarc::driver::sys::CUgraph,
8311 _children: Vec<cudarc::driver::CudaGraph>,
8312}
8313unsafe impl Send for RoutesGraph {}
8316
8317impl Drop for RoutesGraph {
8318 fn drop(&mut self) {
8319 unsafe {
8320 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8321 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8322 }
8323 }
8324}
8325
8326impl Nvfp4DeviceRoutesWorkspace {
8327 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8328 self.in_stage_e.as_ref()
8329 }
8330 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8331 self.in_stage_e.as_mut()
8332 }
8333 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8334 self.out_stage_e.as_mut()
8335 }
8336 pub(crate) fn arm_stages(
8338 &mut self,
8339 e: &Engine,
8340 width: usize,
8341 n_sel: usize,
8342 ) -> Result<(), Box<dyn std::error::Error>> {
8343 let _main = e.gpu.enter_main()?;
8344 if self.in_stage_e.is_none() {
8345 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8346 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8347 }
8348 if self.dev_route_e.is_none() {
8349 self.dev_route_e = Some((
8350 e.htod_i32(&vec![0i32; n_sel])?,
8351 e.htod(&vec![0.0f32; n_sel])?,
8352 ));
8353 }
8354 Ok(())
8355 }
8356
8357 pub(crate) fn in_and_out_stages_mut(
8359 &mut self,
8360 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
8361 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
8362 (Some(input), Some(output)) => Some((input, output)),
8363 _ => None,
8364 }
8365 }
8366 pub(crate) fn dev_route_e_mut(
8367 &mut self,
8368 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
8369 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
8370 }
8371}
8372
8373pub struct Nvfp4DeviceRoutesWorkspace {
8374 gate_out: Vec<crate::CudaSlice<f32>>,
8377 up_out: Vec<crate::CudaSlice<f32>>,
8378 act_q: Vec<crate::CudaSlice<i8>>,
8379 act_d: Vec<crate::CudaSlice<f32>>,
8380 sel: Vec<crate::CudaSlice<i32>>,
8381 partial: Vec<crate::CudaSlice<f32>>,
8382 accumulator: Vec<crate::CudaSlice<f32>>,
8383 combine_w: Vec<crate::CudaSlice<f32>>,
8385 route_w: Vec<crate::CudaSlice<f32>>,
8388 in_q: Vec<crate::CudaSlice<i8>>,
8391 in_d: Vec<crate::CudaSlice<f32>>,
8392 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
8396 prestaged: bool,
8399 rank1_routed: bool,
8402 fence_flags_raw: u64,
8406 fence_ticket: u32,
8407 ev_input: Option<(CudaEvent, usize)>,
8409 in_stage_e: Option<crate::CudaSlice<f32>>,
8412 out_stage_e: Option<crate::CudaSlice<f32>>,
8413 routes_graph: Option<RoutesGraph>,
8414 raw_dev_route_e: Option<(u64, u64)>,
8416 raw_combine: Option<(u64, u64, u64, u64)>,
8417 raw_input: Vec<u64>,
8418 raw_sel: Vec<u64>,
8419 raw_route_w: Vec<u64>,
8420 remote: crate::CudaSlice<f32>,
8421 combined: crate::CudaSlice<f32>,
8422 n_sel: usize,
8423 input: Vec<crate::CudaSlice<f32>>,
8427 ev_rank: Vec<CudaEvent>,
8428 ev_done: Option<CudaEvent>,
8429 ev_entry: Option<(CudaEvent, usize)>,
8430}
8431
8432struct ResidentNvfp4EpRank {
8434 gate: Vec<crate::CudaSlice<u8>>,
8435 up: Vec<crate::CudaSlice<u8>>,
8436 down: Vec<crate::CudaSlice<u8>>,
8437 #[allow(dead_code)]
8438 expert_range: Range<usize>,
8439}
8440
8441pub struct ResidentNvfp4ExpertParallel {
8442 ranks: Vec<ResidentNvfp4EpRank>,
8443 macros_gate: Vec<f32>,
8444 macros_up: Vec<f32>,
8445 macros_down: Vec<f32>,
8446 pub expert_count: usize,
8447 pub input_width: usize,
8448 pub expert_width: usize,
8449 gate_row_bytes: usize,
8450 down_row_bytes: usize,
8451}
8452
8453fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8454 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
8455 matrix.codes,
8456 matrix.scales,
8457 matrix.out_features,
8458 matrix.in_features,
8459 )
8460}
8461
8462fn nvfp4_row_bytes(in_features: usize) -> usize {
8463 in_features / 64 * 36 }
8465
8466pub(crate) fn fuse_rope_append_on() -> bool {
8474 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8475 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
8476}
8477
8478pub(crate) fn no_local_shadow_on() -> bool {
8479 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8480 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
8481}
8482
8483pub(crate) fn nvfp4_bank_v2_on() -> bool {
8484 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8485 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
8486}
8487
8488fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
8492 let row_bytes = nvfp4_row_bytes(in_features);
8493 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
8494 let n_slots = in_features / 32;
8495 let mut out = Vec::with_capacity(v1.len());
8496 for row in 0..out_features {
8497 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
8498 for g in 0..n_slots {
8499 let (sblk, h) = (g / 2, g % 2);
8500 let b = &r[sblk * 36..sblk * 36 + 36];
8501 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
8502 }
8503 for g in 0..n_slots {
8504 let (sblk, h) = (g / 2, g % 2);
8505 let b = &r[sblk * 36..sblk * 36 + 36];
8506 out.push(b[2 * h]);
8507 out.push(b[2 * h + 1]);
8508 }
8509 }
8510 out
8511}
8512
8513fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8515 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
8516 let v1 = nvfp4_repack_matrix(matrix);
8517 if nvfp4_bank_v2_on() {
8518 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
8519 } else {
8520 v1
8521 }
8522}
8523
8524fn nvfp4_column_shard<'a>(
8527 matrix: Nvfp4BlockMatrix<'a>,
8528 tp: usize,
8529 rank: usize,
8530) -> Result<Nvfp4BlockMatrix<'a>, String> {
8531 if matrix.out_features % tp != 0 {
8532 return Err(format!(
8533 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
8534 matrix.out_features
8535 ));
8536 }
8537 let local_out = matrix.out_features / tp;
8538 let code_row = matrix.in_features / 2;
8539 let scale_row = matrix.in_features / 16;
8540 Ok(Nvfp4BlockMatrix {
8541 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
8542 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
8543 macro_scale: matrix.macro_scale,
8544 out_features: local_out,
8545 in_features: matrix.in_features,
8546 })
8547}
8548
8549fn nvfp4_row_shard(
8552 matrix: Nvfp4BlockMatrix<'_>,
8553 tp: usize,
8554 rank: usize,
8555) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
8556 if matrix.in_features % tp != 0 {
8557 return Err(format!(
8558 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
8559 matrix.in_features
8560 ));
8561 }
8562 let local_in = matrix.in_features / tp;
8563 if local_in % 64 != 0 {
8564 return Err(format!(
8565 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
8566 ));
8567 }
8568 let code_row = matrix.in_features / 2;
8569 let scale_row = matrix.in_features / 16;
8570 let local_code = local_in / 2;
8571 let local_scale = local_in / 16;
8572 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
8573 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
8574 for row in 0..matrix.out_features {
8575 let code_start = row * code_row + rank * local_code;
8576 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
8577 let scale_start = row * scale_row + rank * local_scale;
8578 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
8579 }
8580 Ok((codes, scales, local_in))
8581}
8582
8583fn run_rank_nvfp4(
8587 engine: &Engine,
8588 matrix: Nvfp4BlockMatrix<'_>,
8589 activations: &[f32],
8590 tokens: usize,
8591) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8592 matrix.validate()?;
8593 validate_activations(activations, tokens, matrix.in_features)?;
8594 let _main = engine.gpu.enter_main()?;
8595 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
8596 let activations = engine.htod(activations)?;
8597 let output = engine.qmatvec_nvfp4_fast(
8598 &blocks.slice(0..blocks.len()),
8599 &activations,
8600 tokens,
8601 matrix.in_features,
8602 matrix.out_features,
8603 nvfp4_row_bytes(matrix.in_features),
8604 )?;
8605 engine.dtoh(&output)
8606}
8607
8608fn upload_rank_nvfp4(
8609 engine: &Engine,
8610 matrix: Nvfp4BlockMatrix<'_>,
8611) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
8612 matrix.validate()?;
8613 let _main = engine.gpu.enter_main()?;
8614 Ok(ResidentNvfp4Rank {
8615 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
8616 macro_scale: matrix.macro_scale,
8617 out_features: matrix.out_features,
8618 in_features: matrix.in_features,
8619 row_bytes: nvfp4_row_bytes(matrix.in_features),
8620 })
8621}
8622
8623fn run_resident_rank_nvfp4(
8624 engine: &Engine,
8625 rank: &ResidentNvfp4Rank,
8626 activations: &[f32],
8627 tokens: usize,
8628) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8629 validate_activations(activations, tokens, rank.in_features)?;
8630 let _main = engine.gpu.enter_main()?;
8631 let activations = engine.htod(activations)?;
8632 let output = engine.qmatvec_nvfp4_fast(
8633 &rank.blocks.slice(0..rank.blocks.len()),
8634 &activations,
8635 tokens,
8636 rank.in_features,
8637 rank.out_features,
8638 rank.row_bytes,
8639 )?;
8640 engine.dtoh(&output)
8641}
8642
8643fn apply_macro(values: &mut [f32], macro_scale: f32) {
8644 for value in values.iter_mut() {
8645 *value *= macro_scale;
8646 }
8647}
8648
8649impl TpE4m3HostBounce {
8650 pub fn full_nvfp4(
8652 &self,
8653 matrix: Nvfp4BlockMatrix<'_>,
8654 activations: &[f32],
8655 tokens: usize,
8656 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8657 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
8658 apply_macro(&mut output, matrix.macro_scale);
8659 Ok(output)
8660 }
8661
8662 pub fn column_parallel_nvfp4(
8665 &self,
8666 matrix: Nvfp4BlockMatrix<'_>,
8667 activations: &[f32],
8668 tokens: usize,
8669 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
8670 matrix.validate()?;
8671 validate_activations(activations, tokens, matrix.in_features)?;
8672 let tp = self.ranks.len();
8673 let local_out = matrix.out_features / tp;
8674 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8675 let mut rank_outputs = Vec::with_capacity(tp);
8676 for (rank_index, rank) in self.ranks.iter().enumerate() {
8677 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
8678 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
8679 let row_start = rank_index * local_out;
8680 for token in 0..tokens {
8681 gathered[token * matrix.out_features + row_start
8682 ..token * matrix.out_features + row_start + local_out]
8683 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8684 }
8685 rank_outputs.push(output);
8686 }
8687 apply_macro(&mut gathered, matrix.macro_scale);
8688 Ok(ColumnParallelResult {
8689 gathered,
8690 rank_outputs,
8691 })
8692 }
8693
8694 pub fn row_parallel_nvfp4(
8697 &self,
8698 matrix: Nvfp4BlockMatrix<'_>,
8699 activations: &[f32],
8700 tokens: usize,
8701 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
8702 matrix.validate()?;
8703 validate_activations(activations, tokens, matrix.in_features)?;
8704 let tp = self.ranks.len();
8705 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8706 let mut rank_partials = Vec::with_capacity(tp);
8707 for (rank_index, rank) in self.ranks.iter().enumerate() {
8708 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
8709 let local_activations =
8710 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8711 let shard = Nvfp4BlockMatrix {
8712 codes: &codes,
8713 scales: &scales,
8714 macro_scale: matrix.macro_scale,
8715 out_features: matrix.out_features,
8716 in_features: local_in,
8717 };
8718 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
8719 for (sum, value) in reduced.iter_mut().zip(&partial) {
8720 *sum += *value;
8721 }
8722 rank_partials.push(partial);
8723 }
8724 apply_macro(&mut reduced, matrix.macro_scale);
8725 Ok(RowParallelResult {
8726 reduced,
8727 rank_partials,
8728 })
8729 }
8730
8731 pub fn upload_expert_nvfp4(
8732 &self,
8733 gate: Nvfp4BlockMatrix<'_>,
8734 up: Nvfp4BlockMatrix<'_>,
8735 down: Nvfp4BlockMatrix<'_>,
8736 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
8737 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8738 return Err("NVFP4 TP expert gate/up dimensions differ".into());
8739 }
8740 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8741 return Err(format!(
8742 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
8743 down.out_features, down.in_features, gate.out_features, gate.in_features
8744 )
8745 .into());
8746 }
8747 let tp = self.ranks.len();
8748 let mut gate_ranks = Vec::with_capacity(tp);
8749 let mut up_ranks = Vec::with_capacity(tp);
8750 let mut down_ranks = Vec::with_capacity(tp);
8751 for (rank_index, engine) in self.ranks.iter().enumerate() {
8752 gate_ranks.push(upload_rank_nvfp4(
8753 engine,
8754 nvfp4_column_shard(gate, tp, rank_index)?,
8755 )?);
8756 up_ranks.push(upload_rank_nvfp4(
8757 engine,
8758 nvfp4_column_shard(up, tp, rank_index)?,
8759 )?);
8760 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
8761 down_ranks.push(upload_rank_nvfp4(
8762 engine,
8763 Nvfp4BlockMatrix {
8764 codes: &codes,
8765 scales: &scales,
8766 macro_scale: down.macro_scale,
8767 out_features: down.out_features,
8768 in_features: local_in,
8769 },
8770 )?);
8771 }
8772 Ok(ResidentTpNvfp4Expert {
8773 gate: ResidentNvfp4ColumnParallel {
8774 ranks: gate_ranks,
8775 out_features: gate.out_features,
8776 in_features: gate.in_features,
8777 },
8778 up: ResidentNvfp4ColumnParallel {
8779 ranks: up_ranks,
8780 out_features: up.out_features,
8781 in_features: up.in_features,
8782 },
8783 down: ResidentNvfp4RowParallel {
8784 ranks: down_ranks,
8785 out_features: down.out_features,
8786 in_features: down.in_features,
8787 },
8788 input_width: gate.in_features,
8789 expert_width: gate.out_features,
8790 })
8791 }
8792
8793 fn column_parallel_resident_nvfp4(
8794 &self,
8795 matrix: &ResidentNvfp4ColumnParallel,
8796 activations: &[f32],
8797 tokens: usize,
8798 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8799 validate_activations(activations, tokens, matrix.in_features)?;
8800 let local_out = matrix.out_features / self.ranks.len();
8801 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8802 let mut macro_scale = None;
8803 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8804 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
8805 let row_start = rank_index * local_out;
8806 for token in 0..tokens {
8807 gathered[token * matrix.out_features + row_start
8808 ..token * matrix.out_features + row_start + local_out]
8809 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8810 }
8811 macro_scale = Some(shard.macro_scale);
8812 }
8813 apply_macro(
8814 &mut gathered,
8815 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
8816 );
8817 Ok(gathered)
8818 }
8819
8820 fn row_parallel_resident_nvfp4(
8821 &self,
8822 matrix: &ResidentNvfp4RowParallel,
8823 activations: &[f32],
8824 tokens: usize,
8825 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8826 validate_activations(activations, tokens, matrix.in_features)?;
8827 let tp = self.ranks.len();
8828 let local_in = matrix.in_features / tp;
8829 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8830 let mut macro_scale = None;
8831 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8832 if shard.in_features != local_in {
8833 return Err(format!(
8834 "NVFP4 resident row shard in_features {} != expected {local_in}",
8835 shard.in_features
8836 )
8837 .into());
8838 }
8839 let local_activations =
8840 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8841 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
8842 for (sum, value) in reduced.iter_mut().zip(&partial) {
8843 *sum += *value;
8844 }
8845 macro_scale = Some(shard.macro_scale);
8846 }
8847 apply_macro(
8848 &mut reduced,
8849 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
8850 );
8851 Ok(reduced)
8852 }
8853
8854 pub fn run_expert_nvfp4(
8855 &self,
8856 expert: &ResidentTpNvfp4Expert,
8857 input: &[f32],
8858 tokens: usize,
8859 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8860 validate_activations(input, tokens, expert.input_width)?;
8861 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
8862 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
8863 let activated: Vec<f32> = gate
8864 .iter()
8865 .zip(&up)
8866 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
8867 .collect();
8868 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
8869 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
8870 }
8871
8872 pub fn upload_tensor_parallel_nvfp4(
8874 &self,
8875 gate: Nvfp4ExpertBank<'_>,
8876 up: Nvfp4ExpertBank<'_>,
8877 down: Nvfp4ExpertBank<'_>,
8878 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
8879 gate.validate()?;
8880 up.validate()?;
8881 down.validate()?;
8882 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
8883 return Err("NVFP4 TP gate/up/down expert counts differ".into());
8884 }
8885 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8886 return Err("NVFP4 TP gate/up dimensions differ".into());
8887 }
8888 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8889 return Err(format!(
8890 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
8891 down.out_features, down.in_features, gate.out_features, gate.in_features
8892 )
8893 .into());
8894 }
8895 let tp = self.ranks.len();
8896 if gate.out_features % tp != 0 {
8897 return Err(format!(
8898 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
8899 gate.out_features
8900 )
8901 .into());
8902 }
8903 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
8904 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
8905 {
8906 return Err(format!(
8907 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
8908 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
8909 down.in_features
8910 )
8911 .into());
8912 }
8913 if tp > NVFP4_CANONICAL_ROW_SHARDS {
8914 return Err(format!(
8915 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
8916 ({NVFP4_CANONICAL_ROW_SHARDS})"
8917 )
8918 .into());
8919 }
8920
8921 let ep2 = step_nvfp4_ep2_on() && tp == 2;
8922 let mut gate_ranks = Vec::with_capacity(tp);
8923 let mut up_ranks = Vec::with_capacity(tp);
8924 let mut macros_gate_dev = Vec::with_capacity(tp);
8925 let mut macros_up_dev = Vec::with_capacity(tp);
8926 let mut macros_down_dev = Vec::with_capacity(tp);
8927 for (rank_index, engine) in self.ranks.iter().enumerate() {
8928 let _main = engine.gpu.enter_main()?;
8929 let mut gate_host: Vec<u8> = Vec::new();
8935 let mut up_host: Vec<u8> = Vec::new();
8936 let mut owned = 0usize;
8937 for expert in 0..gate.expert_count {
8938 if ep2 {
8939 if expert % 2 != rank_index {
8940 continue;
8941 }
8942 owned += 1;
8943 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
8944 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
8945 } else {
8946 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
8947 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
8948 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
8949 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
8950 }
8951 }
8952 let bank_experts = if ep2 { owned } else { gate.expert_count };
8953 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
8954 let up_expert_bytes = up_host.len() / bank_experts.max(1);
8955 let local_out = if ep2 {
8956 gate.out_features
8957 } else {
8958 gate.out_features / tp
8959 };
8960 gate_ranks.push(ResidentNvfp4ColumnBankRank {
8961 bank: engine.htod_bytes(&gate_host)?,
8962 expert_bytes: gate_expert_bytes,
8963 local_out,
8964 in_features: gate.in_features,
8965 row_bytes: nvfp4_row_bytes(gate.in_features),
8966 });
8967 up_ranks.push(ResidentNvfp4ColumnBankRank {
8968 bank: engine.htod_bytes(&up_host)?,
8969 expert_bytes: up_expert_bytes,
8970 local_out,
8971 in_features: up.in_features,
8972 row_bytes: nvfp4_row_bytes(up.in_features),
8973 });
8974 macros_gate_dev.push(engine.htod(gate.macros)?);
8975 macros_up_dev.push(engine.htod(up.macros)?);
8976 macros_down_dev.push(engine.htod(down.macros)?);
8977 }
8978 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
8982 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
8983 let device_rank = shard_index % tp;
8984 let engine = &self.ranks[device_rank];
8985 let _main = engine.gpu.enter_main()?;
8986 let mut down_host: Vec<u8> = Vec::new();
8987 let mut owned = 0usize;
8988 for expert in 0..down.expert_count {
8989 let down_matrix = down.expert(expert)?;
8990 if ep2 {
8991 if expert % 2 != device_rank {
8994 continue;
8995 }
8996 owned += 1;
8997 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
8998 } else {
8999 let (codes, scales, local_in) =
9000 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9001 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
9002 codes: &codes,
9003 scales: &scales,
9004 macro_scale: down_matrix.macro_scale,
9005 out_features: down_matrix.out_features,
9006 in_features: local_in,
9007 }));
9008 }
9009 }
9010 let bank_experts = if ep2 { owned } else { down.expert_count };
9011 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9012 let local_in = if ep2 {
9013 down.in_features
9014 } else {
9015 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9016 };
9017 down_ranks.push(ResidentNvfp4RowBankRank {
9018 bank: engine.htod_bytes(&down_host)?,
9019 expert_bytes: down_expert_bytes,
9020 device_rank,
9021 out_features: down.out_features,
9022 local_in,
9023 row_bytes: nvfp4_row_bytes(local_in),
9024 });
9025 }
9026 Ok(ResidentNvfp4TensorParallel {
9027 gate: gate_ranks,
9028 up: up_ranks,
9029 down: down_ranks,
9030 macros_gate: gate.macros.to_vec(),
9031 macros_up: up.macros.to_vec(),
9032 macros_down: down.macros.to_vec(),
9033 macros_gate_dev,
9034 macros_up_dev,
9035 macros_down_dev,
9036 expert_count: gate.expert_count,
9037 input_width: gate.in_features,
9038 expert_width: gate.out_features,
9039 device_workspace: std::sync::Mutex::new(None),
9040 t2_workspace: std::sync::Mutex::new(None),
9041 ep2,
9042 })
9043 }
9044
9045 fn run_full_bank_expert_nvfp4(
9049 &self,
9050 ranks: &[ResidentNvfp4ColumnBankRank],
9051 macros: &[f32],
9052 expert: usize,
9053 input: &[f32],
9054 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9055 let owner = expert & 1;
9056 let slot = expert >> 1;
9057 let bank = ranks
9058 .get(owner)
9059 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9060 let engine = &self.ranks[owner];
9061 let _main = engine.gpu.enter_main()?;
9062 let activations = engine.htod(input)?;
9063 let output = if nvfp4_bank_v2_on() {
9064 engine.qmatvec_nvfp4_fast_v2(
9065 &bank.expert(slot),
9066 &activations,
9067 1,
9068 bank.in_features,
9069 bank.local_out,
9070 bank.row_bytes,
9071 )?
9072 } else {
9073 engine.qmatvec_nvfp4_fast(
9074 &bank.expert(slot),
9075 &activations,
9076 1,
9077 bank.in_features,
9078 bank.local_out,
9079 bank.row_bytes,
9080 )?
9081 };
9082 let mut out = engine.dtoh(&output)?;
9083 apply_macro(&mut out, macros[expert]);
9084 Ok(out)
9085 }
9086
9087 fn run_full_down_expert_nvfp4(
9090 &self,
9091 shards: &[ResidentNvfp4RowBankRank],
9092 macros: &[f32],
9093 expert: usize,
9094 input: &[f32],
9095 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9096 let owner = expert & 1;
9097 let slot = expert >> 1;
9098 let shard = shards
9099 .get(owner)
9100 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9101 let engine = &self.ranks[owner];
9102 let _main = engine.gpu.enter_main()?;
9103 let activations = engine.htod(input)?;
9104 let output = if nvfp4_bank_v2_on() {
9105 engine.qmatvec_nvfp4_fast_v2(
9106 &shard.expert(slot),
9107 &activations,
9108 1,
9109 shard.local_in,
9110 shard.out_features,
9111 shard.row_bytes,
9112 )?
9113 } else {
9114 engine.qmatvec_nvfp4_fast(
9115 &shard.expert(slot),
9116 &activations,
9117 1,
9118 shard.local_in,
9119 shard.out_features,
9120 shard.row_bytes,
9121 )?
9122 };
9123 let mut out = engine.dtoh(&output)?;
9124 apply_macro(&mut out, macros[expert]);
9125 Ok(out)
9126 }
9127
9128 fn run_column_bank_expert_nvfp4(
9129 &self,
9130 ranks: &[ResidentNvfp4ColumnBankRank],
9131 macros: &[f32],
9132 expert: usize,
9133 input: &[f32],
9134 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9135 let local_out = ranks
9136 .first()
9137 .ok_or("NVFP4 TP column bank has no ranks")?
9138 .local_out;
9139 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9140 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9141 let _main = engine.gpu.enter_main()?;
9142 let activations = engine.htod(input)?;
9143 let output = if nvfp4_bank_v2_on() {
9144 engine.qmatvec_nvfp4_fast_v2(
9145 &bank.expert(expert),
9146 &activations,
9147 1,
9148 bank.in_features,
9149 bank.local_out,
9150 bank.row_bytes,
9151 )?
9152 } else {
9153 engine.qmatvec_nvfp4_fast(
9154 &bank.expert(expert),
9155 &activations,
9156 1,
9157 bank.in_features,
9158 bank.local_out,
9159 bank.row_bytes,
9160 )?
9161 };
9162 let output = engine.dtoh(&output)?;
9163 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9164 }
9165 apply_macro(&mut gathered, macros[expert]);
9166 Ok(gathered)
9167 }
9168
9169 fn run_row_bank_expert_nvfp4(
9173 &self,
9174 shards: &[ResidentNvfp4RowBankRank],
9175 macros: &[f32],
9176 expert: usize,
9177 input: &[f32],
9178 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9179 let out_features = shards
9180 .first()
9181 .ok_or("NVFP4 TP row bank has no canonical shards")?
9182 .out_features;
9183 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
9184 let mut reduced = vec![0.0f32; out_features];
9185 for (shard_index, shard) in shards.iter().enumerate() {
9186 let engine = self
9187 .ranks
9188 .get(shard.device_rank)
9189 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
9190 let _main = engine.gpu.enter_main()?;
9191 let local_activations =
9192 activation_shard(input, 1, in_features, shards.len(), shard_index);
9193 let activations = engine.htod(&local_activations)?;
9194 let output = if nvfp4_bank_v2_on() {
9195 engine.qmatvec_nvfp4_fast_v2(
9196 &shard.expert(expert),
9197 &activations,
9198 1,
9199 shard.local_in,
9200 shard.out_features,
9201 shard.row_bytes,
9202 )?
9203 } else {
9204 engine.qmatvec_nvfp4_fast(
9205 &shard.expert(expert),
9206 &activations,
9207 1,
9208 shard.local_in,
9209 shard.out_features,
9210 shard.row_bytes,
9211 )?
9212 };
9213 let partial = engine.dtoh(&output)?;
9214 for (sum, value) in reduced.iter_mut().zip(&partial) {
9215 *sum += *value;
9216 }
9217 }
9218 apply_macro(&mut reduced, macros[expert]);
9219 Ok(reduced)
9220 }
9221
9222 pub fn upload_expert_parallel_nvfp4(
9226 &self,
9227 gate: Nvfp4ExpertBank<'_>,
9228 up: Nvfp4ExpertBank<'_>,
9229 down: Nvfp4ExpertBank<'_>,
9230 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
9231 gate.validate()?;
9232 up.validate()?;
9233 down.validate()?;
9234 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9235 return Err("NVFP4 EP gate/up/down expert counts differ".into());
9236 }
9237 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9238 return Err("NVFP4 EP gate/up dimensions differ".into());
9239 }
9240 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9241 return Err(format!(
9242 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9243 down.out_features, down.in_features, gate.out_features, gate.in_features
9244 )
9245 .into());
9246 }
9247 let world = self.ranks.len();
9248 if gate.expert_count % world != 0 {
9249 return Err(format!(
9250 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9251 gate.expert_count
9252 )
9253 .into());
9254 }
9255 let experts_per_rank = gate.expert_count / world;
9256 let mut ranks = Vec::with_capacity(world);
9257 for (rank_index, engine) in self.ranks.iter().enumerate() {
9258 let _main = engine.gpu.enter_main()?;
9259 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9260 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9261 let mut up_experts = Vec::with_capacity(experts_per_rank);
9262 let mut down_experts = Vec::with_capacity(experts_per_rank);
9263 for expert in expert_range.clone() {
9264 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9265 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9266 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9267 }
9268 ranks.push(ResidentNvfp4EpRank {
9269 gate: gate_experts,
9270 up: up_experts,
9271 down: down_experts,
9272 expert_range,
9273 });
9274 }
9275 Ok(ResidentNvfp4ExpertParallel {
9276 ranks,
9277 macros_gate: gate.macros.to_vec(),
9278 macros_up: up.macros.to_vec(),
9279 macros_down: down.macros.to_vec(),
9280 expert_count: gate.expert_count,
9281 input_width: gate.in_features,
9282 expert_width: gate.out_features,
9283 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9284 down_row_bytes: nvfp4_row_bytes(down.in_features),
9285 })
9286 }
9287
9288 #[allow(clippy::too_many_arguments)]
9294 pub fn run_routed_experts_nvfp4(
9295 &self,
9296 experts: &ResidentNvfp4ExpertParallel,
9297 input: &[f32],
9298 tokens: usize,
9299 selected: &[usize],
9300 route_weights: &[f32],
9301 experts_per_token: usize,
9302 activation_limit: Option<f32>,
9303 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9304 validate_activations(input, tokens, experts.input_width)?;
9305 let pairs = tokens
9306 .checked_mul(experts_per_token)
9307 .ok_or("NVFP4 EP route count overflow")?;
9308 if selected.len() != pairs || route_weights.len() != pairs {
9309 return Err(format!(
9310 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9311 {experts_per_token} ({pairs})",
9312 selected.len(),
9313 route_weights.len(),
9314 )
9315 .into());
9316 }
9317 if !route_weights.iter().all(|weight| weight.is_finite()) {
9318 return Err("NVFP4 EP route weights contain a non-finite value".into());
9319 }
9320 let experts_per_rank = experts.expert_count / experts.ranks.len();
9321 let mut output = vec![0.0f32; tokens * experts.input_width];
9322 for token in 0..tokens {
9323 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9324 for slot in 0..experts_per_token {
9325 let pair = token * experts_per_token + slot;
9326 let expert = selected[pair];
9327 if expert >= experts.expert_count {
9328 return Err(format!(
9329 "NVFP4 EP selected expert {expert} outside 0..{}",
9330 experts.expert_count
9331 )
9332 .into());
9333 }
9334 let owner = expert / experts_per_rank;
9335 let local = expert - owner * experts_per_rank;
9336 let rank = &experts.ranks[owner];
9337 let engine = &self.ranks[owner];
9338 let _main = engine.gpu.enter_main()?;
9339 let device_input = engine.htod(input_row)?;
9340 let gate_out = engine.qmatvec_nvfp4_fast(
9341 &rank.gate[local].slice(0..rank.gate[local].len()),
9342 &device_input,
9343 1,
9344 experts.input_width,
9345 experts.expert_width,
9346 experts.gate_row_bytes,
9347 )?;
9348 let up_out = engine.qmatvec_nvfp4_fast(
9349 &rank.up[local].slice(0..rank.up[local].len()),
9350 &device_input,
9351 1,
9352 experts.input_width,
9353 experts.expert_width,
9354 experts.gate_row_bytes,
9355 )?;
9356 let mut gate_host = engine.dtoh(&gate_out)?;
9357 let mut up_host = engine.dtoh(&up_out)?;
9358 apply_macro(&mut gate_host, experts.macros_gate[expert]);
9359 apply_macro(&mut up_host, experts.macros_up[expert]);
9360 let activated: Vec<f32> = gate_host
9361 .iter()
9362 .zip(&up_host)
9363 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
9364 .collect();
9365 let device_activated = engine.htod(&activated)?;
9366 let down_out = engine.qmatvec_nvfp4_fast(
9367 &rank.down[local].slice(0..rank.down[local].len()),
9368 &device_activated,
9369 1,
9370 experts.expert_width,
9371 experts.input_width,
9372 experts.down_row_bytes,
9373 )?;
9374 let mut down_host = engine.dtoh(&down_out)?;
9375 apply_macro(&mut down_host, experts.macros_down[expert]);
9376 let weight = route_weights[pair];
9377 for (sum, value) in output
9378 [token * experts.input_width..(token + 1) * experts.input_width]
9379 .iter_mut()
9380 .zip(down_host)
9381 {
9382 *sum += weight * value;
9383 }
9384 }
9385 }
9386 Ok(output)
9387 }
9388
9389 pub fn run_tensor_parallel_routes_nvfp4_device(
9403 &self,
9404 experts: &ResidentNvfp4TensorParallel,
9405 input: &[f32],
9406 selected: &[usize],
9407 route_weights: &[f32],
9408 experts_per_token: usize,
9409 activation_limit: Option<f32>,
9410 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9411 validate_activations(input, 1, experts.input_width)?;
9412 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9413 return Err(format!(
9414 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
9415 selected.len(),
9416 route_weights.len(),
9417 )
9418 .into());
9419 }
9420 if !route_weights.iter().all(|weight| weight.is_finite()) {
9421 return Err("NVFP4 device route weights contain a non-finite value".into());
9422 }
9423 let world = self.ranks.len();
9424 if world != NVFP4_CANONICAL_ROW_SHARDS {
9425 return Err(format!(
9426 "NVFP4 device routes require world == canonical shard grid \
9427 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9428 )
9429 .into());
9430 }
9431 let local_out = if experts.ep2 {
9432 experts.expert_width
9433 } else {
9434 experts.expert_width / world
9435 };
9436
9437 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9441 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9442 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9443 let started = timing.then(std::time::Instant::now);
9444
9445 let n_sel = experts_per_token;
9446 let mut workspace_guard = experts
9447 .device_workspace
9448 .lock()
9449 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9450 if workspace_guard.is_none() {
9451 let mut gate_out = Vec::with_capacity(world);
9452 let mut up_out = Vec::with_capacity(world);
9453 let mut act_q = Vec::with_capacity(world);
9454 let mut act_d = Vec::with_capacity(world);
9455 let mut sel = Vec::with_capacity(world);
9456 let mut partial = Vec::with_capacity(world);
9457 let mut accumulator = Vec::with_capacity(world);
9458 let mut combine_w = Vec::with_capacity(world);
9459 let mut route_w = Vec::with_capacity(world);
9460 let mut in_q = Vec::with_capacity(world);
9461 let mut in_d = Vec::with_capacity(world);
9462 let mut input = Vec::with_capacity(world);
9463 let mut ev_rank = Vec::with_capacity(world);
9464 let moe_direct = moe_direct_on();
9465 for (rank, engine) in self.ranks.iter().enumerate() {
9466 let _main = engine.gpu.enter_main()?;
9467 gate_out.push(engine.uninit(n_sel * local_out)?);
9468 up_out.push(engine.uninit(n_sel * local_out)?);
9469 act_q.push(engine.uninit_i8(n_sel * local_out)?);
9470 act_d.push(engine.uninit(n_sel * local_out / 32)?);
9471 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
9472 partial.push(engine.uninit(n_sel * experts.input_width)?);
9473 if moe_direct && rank != 0 {
9475 let root = &self.ranks[0];
9476 let _root_main = root.gpu.enter_main()?;
9477 accumulator.push(root.zeros(experts.input_width)?);
9478 } else {
9479 accumulator.push(engine.zeros(experts.input_width)?);
9480 }
9481 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9482 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9483 in_q.push(engine.uninit_i8(experts.input_width)?);
9484 in_d.push(engine.uninit(experts.input_width / 32)?);
9485 input.push(engine.uninit(experts.input_width)?);
9486 ev_rank.push(engine.ctx().new_event(None)?);
9487 }
9488 let root = &self.ranks[0];
9489 let _main = root.gpu.enter_main()?;
9490 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
9491 prestaged: false,
9492 rank1_routed: false,
9493 ev_input: None,
9494 fence_flags_raw: 0,
9495 fence_ticket: 0,
9496 gate_out,
9497 up_out,
9498 act_q,
9499 act_d,
9500 sel,
9501 partial,
9502 accumulator,
9503 combine_w,
9504 route_w,
9505 in_q,
9506 in_d,
9507 dev_route_e: None,
9508 in_stage_e: None,
9509 out_stage_e: None,
9510 routes_graph: None,
9511 raw_dev_route_e: None,
9512 raw_combine: None,
9513 raw_input: Vec::new(),
9514 raw_sel: Vec::new(),
9515 raw_route_w: Vec::new(),
9516 remote: root.uninit(experts.input_width)?,
9517 combined: root.uninit(experts.input_width)?,
9518 n_sel,
9519 input,
9520 ev_rank,
9521 ev_done: Some(root.ctx().new_event(None)?),
9522 ev_entry: None,
9523 });
9524 }
9525 let workspace = workspace_guard
9526 .as_mut()
9527 .expect("NVFP4 device routes workspace initialized above");
9528 if experts.ep2 {
9531 return Ok(vec![0.0f32; experts.input_width]);
9532 }
9533 if workspace.n_sel != n_sel {
9534 return Err(format!(
9535 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
9536 workspace.n_sel
9537 )
9538 .into());
9539 }
9540 for &expert in selected {
9541 if expert >= experts.expert_count {
9542 return Err(format!(
9543 "NVFP4 device selected expert {expert} outside 0..{}",
9544 experts.expert_count
9545 )
9546 .into());
9547 }
9548 }
9549 let sel_i32 = selected
9550 .iter()
9551 .map(|&expert| expert as i32)
9552 .collect::<Vec<_>>();
9553
9554 for (rank_index, engine) in self.ranks.iter().enumerate() {
9561 let _main = engine.gpu.enter_main()?;
9562 let device_input = engine.htod(input)?;
9563 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
9564 engine.quantize_q8_1_into(
9565 &device_input,
9566 1,
9567 experts.input_width,
9568 &mut in_q[rank_index],
9569 &mut in_d[rank_index],
9570 )?;
9571 }
9573 self.nvfp4_routes_batched_sweeps(
9574 experts,
9575 workspace,
9576 selected,
9577 route_weights,
9578 &sel_i32,
9579 local_out,
9580 n_sel,
9581 activation_limit,
9582 false,
9583 )?;
9584
9585 let root = &self.ranks[0];
9588 for engine in &self.ranks[1..] {
9589 let _main = engine.gpu.enter_main()?;
9590 engine.stream().synchronize()?;
9591 }
9592 let _main = root.gpu.enter_main()?;
9593 root.stream()
9594 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
9595 root.add(
9596 &workspace.accumulator[0],
9597 &workspace.remote,
9598 &mut workspace.combined,
9599 experts.input_width,
9600 )?;
9601 let output = root.dtoh(&workspace.combined)?;
9602 if let Some(started) = started {
9603 use std::sync::atomic::Ordering;
9604 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9605 + started.elapsed().as_nanos() as u64;
9606 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9607 if calls % 430 == 0 {
9608 eprintln!(
9609 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9610 ns as f64 / 1.0e6,
9611 ns as f64 / calls as f64 / 1.0e3,
9612 );
9613 }
9614 }
9615 Ok(output)
9616 }
9617
9618 #[allow(clippy::too_many_arguments)]
9623 fn nvfp4_routes_batched_sweeps(
9624 &self,
9625 experts: &ResidentNvfp4TensorParallel,
9626 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9627 selected: &[usize],
9628 route_weights: &[f32],
9629 sel_i32: &[i32],
9630 local_out: usize,
9631 n_sel: usize,
9632 activation_limit: Option<f32>,
9633 device_routed: bool,
9634 ) -> Result<(), Box<dyn std::error::Error>> {
9635 for rank_index in 0..self.ranks.len() {
9636 self.nvfp4_routes_batched_sweeps_rank(
9637 experts,
9638 workspace,
9639 selected,
9640 route_weights,
9641 sel_i32,
9642 local_out,
9643 n_sel,
9644 activation_limit,
9645 device_routed,
9646 rank_index,
9647 )?;
9648 }
9649 Ok(())
9650 }
9651
9652 #[allow(clippy::too_many_arguments)]
9655 fn nvfp4_routes_batched_sweeps_rank(
9656 &self,
9657 experts: &ResidentNvfp4TensorParallel,
9658 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9659 selected: &[usize],
9660 route_weights: &[f32],
9661 sel_i32: &[i32],
9662 local_out: usize,
9663 n_sel: usize,
9664 activation_limit: Option<f32>,
9665 device_routed: bool,
9666 rank_index: usize,
9667 ) -> Result<(), Box<dyn std::error::Error>> {
9668 {
9669 let engine = &self.ranks[rank_index];
9670 let _main = engine.gpu.enter_main()?;
9671 if experts.ep2 {
9676 if !device_routed {
9677 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
9678 }
9679 let gate_bank = &experts.gate[rank_index];
9680 let up_bank = &experts.up[rank_index];
9681 if gate_bank.local_out != experts.expert_width
9682 || gate_bank.expert_bytes != up_bank.expert_bytes
9683 {
9684 return Err("NVFP4 EP2 bank geometry drifted".into());
9685 }
9686 {
9687 let Nvfp4DeviceRoutesWorkspace {
9688 sel,
9689 gate_out,
9690 up_out,
9691 in_q,
9692 in_d,
9693 ..
9694 } = &mut *workspace;
9695 engine.qmatvec_nvfp4_sel_gu_ep_into(
9696 &gate_bank.bank,
9697 &up_bank.bank,
9698 &sel[rank_index],
9699 &in_q[rank_index],
9700 &in_d[rank_index],
9701 &mut gate_out[rank_index],
9702 &mut up_out[rank_index],
9703 n_sel,
9704 gate_bank.in_features,
9705 gate_bank.local_out,
9706 gate_bank.row_bytes,
9707 gate_bank.expert_bytes,
9708 rank_index,
9709 )?;
9710 }
9711 {
9712 let Nvfp4DeviceRoutesWorkspace {
9713 gate_out,
9714 up_out,
9715 sel,
9716 act_q,
9717 act_d,
9718 ..
9719 } = &mut *workspace;
9720 engine.silu_mul_scaled_q8_1_sel_ep_into(
9721 &gate_out[rank_index],
9722 &up_out[rank_index],
9723 &experts.macros_gate_dev[rank_index],
9724 &experts.macros_up_dev[rank_index],
9725 &sel[rank_index],
9726 activation_limit,
9727 &mut act_q[rank_index],
9728 &mut act_d[rank_index],
9729 local_out,
9730 n_sel,
9731 rank_index,
9732 )?;
9733 }
9734 let shard = &experts.down[rank_index];
9735 if shard.device_rank != rank_index || shard.local_in != local_out {
9736 return Err("NVFP4 EP2 down bank placement drifted".into());
9737 }
9738 {
9739 let Nvfp4DeviceRoutesWorkspace {
9740 sel,
9741 act_q,
9742 act_d,
9743 route_w,
9744 accumulator,
9745 ..
9746 } = &mut *workspace;
9747 engine.qmatvec_nvfp4_sel_down8_ep_into(
9748 &shard.bank,
9749 &sel[rank_index],
9750 &act_q[rank_index],
9751 &act_d[rank_index],
9752 &route_w[rank_index],
9753 &experts.macros_down_dev[rank_index],
9754 &mut accumulator[rank_index],
9755 n_sel,
9756 shard.local_in,
9757 shard.out_features,
9758 shard.row_bytes,
9759 shard.expert_bytes,
9760 local_out,
9761 local_out / 32,
9762 rank_index,
9763 )?;
9764 }
9765 return Ok(());
9766 }
9767 if !device_routed {
9768 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
9769 let folded = (0..n_sel)
9772 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
9773 .collect::<Vec<_>>();
9774 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
9775 engine.stream().memcpy_htod(&folded, &mut view)?;
9776 }
9777 let gate_bank = &experts.gate[rank_index];
9778 let up_bank = &experts.up[rank_index];
9779 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
9780 let gu_fused = nvfp4_bank_v2_on()
9783 && gate_bank.in_features == up_bank.in_features
9784 && gate_bank.local_out == up_bank.local_out
9785 && gate_bank.row_bytes == up_bank.row_bytes
9786 && gate_bank.expert_bytes == up_bank.expert_bytes;
9787 if gu_fused {
9788 let Nvfp4DeviceRoutesWorkspace {
9789 sel,
9790 gate_out,
9791 up_out,
9792 in_q,
9793 in_d,
9794 ..
9795 } = &mut *workspace;
9796 engine.qmatvec_nvfp4_sel_gu_into(
9797 &gate_bank.bank,
9798 &up_bank.bank,
9799 &sel[rank_index],
9800 &in_q[rank_index],
9801 &in_d[rank_index],
9802 &mut gate_out[rank_index],
9803 &mut up_out[rank_index],
9804 n_sel,
9805 gate_bank.in_features,
9806 gate_bank.local_out,
9807 gate_bank.row_bytes,
9808 gate_bank.expert_bytes,
9809 )?;
9810 } else {
9811 engine.qmatvec_nvfp4_sel_into(
9812 &gate_bank.bank,
9813 &workspace.sel[rank_index],
9814 aq,
9815 ad,
9816 &mut workspace.gate_out[rank_index],
9817 n_sel,
9818 gate_bank.in_features,
9819 gate_bank.local_out,
9820 gate_bank.row_bytes,
9821 gate_bank.expert_bytes,
9822 0,
9823 0,
9824 )?;
9825 engine.qmatvec_nvfp4_sel_into(
9826 &up_bank.bank,
9827 &workspace.sel[rank_index],
9828 aq,
9829 ad,
9830 &mut workspace.up_out[rank_index],
9831 n_sel,
9832 up_bank.in_features,
9833 up_bank.local_out,
9834 up_bank.row_bytes,
9835 up_bank.expert_bytes,
9836 0,
9837 0,
9838 )?;
9839 }
9840 {
9844 let Nvfp4DeviceRoutesWorkspace {
9845 gate_out,
9846 up_out,
9847 sel,
9848 act_q,
9849 act_d,
9850 ..
9851 } = &mut *workspace;
9852 engine.silu_mul_scaled_q8_1_sel_into(
9853 &gate_out[rank_index],
9854 &up_out[rank_index],
9855 &experts.macros_gate_dev[rank_index],
9856 &experts.macros_up_dev[rank_index],
9857 &sel[rank_index],
9858 activation_limit,
9859 &mut act_q[rank_index],
9860 &mut act_d[rank_index],
9861 local_out,
9862 n_sel,
9863 )?;
9864 }
9865 let shard = &experts.down[rank_index];
9866 if shard.device_rank != rank_index || shard.local_in != local_out {
9867 return Err(
9868 "NVFP4 device routes: down canonical shard placement drifted from \
9869 the gate/up column split"
9870 .into(),
9871 );
9872 }
9873 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
9880 if down8 {
9881 let Nvfp4DeviceRoutesWorkspace {
9882 sel,
9883 act_q,
9884 act_d,
9885 route_w,
9886 accumulator,
9887 ..
9888 } = &mut *workspace;
9889 engine.qmatvec_nvfp4_sel_down8_into(
9890 &shard.bank,
9891 &sel[rank_index],
9892 &act_q[rank_index],
9893 &act_d[rank_index],
9894 &route_w[rank_index],
9895 &experts.macros_down_dev[rank_index],
9896 &mut accumulator[rank_index],
9897 n_sel,
9898 shard.local_in,
9899 shard.out_features,
9900 shard.row_bytes,
9901 shard.expert_bytes,
9902 local_out,
9903 local_out / 32,
9904 )?;
9905 } else {
9906 let Nvfp4DeviceRoutesWorkspace {
9907 sel,
9908 act_q,
9909 act_d,
9910 partial,
9911 ..
9912 } = &mut *workspace;
9913 engine.qmatvec_nvfp4_sel_into(
9914 &shard.bank,
9915 &sel[rank_index],
9916 &act_q[rank_index],
9917 &act_d[rank_index],
9918 &mut partial[rank_index],
9919 n_sel,
9920 shard.local_in,
9921 shard.out_features,
9922 shard.row_bytes,
9923 shard.expert_bytes,
9924 local_out,
9925 local_out / 32,
9926 )?;
9927 }
9928 if !down8 {
9933 let Nvfp4DeviceRoutesWorkspace {
9934 partial,
9935 combine_w,
9936 route_w,
9937 sel,
9938 accumulator,
9939 ..
9940 } = &mut *workspace;
9941 if device_routed {
9942 engine.axpy_rows_seq_md_into(
9943 &partial[rank_index],
9944 &route_w[rank_index],
9945 &experts.macros_down_dev[rank_index],
9946 &sel[rank_index],
9947 &mut accumulator[rank_index],
9948 experts.input_width,
9949 n_sel,
9950 )?;
9951 } else {
9952 engine.axpy_rows_seq_into(
9953 &partial[rank_index],
9954 &combine_w[rank_index],
9955 &mut accumulator[rank_index],
9956 experts.input_width,
9957 n_sel,
9958 )?;
9959 }
9960 }
9961 }
9962 Ok(())
9963 }
9964
9965 pub fn run_tensor_parallel_routes_nvfp4_device_io(
9973 &self,
9974 experts: &ResidentNvfp4TensorParallel,
9975 e: &Engine,
9976 input_dev: &crate::CudaSlice<f32>,
9977 selected: &[usize],
9978 route_weights: &[f32],
9979 experts_per_token: usize,
9980 activation_limit: Option<f32>,
9981 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9982 if input_dev.len() != experts.input_width {
9983 return Err(format!(
9984 "NVFP4 device-io routes input {} != width {}",
9985 input_dev.len(),
9986 experts.input_width
9987 )
9988 .into());
9989 }
9990 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9991 return Err(format!(
9992 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
9993 selected.len(),
9994 route_weights.len(),
9995 )
9996 .into());
9997 }
9998 if !route_weights.iter().all(|weight| weight.is_finite()) {
9999 return Err("NVFP4 device route weights contain a non-finite value".into());
10000 }
10001 let world = self.ranks.len();
10002 if world != NVFP4_CANONICAL_ROW_SHARDS {
10003 return Err(format!(
10004 "NVFP4 device routes require world == canonical shard grid \
10005 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10006 )
10007 .into());
10008 }
10009 let local_out = experts.expert_width / world;
10010 let n_sel = experts_per_token;
10011
10012 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10013 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10014 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10015 let started = timing.then(std::time::Instant::now);
10016
10017 let mut workspace_guard = experts
10018 .device_workspace
10019 .lock()
10020 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10021 if workspace_guard.is_none() {
10022 drop(workspace_guard);
10023 let zero = vec![0.0f32; experts.input_width];
10026 let zero_sel = vec![0usize; n_sel];
10027 let zero_w = vec![0.0f32; n_sel];
10028 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10029 experts,
10030 &zero,
10031 &zero_sel,
10032 &zero_w,
10033 n_sel,
10034 activation_limit,
10035 )?;
10036 workspace_guard = experts
10037 .device_workspace
10038 .lock()
10039 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10040 }
10041 let workspace = workspace_guard
10042 .as_mut()
10043 .expect("NVFP4 device routes workspace initialized above");
10044 if workspace.n_sel != n_sel {
10045 return Err(format!(
10046 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10047 workspace.n_sel
10048 )
10049 .into());
10050 }
10051 for &expert in selected {
10052 if expert >= experts.expert_count {
10053 return Err(format!(
10054 "NVFP4 device selected expert {expert} outside 0..{}",
10055 experts.expert_count
10056 )
10057 .into());
10058 }
10059 }
10060 let sel_i32 = selected
10061 .iter()
10062 .map(|&expert| expert as i32)
10063 .collect::<Vec<_>>();
10064
10065 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10069 if *device != e.ctx().ordinal() {
10070 return Err("NVFP4 device-io routes engine changed".into());
10071 }
10072 } else {
10073 let _main = e.gpu.enter_main()?;
10074 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10075 }
10076 {
10077 let _main = e.gpu.enter_main()?;
10078 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10079 ev_entry.record(&e.stream())?;
10080 }
10081 for (rank_index, engine) in self.ranks.iter().enumerate() {
10082 let _main = engine.gpu.enter_main()?;
10083 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10084 engine.stream().wait(ev_entry)?;
10085 {
10086 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10087 engine
10088 .stream()
10089 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10090 }
10091 {
10092 let Nvfp4DeviceRoutesWorkspace {
10093 input, in_q, in_d, ..
10094 } = &mut *workspace;
10095 engine.quantize_q8_1_into(
10096 &input[rank_index],
10097 1,
10098 experts.input_width,
10099 &mut in_q[rank_index],
10100 &mut in_d[rank_index],
10101 )?;
10102 }
10103 }
10104 self.nvfp4_routes_batched_sweeps(
10105 experts,
10106 workspace,
10107 selected,
10108 route_weights,
10109 &sel_i32,
10110 local_out,
10111 n_sel,
10112 activation_limit,
10113 false,
10114 )?;
10115
10116 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10122 let _main = engine.gpu.enter_main()?;
10123 workspace.ev_rank[rank_index].record(&engine.stream())?;
10124 }
10125 if moe_direct_on() && self.ranks.len() == 2 {
10126 {
10133 let root = &self.ranks[0];
10134 let _main = root.gpu.enter_main()?;
10135 workspace
10136 .ev_done
10137 .as_ref()
10138 .expect("device routes done event")
10139 .record(&root.stream())?;
10140 }
10141 let _main = e.gpu.enter_main()?;
10142 e.stream().wait(
10143 workspace
10144 .ev_done
10145 .as_ref()
10146 .expect("device routes done event"),
10147 )?;
10148 for ev in workspace.ev_rank.iter().skip(1) {
10149 e.stream().wait(ev)?;
10150 }
10151 let mut output = e.uninit(experts.input_width)?;
10152 e.add(
10153 &workspace.accumulator[0],
10154 &workspace.accumulator[1],
10155 &mut output,
10156 experts.input_width,
10157 )?;
10158 let output = output;
10159 if let Some(started) = started {
10160 use std::sync::atomic::Ordering;
10161 let ns = TIMING_NS
10162 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10163 + started.elapsed().as_nanos() as u64;
10164 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10165 if calls % 430 == 0 {
10166 eprintln!(
10167 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10168 ns as f64 / 1.0e6,
10169 ns as f64 / calls as f64 / 1.0e3,
10170 );
10171 }
10172 }
10173 return Ok(output);
10174 }
10175 {
10176 let root = &self.ranks[0];
10177 let _main = root.gpu.enter_main()?;
10178 for ev in workspace.ev_rank.iter().skip(1) {
10179 root.stream().wait(ev)?;
10180 }
10181 root.stream()
10182 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10183 {
10184 let Nvfp4DeviceRoutesWorkspace {
10185 accumulator,
10186 remote,
10187 combined,
10188 ..
10189 } = &mut *workspace;
10190 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10191 }
10192 workspace
10193 .ev_done
10194 .as_ref()
10195 .expect("device routes done event")
10196 .record(&root.stream())?;
10197 }
10198 let output = {
10199 let _main = e.gpu.enter_main()?;
10200 e.stream().wait(
10201 workspace
10202 .ev_done
10203 .as_ref()
10204 .expect("device routes done event"),
10205 )?;
10206 let mut output = e.uninit(experts.input_width)?;
10209 e.stream().memcpy_dtod(
10210 &workspace.combined.slice(0..experts.input_width),
10211 &mut output.slice_mut(0..experts.input_width),
10212 )?;
10213 output
10214 };
10215 if let Some(started) = started {
10216 use std::sync::atomic::Ordering;
10217 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10218 + started.elapsed().as_nanos() as u64;
10219 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10220 if calls % 430 == 0 {
10221 eprintln!(
10222 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10223 ns as f64 / 1.0e6,
10224 ns as f64 / calls as f64 / 1.0e3,
10225 );
10226 }
10227 }
10228 Ok(output)
10229 }
10230
10231 #[allow(clippy::too_many_arguments)]
10237 pub fn nvfp4_routes_prestage(
10242 &self,
10243 experts: &ResidentNvfp4TensorParallel,
10244 e: &Engine,
10245 input_dev: &crate::CudaSlice<f32>,
10246 ) -> Result<bool, Box<dyn std::error::Error>> {
10247 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
10248 }
10249
10250 pub fn nvfp4_routes_prestage_with(
10256 &self,
10257 experts: &ResidentNvfp4TensorParallel,
10258 e: &Engine,
10259 input_dev: &crate::CudaSlice<f32>,
10260 rank1_router: impl FnOnce(
10261 &Engine,
10262 &crate::CudaSlice<f32>,
10263 &mut crate::CudaSlice<i32>,
10264 &mut crate::CudaSlice<f32>,
10265 ) -> Result<bool, Box<dyn std::error::Error>>,
10266 ) -> Result<bool, Box<dyn std::error::Error>> {
10267 if !routes_prestage_on() || step_tp_graph_enabled()? {
10268 return Ok(false);
10269 }
10270 if input_dev.len() != experts.input_width {
10271 return Err("NVFP4 prestage input width mismatch".into());
10272 }
10273 let mut workspace_guard = experts
10274 .device_workspace
10275 .lock()
10276 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10277 let Some(workspace) = workspace_guard.as_mut() else {
10278 return Ok(false);
10279 };
10280 if workspace.ev_input.is_none() {
10281 let _main = e.gpu.enter_main()?;
10282 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10283 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
10284 return Err("NVFP4 prestage engine changed".into());
10285 }
10286 {
10287 let _main = e.gpu.enter_main()?;
10288 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10289 ev.record(&e.stream())?;
10290 }
10291 for (rank_index, engine) in self.ranks.iter().enumerate() {
10292 let _main = engine.gpu.enter_main()?;
10293 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10294 engine.stream().wait(ev)?;
10295 {
10296 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10297 engine
10298 .stream()
10299 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10300 }
10301 {
10302 let Nvfp4DeviceRoutesWorkspace {
10303 input, in_q, in_d, ..
10304 } = &mut *workspace;
10305 engine.quantize_q8_1_into(
10306 &input[rank_index],
10307 1,
10308 experts.input_width,
10309 &mut in_q[rank_index],
10310 &mut in_d[rank_index],
10311 )?;
10312 }
10313 }
10314 if self.ranks.len() == 2 {
10315 let rank1 = &self.ranks[1];
10316 let _r1 = rank1.gpu.enter_main()?;
10317 let Nvfp4DeviceRoutesWorkspace {
10318 input,
10319 sel,
10320 route_w,
10321 ..
10322 } = &mut *workspace;
10323 let (in1, rest_sel) = (&input[1], &mut sel[1]);
10324 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
10325 workspace.rank1_routed = true;
10326 }
10327 }
10328 workspace.prestaged = true;
10329 Ok(true)
10330 }
10331
10332 #[allow(clippy::too_many_arguments)]
10343 pub fn run_tensor_parallel_routes_nvfp4_device_routed_t2(
10344 &self,
10345 experts: &ResidentNvfp4TensorParallel,
10346 e: &Engine,
10347 z2: &crate::CudaSlice<f32>,
10348 sel_d: &crate::CudaSlice<i32>,
10349 w_d: &crate::CudaSlice<f32>,
10350 n_sel_col: usize,
10351 activation_limit: Option<f32>,
10352 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10353 let world = self.ranks.len();
10354 if world != NVFP4_CANONICAL_ROW_SHARDS {
10355 return Err("NVFP4 t2 routes require the canonical 2-shard grid".into());
10356 }
10357 let width = experts.input_width;
10358 let n_sel = 2 * n_sel_col;
10359 if z2.len() < 2 * width || sel_d.len() < n_sel || w_d.len() < n_sel {
10360 return Err("NVFP4 t2 routes geometry".into());
10361 }
10362 if !nvfp4_bank_v2_on() {
10363 return Err("NVFP4 t2 routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
10364 }
10365 let local_out = experts.expert_width / world;
10366 let mut guard = experts
10367 .t2_workspace
10368 .lock()
10369 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
10370 if guard.as_ref().is_none_or(|ws| ws.n_sel != n_sel) {
10371 let mut input2 = Vec::new();
10372 let mut in_q2 = Vec::new();
10373 let mut in_d2 = Vec::new();
10374 let mut sel2 = Vec::new();
10375 let mut route_w2 = Vec::new();
10376 let mut gate_out2 = Vec::new();
10377 let mut up_out2 = Vec::new();
10378 let mut act_q2 = Vec::new();
10379 let mut act_d2 = Vec::new();
10380 let mut partial2 = Vec::new();
10381 let mut acc_a = Vec::new();
10382 let mut acc_b = Vec::new();
10383 let mut ev_rank = Vec::new();
10384 for engine in &self.ranks {
10385 let _m = engine.gpu.enter_main()?;
10386 input2.push(engine.uninit(2 * width)?);
10387 in_q2.push(engine.alloc_i8_uninit(2 * width)?);
10388 in_d2.push(engine.uninit(2 * (width / 32))?);
10389 sel2.push(engine.htod_i32(&vec![0i32; n_sel])?);
10390 route_w2.push(engine.uninit(n_sel)?);
10391 gate_out2.push(engine.uninit(n_sel * local_out)?);
10392 up_out2.push(engine.uninit(n_sel * local_out)?);
10393 act_q2.push(engine.alloc_i8_uninit(n_sel * local_out)?);
10394 act_d2.push(engine.uninit(n_sel * (local_out / 32))?);
10395 partial2.push(engine.uninit(n_sel * width)?);
10396 acc_a.push(engine.uninit(width)?);
10397 acc_b.push(engine.uninit(width)?);
10398 ev_rank.push(engine.ctx().new_event(None)?);
10399 }
10400 let root = &self.ranks[0];
10401 let (peer_a, peer_b, omix_a, omix_b, ev_root) = {
10402 let _m = root.gpu.enter_main()?;
10403 (
10404 root.uninit(width)?,
10405 root.uninit(width)?,
10406 root.uninit(width)?,
10407 root.uninit(width)?,
10408 root.ctx().new_event(None)?,
10409 )
10410 };
10411 let ev_entry = {
10412 let _m = e.gpu.enter_main()?;
10413 e.ctx().new_event(None)?
10414 };
10415 *guard = Some(Nvfp4T2Workspace {
10416 input2,
10417 in_q2,
10418 in_d2,
10419 sel2,
10420 route_w2,
10421 gate_out2,
10422 up_out2,
10423 act_q2,
10424 act_d2,
10425 partial2,
10426 acc_a,
10427 acc_b,
10428 peer_a,
10429 peer_b,
10430 omix_a,
10431 omix_b,
10432 ev_entry,
10433 ev_rank,
10434 ev_root,
10435 n_sel,
10436 e_device: e.ctx().ordinal(),
10437 });
10438 }
10439 let ws = guard.as_mut().expect("armed above");
10440 if ws.e_device != e.ctx().ordinal() {
10441 return Err("NVFP4 t2 routes engine changed".into());
10442 }
10443 {
10444 let _main = e.gpu.enter_main()?;
10445 ws.ev_entry.record(&e.stream())?;
10446 }
10447 for rank in 0..world {
10448 let engine = &self.ranks[rank];
10449 let _main = engine.gpu.enter_main()?;
10450 engine.stream().wait(&ws.ev_entry)?;
10451 {
10452 let mut dst = ws.input2[rank].slice_mut(0..2 * width);
10453 engine
10454 .stream()
10455 .memcpy_dtod(&z2.slice(0..2 * width), &mut dst)?;
10456 }
10457 {
10458 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
10459 engine
10460 .stream()
10461 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10462 }
10463 {
10464 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
10465 engine
10466 .stream()
10467 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10468 }
10469 {
10470 let Nvfp4T2Workspace {
10471 input2,
10472 in_q2,
10473 in_d2,
10474 ..
10475 } = &mut *ws;
10476 engine.quantize_q8_1_into(
10477 &input2[rank],
10478 2,
10479 width,
10480 &mut in_q2[rank],
10481 &mut in_d2[rank],
10482 )?;
10483 }
10484 let gate_bank = &experts.gate[rank];
10485 let up_bank = &experts.up[rank];
10486 if gate_bank.in_features != up_bank.in_features
10487 || gate_bank.local_out != up_bank.local_out
10488 || gate_bank.row_bytes != up_bank.row_bytes
10489 || gate_bank.expert_bytes != up_bank.expert_bytes
10490 {
10491 return Err("NVFP4 t2 routes need matched gate/up bank geometry".into());
10492 }
10493 {
10494 let Nvfp4T2Workspace {
10495 sel2,
10496 in_q2,
10497 in_d2,
10498 gate_out2,
10499 up_out2,
10500 ..
10501 } = &mut *ws;
10502 engine.qmatvec_nvfp4_sel_gu_tcol_into(
10503 &gate_bank.bank,
10504 &up_bank.bank,
10505 &sel2[rank],
10506 &in_q2[rank],
10507 &in_d2[rank],
10508 &mut gate_out2[rank],
10509 &mut up_out2[rank],
10510 n_sel,
10511 n_sel_col,
10512 gate_bank.in_features,
10513 gate_bank.local_out,
10514 gate_bank.row_bytes,
10515 gate_bank.expert_bytes,
10516 width,
10517 width / 32,
10518 )?;
10519 }
10520 {
10521 let Nvfp4T2Workspace {
10522 gate_out2,
10523 up_out2,
10524 sel2,
10525 act_q2,
10526 act_d2,
10527 ..
10528 } = &mut *ws;
10529 engine.silu_mul_scaled_q8_1_sel_into(
10530 &gate_out2[rank],
10531 &up_out2[rank],
10532 &experts.macros_gate_dev[rank],
10533 &experts.macros_up_dev[rank],
10534 &sel2[rank],
10535 activation_limit,
10536 &mut act_q2[rank],
10537 &mut act_d2[rank],
10538 local_out,
10539 n_sel,
10540 )?;
10541 }
10542 let shard = &experts.down[rank];
10543 if shard.device_rank != rank || shard.local_in != local_out {
10544 return Err("NVFP4 t2 routes: down shard placement drifted".into());
10545 }
10546 {
10547 let Nvfp4T2Workspace {
10548 sel2,
10549 act_q2,
10550 act_d2,
10551 partial2,
10552 ..
10553 } = &mut *ws;
10554 engine.qmatvec_nvfp4_sel_into(
10555 &shard.bank,
10556 &sel2[rank],
10557 &act_q2[rank],
10558 &act_d2[rank],
10559 &mut partial2[rank],
10560 n_sel,
10561 shard.local_in,
10562 shard.out_features,
10563 shard.row_bytes,
10564 shard.expert_bytes,
10565 local_out,
10566 local_out / 32,
10567 )?;
10568 }
10569 {
10570 let Nvfp4T2Workspace {
10571 partial2,
10572 route_w2,
10573 sel2,
10574 acc_a,
10575 acc_b,
10576 ..
10577 } = &mut *ws;
10578 engine.axpy_rows_seq_md_off_into(
10579 &partial2[rank],
10580 &route_w2[rank],
10581 &experts.macros_down_dev[rank],
10582 &sel2[rank],
10583 &mut acc_a[rank],
10584 width,
10585 n_sel_col,
10586 0,
10587 )?;
10588 engine.axpy_rows_seq_md_off_into(
10589 &partial2[rank],
10590 &route_w2[rank],
10591 &experts.macros_down_dev[rank],
10592 &sel2[rank],
10593 &mut acc_b[rank],
10594 width,
10595 n_sel_col,
10596 n_sel_col,
10597 )?;
10598 }
10599 if rank != 0 {
10600 ws.ev_rank[rank].record(&engine.stream())?;
10601 }
10602 }
10603 let root = &self.ranks[0];
10604 {
10605 let _main = root.gpu.enter_main()?;
10606 for ev in ws.ev_rank.iter().skip(1) {
10607 root.stream().wait(ev)?;
10608 }
10609 {
10610 let Nvfp4T2Workspace {
10611 acc_a,
10612 acc_b,
10613 peer_a,
10614 peer_b,
10615 omix_a,
10616 omix_b,
10617 ..
10618 } = &mut *ws;
10619 {
10620 let mut dst = peer_a.slice_mut(0..width);
10621 root.stream()
10622 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
10623 }
10624 {
10625 let mut dst = peer_b.slice_mut(0..width);
10626 root.stream()
10627 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
10628 }
10629 root.add(&acc_a[0], peer_a, omix_a, width)?;
10630 root.add(&acc_b[0], peer_b, omix_b, width)?;
10631 }
10632 ws.ev_root.record(&root.stream())?;
10633 }
10634 let _main = e.gpu.enter_main()?;
10635 e.stream().wait(&ws.ev_root)?;
10636 let mut out = e.uninit(2 * width)?;
10637 e.stream()
10638 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
10639 e.stream().memcpy_dtod(
10640 &ws.omix_b.slice(0..width),
10641 &mut out.slice_mut(width..2 * width),
10642 )?;
10643 Ok(out)
10644 }
10645
10646 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
10647 &self,
10648 experts: &ResidentNvfp4TensorParallel,
10649 e: &Engine,
10650 input_dev: &crate::CudaSlice<f32>,
10651 sel_d: &crate::CudaSlice<i32>,
10652 w_d: &crate::CudaSlice<f32>,
10653 experts_per_token: usize,
10654 activation_limit: Option<f32>,
10655 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10656 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10657 experts,
10658 e,
10659 input_dev,
10660 sel_d,
10661 w_d,
10662 experts_per_token,
10663 activation_limit,
10664 || Ok(()),
10665 )
10666 }
10667
10668 #[allow(clippy::too_many_arguments)]
10674 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10675 &self,
10676 experts: &ResidentNvfp4TensorParallel,
10677 e: &Engine,
10678 input_dev: &crate::CudaSlice<f32>,
10679 sel_d: &crate::CudaSlice<i32>,
10680 w_d: &crate::CudaSlice<f32>,
10681 experts_per_token: usize,
10682 activation_limit: Option<f32>,
10683 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10684 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10685 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10686 experts,
10687 e,
10688 input_dev,
10689 sel_d,
10690 w_d,
10691 experts_per_token,
10692 activation_limit,
10693 pre_join,
10694 None,
10695 )
10696 }
10697
10698 #[allow(clippy::too_many_arguments)]
10703 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10704 &self,
10705 experts: &ResidentNvfp4TensorParallel,
10706 e: &Engine,
10707 input_dev: &crate::CudaSlice<f32>,
10708 sel_d: &crate::CudaSlice<i32>,
10709 w_d: &crate::CudaSlice<f32>,
10710 experts_per_token: usize,
10711 activation_limit: Option<f32>,
10712 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10713 post_add: Option<(u64, u64)>,
10714 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10715 if input_dev.len() != experts.input_width {
10716 return Err(format!(
10717 "NVFP4 device-routed input {} != width {}",
10718 input_dev.len(),
10719 experts.input_width
10720 )
10721 .into());
10722 }
10723 let n_sel = experts_per_token;
10724 if sel_d.len() < n_sel || w_d.len() < n_sel {
10725 return Err(format!(
10726 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
10727 sel_d.len(),
10728 w_d.len()
10729 )
10730 .into());
10731 }
10732 let world = self.ranks.len();
10733 if world != NVFP4_CANONICAL_ROW_SHARDS {
10734 return Err(format!(
10735 "NVFP4 device routes require world == canonical shard grid \
10736 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10737 )
10738 .into());
10739 }
10740 let local_out = if experts.ep2 {
10741 experts.expert_width
10742 } else {
10743 experts.expert_width / world
10744 };
10745
10746 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10747 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10748 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10749 let started = timing.then(std::time::Instant::now);
10750
10751 let mut workspace_guard = experts
10752 .device_workspace
10753 .lock()
10754 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10755 if workspace_guard.is_none() {
10756 drop(workspace_guard);
10757 let zero = vec![0.0f32; experts.input_width];
10758 let zero_sel = vec![0usize; n_sel];
10759 let zero_w = vec![0.0f32; n_sel];
10760 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10761 experts,
10762 &zero,
10763 &zero_sel,
10764 &zero_w,
10765 n_sel,
10766 activation_limit,
10767 )?;
10768 workspace_guard = experts
10769 .device_workspace
10770 .lock()
10771 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10772 }
10773 let workspace = workspace_guard
10774 .as_mut()
10775 .expect("NVFP4 device routes workspace initialized above");
10776 if workspace.n_sel != n_sel {
10777 return Err(format!(
10778 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10779 workspace.n_sel
10780 )
10781 .into());
10782 }
10783
10784 if step_tp_graph_enabled()? {
10789 if experts.ep2 {
10790 return Err(
10791 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
10792 co-gated; unset one"
10793 .into(),
10794 );
10795 }
10796 if workspace.dev_route_e.is_none() {
10797 let _main = e.gpu.enter_main()?;
10798 workspace.dev_route_e = Some((
10799 e.htod_i32(&vec![0i32; n_sel])?,
10800 e.htod(&vec![0.0f32; n_sel])?,
10801 ));
10802 }
10803 if workspace.in_stage_e.is_none() {
10804 let _main = e.gpu.enter_main()?;
10805 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
10806 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
10807 }
10808 if workspace.routes_graph.is_none() {
10809 let graph = self.nvfp4_routes_build_graph(
10810 experts,
10811 workspace,
10812 local_out,
10813 n_sel,
10814 activation_limit,
10815 )?;
10816 workspace.routes_graph = Some(graph);
10817 eprintln!(
10818 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
10819 children=3 updates=none performance_claim=false"
10820 );
10821 }
10822 let output = {
10823 let _main = e.gpu.enter_main()?;
10824 {
10825 let (sel_e, w_e) = workspace
10826 .dev_route_e
10827 .as_mut()
10828 .expect("device route staging set above");
10829 {
10830 let mut dst = sel_e.slice_mut(0..n_sel);
10831 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10832 }
10833 {
10834 let mut dst = w_e.slice_mut(0..n_sel);
10835 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10836 }
10837 }
10838 {
10839 let in_stage = workspace
10840 .in_stage_e
10841 .as_mut()
10842 .expect("graph staging set above");
10843 let mut dst = in_stage.slice_mut(0..experts.input_width);
10844 e.stream()
10845 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
10846 }
10847 unsafe {
10848 let r = cudarc::driver::sys::cuGraphLaunch(
10849 workspace
10850 .routes_graph
10851 .as_ref()
10852 .expect("routes graph built above")
10853 .exec,
10854 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
10855 );
10856 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
10857 return Err(format!("routes graph launch: {r:?}").into());
10858 }
10859 }
10860 let mut output = e.uninit(experts.input_width)?;
10861 {
10862 let out_stage = workspace
10863 .out_stage_e
10864 .as_ref()
10865 .expect("graph staging set above");
10866 e.stream().memcpy_dtod(
10867 &out_stage.slice(0..experts.input_width),
10868 &mut output.slice_mut(0..experts.input_width),
10869 )?;
10870 }
10871 output
10872 };
10873 if let Some(started) = started {
10874 use std::sync::atomic::Ordering;
10875 let ns = TIMING_NS
10876 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10877 + started.elapsed().as_nanos() as u64;
10878 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10879 if calls % 430 == 0 {
10880 eprintln!(
10881 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10882 ns as f64 / 1.0e6,
10883 ns as f64 / calls as f64 / 1.0e3,
10884 );
10885 }
10886 }
10887 return Ok(output);
10888 }
10889
10890 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10894 if *device != e.ctx().ordinal() {
10895 return Err("NVFP4 device-routed routes engine changed".into());
10896 }
10897 } else {
10898 let _main = e.gpu.enter_main()?;
10899 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10900 }
10901 if workspace.dev_route_e.is_none() {
10902 let _main = e.gpu.enter_main()?;
10903 workspace.dev_route_e = Some((
10904 e.htod_i32(&vec![0i32; n_sel])?,
10905 e.htod(&vec![0.0f32; n_sel])?,
10906 ));
10907 }
10908 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
10914 let e_device = e.ctx().ordinal();
10915 let rank1_routed_peek = workspace.rank1_routed;
10917 let stage_needed = !mirror
10918 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
10919 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
10920 });
10921 {
10922 let _main = e.gpu.enter_main()?;
10923 if stage_needed {
10924 let (sel_e, w_e) = workspace
10925 .dev_route_e
10926 .as_mut()
10927 .expect("device route staging set above");
10928 {
10929 let mut dst = sel_e.slice_mut(0..n_sel);
10930 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10931 }
10932 {
10933 let mut dst = w_e.slice_mut(0..n_sel);
10934 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10935 }
10936 }
10937 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10938 ev_entry.record(&e.stream())?;
10939 }
10940 let prestaged = std::mem::take(&mut workspace.prestaged);
10943 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
10944 for (rank_index, engine) in self.ranks.iter().enumerate() {
10945 let _main = engine.gpu.enter_main()?;
10946 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10947 engine.stream().wait(ev_entry)?;
10948 if !prestaged {
10949 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10950 engine
10951 .stream()
10952 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10953 }
10954 if !(rank1_routed && rank_index == 1) {
10955 let same_dev = engine.ctx().ordinal() == e_device;
10959 if mirror {
10960 let Nvfp4DeviceRoutesWorkspace {
10963 sel,
10964 route_w,
10965 dev_route_e,
10966 ..
10967 } = &mut *workspace;
10968 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
10969 if same_dev {
10970 (sel_d, w_d)
10971 } else {
10972 let (sel_e, w_e) = dev_route_e
10973 .as_ref()
10974 .expect("device route staging set above");
10975 (sel_e, w_e)
10976 };
10977 engine.moe_sel_w_mirror(
10978 src_sel,
10979 src_w,
10980 &mut sel[rank_index],
10981 &mut route_w[rank_index],
10982 n_sel,
10983 )?;
10984 } else {
10985 let (sel_e, w_e) = workspace
10986 .dev_route_e
10987 .as_ref()
10988 .expect("device route staging set above");
10989 {
10990 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
10991 engine
10992 .stream()
10993 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
10994 }
10995 {
10996 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
10997 engine
10998 .stream()
10999 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11000 }
11001 }
11002 }
11003 if !prestaged {
11004 let Nvfp4DeviceRoutesWorkspace {
11005 input, in_q, in_d, ..
11006 } = &mut *workspace;
11007 engine.quantize_q8_1_into(
11008 &input[rank_index],
11009 1,
11010 experts.input_width,
11011 &mut in_q[rank_index],
11012 &mut in_d[rank_index],
11013 )?;
11014 }
11015 }
11016 self.nvfp4_routes_batched_sweeps(
11017 experts,
11018 workspace,
11019 &[],
11020 &[],
11021 &[],
11022 local_out,
11023 n_sel,
11024 activation_limit,
11025 true,
11026 )?;
11027
11028 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11031 let _main = engine.gpu.enter_main()?;
11032 workspace.ev_rank[rank_index].record(&engine.stream())?;
11033 }
11034 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
11037 let mut ticket = 0u32;
11038 if memops {
11039 use cudarc::driver::sys;
11040 if workspace.fence_flags_raw == 0 {
11041 let root = &self.ranks[0];
11042 let _main = root.gpu.enter_main()?;
11043 let mut ptr: sys::CUdeviceptr = 0;
11044 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
11045 if r != sys::CUresult::CUDA_SUCCESS {
11046 return Err(format!("fence flag alloc: {r:?}").into());
11047 }
11048 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
11049 if r != sys::CUresult::CUDA_SUCCESS {
11050 return Err(format!("fence flag memset: {r:?}").into());
11051 }
11052 workspace.fence_flags_raw = ptr as u64;
11053 }
11054 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
11055 ticket = workspace.fence_ticket;
11056 let base = workspace.fence_flags_raw;
11057 if fence_rank1_on() {
11063 let peer = &self.ranks[1];
11064 let _pmain = peer.gpu.enter_main()?;
11065 peer.ring_flag_raw(base, ticket)?;
11066 }
11067 {
11068 let root = &self.ranks[0];
11069 let _main = root.gpu.enter_main()?;
11070 let r = unsafe {
11071 sys::cuStreamWriteValue32_v2(
11072 root.stream().cu_stream() as sys::CUstream,
11073 (base + 4) as sys::CUdeviceptr,
11074 ticket,
11075 0,
11076 )
11077 };
11078 if r != sys::CUresult::CUDA_SUCCESS {
11079 return Err(format!("fence write root: {r:?}").into());
11080 }
11081 }
11082 }
11083 pre_join()?;
11086
11087 if moe_direct_on() && self.ranks.len() == 2 {
11088 let _main = e.gpu.enter_main()?;
11095 if memops {
11096 use cudarc::driver::sys;
11097 let base = workspace.fence_flags_raw;
11098 let r = unsafe {
11099 sys::cuStreamWaitValue32_v2(
11100 e.stream().cu_stream() as sys::CUstream,
11101 (base + 4) as sys::CUdeviceptr,
11102 ticket,
11103 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11104 )
11105 };
11106 if r != sys::CUresult::CUDA_SUCCESS {
11107 return Err(format!("fence wait: {r:?}").into());
11108 }
11109 if fence_rank1_on() {
11110 let r = unsafe {
11112 sys::cuStreamWaitValue32_v2(
11113 e.stream().cu_stream() as sys::CUstream,
11114 base as sys::CUdeviceptr,
11115 ticket,
11116 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11117 )
11118 };
11119 if r != sys::CUresult::CUDA_SUCCESS {
11120 return Err(format!("fence wait rank1: {r:?}").into());
11121 }
11122 } else {
11123 for ev in workspace.ev_rank.iter().skip(1) {
11124 e.stream().wait(ev)?;
11125 }
11126 }
11127 } else {
11128 {
11129 let root = &self.ranks[0];
11130 let _rmain = root.gpu.enter_main()?;
11131 workspace
11132 .ev_done
11133 .as_ref()
11134 .expect("device routes done event")
11135 .record(&root.stream())?;
11136 }
11137 e.stream().wait(
11138 workspace
11139 .ev_done
11140 .as_ref()
11141 .expect("device routes done event"),
11142 )?;
11143 for ev in workspace.ev_rank.iter().skip(1) {
11144 e.stream().wait(ev)?;
11145 }
11146 }
11147 let mut output = e.uninit(experts.input_width)?;
11148 if let Some((sh_raw, scale_raw)) = post_add {
11149 e.add3_raw(
11152 &workspace.accumulator[0],
11153 &workspace.accumulator[1],
11154 sh_raw,
11155 scale_raw,
11156 &mut output,
11157 experts.input_width,
11158 )?;
11159 } else {
11160 e.add(
11161 &workspace.accumulator[0],
11162 &workspace.accumulator[1],
11163 &mut output,
11164 experts.input_width,
11165 )?;
11166 }
11167 let output = output;
11168 if let Some(started) = started {
11169 use std::sync::atomic::Ordering;
11170 let ns = TIMING_NS
11171 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11172 + started.elapsed().as_nanos() as u64;
11173 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11174 if calls % 430 == 0 {
11175 eprintln!(
11176 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11177 ns as f64 / 1.0e6,
11178 ns as f64 / calls as f64 / 1.0e3,
11179 );
11180 }
11181 }
11182 return Ok(output);
11183 }
11184 {
11185 let root = &self.ranks[0];
11186 let _main = root.gpu.enter_main()?;
11187 for ev in workspace.ev_rank.iter().skip(1) {
11188 root.stream().wait(ev)?;
11189 }
11190 root.stream()
11191 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11192 {
11193 let Nvfp4DeviceRoutesWorkspace {
11194 accumulator,
11195 remote,
11196 combined,
11197 ..
11198 } = &mut *workspace;
11199 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11200 }
11201 workspace
11202 .ev_done
11203 .as_ref()
11204 .expect("device routes done event")
11205 .record(&root.stream())?;
11206 }
11207 let output = {
11208 let _main = e.gpu.enter_main()?;
11209 e.stream().wait(
11210 workspace
11211 .ev_done
11212 .as_ref()
11213 .expect("device routes done event"),
11214 )?;
11215 let mut output = e.uninit(experts.input_width)?;
11218 e.stream().memcpy_dtod(
11219 &workspace.combined.slice(0..experts.input_width),
11220 &mut output.slice_mut(0..experts.input_width),
11221 )?;
11222 output
11223 };
11224 if let Some(started) = started {
11225 use std::sync::atomic::Ordering;
11226 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11227 + started.elapsed().as_nanos() as u64;
11228 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11229 if calls % 430 == 0 {
11230 eprintln!(
11231 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11232 ns as f64 / 1.0e6,
11233 ns as f64 / calls as f64 / 1.0e3,
11234 );
11235 }
11236 }
11237 Ok(output)
11238 }
11239
11240 pub(crate) fn decode_v2_finish_root_fused(
11244 &self,
11245 ws: &mut StepTpDecodeV2Ws,
11246 ) -> Result<(), Box<dyn std::error::Error>> {
11247 let root = &self.ranks[0];
11248 let _main = root.gpu.enter_main()?;
11249 if ws.raw_peer_partial != 0 {
11250 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
11252 } else {
11253 root.stream()
11254 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
11255 }
11256 {
11257 let StepTpDecodeV2Ws {
11258 o_partials,
11259 peer_partial,
11260 reduce_a,
11261 o_out,
11262 ..
11263 } = &mut *ws;
11264 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
11265 }
11266 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
11267 if shadows {
11268 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
11271 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
11272 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
11273 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
11274 }
11275 if shadows && ws.raw_peer_partial != 0 {
11276 raw_copy_bytes(
11277 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
11278 ws.raw_k1,
11279 ws.local_kv_dim * 4,
11280 root,
11281 )?;
11282 raw_copy_bytes(
11283 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
11284 ws.raw_v1,
11285 ws.local_kv_dim * 4,
11286 root,
11287 )?;
11288 } else if shadows {
11289 let start = ws.local_kv_dim;
11290 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
11291 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
11292 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
11293 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
11294 }
11295 if ws.raw_mixed_stage_e != 0 {
11296 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
11299 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
11300 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
11301 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
11302 }
11303 Ok(())
11304 }
11305
11306 pub(crate) fn decode_v2_arm_token_mirrors(
11309 &self,
11310 ws: &mut StepTpDecodeV2Ws,
11311 mixed_stage_e: u64,
11312 shadow_stage_e: (u64, u64),
11313 ) -> Result<(), Box<dyn std::error::Error>> {
11314 use cudarc::driver::DevicePtr;
11315 let root = &self.ranks[0];
11316 let _main = root.gpu.enter_main()?;
11317 let stream = root.stream();
11318 let (a, _g) = ws.reduce_a.device_ptr(&stream);
11319 ws.raw_reduce_a = a as u64;
11320 ws.raw_mixed_stage_e = mixed_stage_e;
11321 ws.raw_shadow_stage_e = shadow_stage_e;
11322 Ok(())
11323 }
11324
11325 fn nvfp4_routes_build_graph(
11331 &self,
11332 experts: &ResidentNvfp4TensorParallel,
11333 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11334 local_out: usize,
11335 n_sel: usize,
11336 activation_limit: Option<f32>,
11337 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
11338 use cudarc::driver::DevicePtr;
11339 use cudarc::driver::sys;
11340 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
11341 if r == sys::CUresult::CUDA_SUCCESS {
11342 Ok(())
11343 } else {
11344 Err(format!("{what}: {r:?}").into())
11345 }
11346 }
11347 let world = self.ranks.len();
11348 if world != 2 {
11349 return Err("routes graph door is built for the TP2 pair".into());
11350 }
11351 let width = experts.input_width;
11352
11353 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
11355 let stream = engine.stream();
11356 let (ptr, _g) = buf.device_ptr(&stream);
11357 ptr as u64
11358 };
11359 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
11360 let stream = engine.stream();
11361 let (ptr, _g) = buf.device_ptr(&stream);
11362 ptr as u64
11363 };
11364 let (sel_e, w_e) = workspace
11365 .dev_route_e
11366 .as_ref()
11367 .expect("device route staging set before graph build");
11368 let root_engine = &self.ranks[0];
11369 let p_in_stage = ptr_f32(
11370 workspace.in_stage_e.as_ref().expect("graph staging"),
11371 root_engine,
11372 );
11373 let p_out_stage = ptr_f32(
11374 workspace.out_stage_e.as_ref().expect("graph staging"),
11375 root_engine,
11376 );
11377 let p_sel_e = ptr_i32(sel_e, root_engine);
11378 let p_w_e = ptr_f32(w_e, root_engine);
11379 let p_input: Vec<u64> = (0..world)
11380 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
11381 .collect();
11382 let p_sel: Vec<u64> = (0..world)
11383 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
11384 .collect();
11385 let p_route_w: Vec<u64> = (0..world)
11386 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
11387 .collect();
11388 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
11389 let p_remote = ptr_f32(&workspace.remote, root_engine);
11390 let p_combined = ptr_f32(&workspace.combined, root_engine);
11391
11392 let raw_copy = |dst: u64,
11393 src: u64,
11394 bytes: usize,
11395 engine: &Engine|
11396 -> Result<(), Box<dyn std::error::Error>> {
11397 unsafe {
11398 cu_try(
11399 sys::cuMemcpyAsync(
11400 dst as sys::CUdeviceptr,
11401 src as sys::CUdeviceptr,
11402 bytes,
11403 engine.stream().cu_stream() as sys::CUstream,
11404 ),
11405 "routes graph cuMemcpyAsync",
11406 )
11407 }
11408 };
11409
11410 let mut children = Vec::with_capacity(3);
11411 for rank in 0..world {
11412 let engine = &self.ranks[rank];
11413 let _main = engine.gpu.enter_main()?;
11414 let (child, _retained) = engine.capture_graph_retained(|_| {
11415 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
11416 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
11417 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
11418 {
11419 let Nvfp4DeviceRoutesWorkspace {
11420 input, in_q, in_d, ..
11421 } = &mut *workspace;
11422 engine.quantize_q8_1_into(
11423 &input[rank],
11424 1,
11425 width,
11426 &mut in_q[rank],
11427 &mut in_d[rank],
11428 )?;
11429 }
11430 self.nvfp4_routes_batched_sweeps_rank(
11431 experts,
11432 workspace,
11433 &[],
11434 &[],
11435 &[],
11436 local_out,
11437 n_sel,
11438 activation_limit,
11439 true,
11440 rank,
11441 )?;
11442 Ok(())
11443 })?;
11444 children.push(child);
11445 }
11446 {
11447 let root = &self.ranks[0];
11448 let _main = root.gpu.enter_main()?;
11449 let (child, _retained) = root.capture_graph_retained(|_| {
11450 raw_copy(p_remote, p_acc1, width * 4, root)?;
11451 {
11452 let Nvfp4DeviceRoutesWorkspace {
11453 accumulator,
11454 remote,
11455 combined,
11456 ..
11457 } = &mut *workspace;
11458 root.add(&accumulator[0], remote, combined, width)?;
11459 }
11460 raw_copy(p_out_stage, p_combined, width * 4, root)?;
11461 Ok(())
11462 })?;
11463 children.push(child);
11464 }
11465
11466 let mut parent: sys::CUgraph = std::ptr::null_mut();
11467 unsafe {
11468 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
11469 }
11470 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
11471 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
11472 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
11473 unsafe {
11474 cu_try(
11475 sys::cuGraphAddChildGraphNode(
11476 &mut n0,
11477 parent,
11478 std::ptr::null(),
11479 0,
11480 children[0].cu_graph(),
11481 ),
11482 "routes child r0",
11483 )?;
11484 cu_try(
11485 sys::cuGraphAddChildGraphNode(
11486 &mut n1,
11487 parent,
11488 std::ptr::null(),
11489 0,
11490 children[1].cu_graph(),
11491 ),
11492 "routes child r1",
11493 )?;
11494 let deps = [n0, n1];
11495 cu_try(
11496 sys::cuGraphAddChildGraphNode(
11497 &mut n2,
11498 parent,
11499 deps.as_ptr(),
11500 2,
11501 children[2].cu_graph(),
11502 ),
11503 "routes child root",
11504 )?;
11505 }
11506 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
11507 unsafe {
11508 cu_try(
11509 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
11510 "routes instantiate",
11511 )?;
11512 }
11513 Ok(RoutesGraph {
11514 exec,
11515 parent,
11516 _children: children,
11517 })
11518 }
11519
11520 #[allow(clippy::too_many_arguments)]
11524 pub(crate) fn routes_rank_section(
11525 &self,
11526 experts: &ResidentNvfp4TensorParallel,
11527 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11528 raw_input_src: u64,
11529 local_out: usize,
11530 n_sel: usize,
11531 activation_limit: Option<f32>,
11532 rank_index: usize,
11533 ) -> Result<(), Box<dyn std::error::Error>> {
11534 let engine = &self.ranks[rank_index];
11535 {
11536 let _main = engine.gpu.enter_main()?;
11537 let (sel_e_ptr, w_e_ptr) = workspace
11539 .raw_dev_route_e
11540 .ok_or("routes rank section requires armed staging pointers")?;
11541 raw_copy_bytes(
11542 workspace.raw_input[rank_index],
11543 raw_input_src,
11544 experts.input_width * 4,
11545 engine,
11546 )?;
11547 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
11548 raw_copy_bytes(
11549 workspace.raw_route_w[rank_index],
11550 w_e_ptr,
11551 n_sel * 4,
11552 engine,
11553 )?;
11554 {
11555 let Nvfp4DeviceRoutesWorkspace {
11556 input, in_q, in_d, ..
11557 } = &mut *workspace;
11558 engine.quantize_q8_1_into(
11559 &input[rank_index],
11560 1,
11561 experts.input_width,
11562 &mut in_q[rank_index],
11563 &mut in_d[rank_index],
11564 )?;
11565 }
11566 }
11567 self.nvfp4_routes_batched_sweeps_rank(
11568 experts,
11569 workspace,
11570 &[],
11571 &[],
11572 &[],
11573 local_out,
11574 n_sel,
11575 activation_limit,
11576 true,
11577 rank_index,
11578 )
11579 }
11580
11581 pub(crate) fn routes_root_section(
11584 &self,
11585 experts: &ResidentNvfp4TensorParallel,
11586 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11587 ) -> Result<(), Box<dyn std::error::Error>> {
11588 let root = &self.ranks[0];
11589 let _main = root.gpu.enter_main()?;
11590 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
11591 .raw_combine
11592 .ok_or("routes root section requires armed combine pointers")?;
11593 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
11594 {
11595 let Nvfp4DeviceRoutesWorkspace {
11596 accumulator,
11597 remote,
11598 combined,
11599 ..
11600 } = &mut *workspace;
11601 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11602 }
11603 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
11604 Ok(())
11605 }
11606
11607 pub(crate) fn routes_arm_raw(
11610 &self,
11611 experts: &ResidentNvfp4TensorParallel,
11612 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11613 ) -> Result<(), Box<dyn std::error::Error>> {
11614 use cudarc::driver::DevicePtr;
11615 if workspace.raw_dev_route_e.is_some() {
11616 return Ok(());
11617 }
11618 let _ = experts;
11619 let (sel_e, w_e) = workspace
11620 .dev_route_e
11621 .as_ref()
11622 .ok_or("routes staging not armed")?;
11623 let root = &self.ranks[0];
11624 {
11625 let _main = root.gpu.enter_main()?;
11626 let stream = root.stream();
11627 let (a, _g) = sel_e.device_ptr(&stream);
11628 let (b, _g) = w_e.device_ptr(&stream);
11629 workspace.raw_dev_route_e = Some((a as u64, b as u64));
11630 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
11631 let (d, _g) = workspace.remote.device_ptr(&stream);
11632 let (f, _g) = workspace.combined.device_ptr(&stream);
11633 let out_stage = workspace
11634 .out_stage_e
11635 .as_ref()
11636 .ok_or("routes out stage not armed")?;
11637 let (g_, _g) = out_stage.device_ptr(&stream);
11638 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
11639 }
11640 for rank in 0..self.ranks.len() {
11641 let engine = &self.ranks[rank];
11642 let _main = engine.gpu.enter_main()?;
11643 let stream = engine.stream();
11644 let (a, _g) = workspace.input[rank].device_ptr(&stream);
11645 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
11646 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
11647 workspace.raw_input.push(a as u64);
11648 workspace.raw_sel.push(b as u64);
11649 workspace.raw_route_w.push(c as u64);
11650 }
11651 Ok(())
11652 }
11653
11654 pub fn run_tensor_parallel_routes_nvfp4(
11658 &self,
11659 experts: &ResidentNvfp4TensorParallel,
11660 input: &[f32],
11661 tokens: usize,
11662 selected: &[usize],
11663 route_weights: &[f32],
11664 experts_per_token: usize,
11665 activation_limit: Option<f32>,
11666 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11667 validate_activations(input, tokens, experts.input_width)?;
11668 let pairs = tokens
11669 .checked_mul(experts_per_token)
11670 .ok_or("NVFP4 TP route count overflow")?;
11671 if selected.len() != pairs || route_weights.len() != pairs {
11672 return Err(format!(
11673 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
11674 {experts_per_token} ({pairs})",
11675 selected.len(),
11676 route_weights.len(),
11677 )
11678 .into());
11679 }
11680 if !route_weights.iter().all(|weight| weight.is_finite()) {
11681 return Err("NVFP4 TP route weights contain a non-finite value".into());
11682 }
11683
11684 let mut output = vec![0.0f32; tokens * experts.input_width];
11685 for token in 0..tokens {
11686 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11687 for slot in 0..experts_per_token {
11688 let pair = token * experts_per_token + slot;
11689 let expert = selected[pair];
11690 if expert >= experts.expert_count {
11691 return Err(format!(
11692 "NVFP4 TP selected expert {expert} outside 0..{}",
11693 experts.expert_count
11694 )
11695 .into());
11696 }
11697 let gate = if experts.ep2 {
11703 self.run_full_bank_expert_nvfp4(
11704 &experts.gate,
11705 &experts.macros_gate,
11706 expert,
11707 input_row,
11708 )?
11709 } else {
11710 self.run_column_bank_expert_nvfp4(
11711 &experts.gate,
11712 &experts.macros_gate,
11713 expert,
11714 input_row,
11715 )?
11716 };
11717 let up = if experts.ep2 {
11718 self.run_full_bank_expert_nvfp4(
11719 &experts.up,
11720 &experts.macros_up,
11721 expert,
11722 input_row,
11723 )?
11724 } else {
11725 self.run_column_bank_expert_nvfp4(
11726 &experts.up,
11727 &experts.macros_up,
11728 expert,
11729 input_row,
11730 )?
11731 };
11732 let activated: Vec<f32> = gate
11733 .iter()
11734 .zip(&up)
11735 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11736 .collect();
11737 debug_assert_eq!(activated.len(), experts.expert_width);
11738 let down = if experts.ep2 {
11739 self.run_full_down_expert_nvfp4(
11740 &experts.down,
11741 &experts.macros_down,
11742 expert,
11743 &activated,
11744 )?
11745 } else {
11746 self.run_row_bank_expert_nvfp4(
11747 &experts.down,
11748 &experts.macros_down,
11749 expert,
11750 &activated,
11751 )?
11752 };
11753 let weight = route_weights[pair];
11754 for (sum, value) in output
11755 [token * experts.input_width..(token + 1) * experts.input_width]
11756 .iter_mut()
11757 .zip(down)
11758 {
11759 *sum += weight * value;
11760 }
11761 }
11762 }
11763 Ok(output)
11764 }
11765}
11766
11767#[cfg(test)]
11768mod tests {
11769 use super::*;
11770
11771 #[test]
11772 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
11773 let limit = Some(7.0);
11774 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
11775 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
11776 assert!(
11777 step_expert_activation_host(-20.0, 9.0, limit).abs()
11778 < step_expert_activation_host(-20.0, 9.0, None).abs()
11779 );
11780 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
11781 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
11782 assert!(validate_step_expert_activation_limit(limit).is_ok());
11783 }
11784
11785 #[test]
11786 fn moe_residual_host_preserves_official_add_order() {
11787 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
11788 assert_eq!(output, [0.0]);
11789 assert_eq!(
11790 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
11791 "MoE residual lengths residual=1 routed=2 shared=1"
11792 );
11793 }
11794
11795 #[test]
11796 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
11797 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
11798 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
11799 assert_eq!(owners.len(), 4);
11800 for (rank, owner) in owners.iter().enumerate() {
11801 assert_eq!(owner.rank, rank);
11802 assert_eq!(owner.selected, vec![0, 36]);
11803 assert_eq!(owner.token_rows, vec![0, 0]);
11804 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
11805 }
11806 }
11807
11808 #[test]
11809 fn expert_owner_routes_validate_geometry_and_selected_experts() {
11810 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
11811 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
11812 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
11813 assert!(error.contains("outside 0..288"));
11814 }
11815
11816 #[test]
11817 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
11818 let selected = [
11819 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
11820 ];
11821 assert_eq!(
11822 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
11823 16
11824 );
11825 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
11826 assert_eq!(
11827 owners
11828 .iter()
11829 .map(|owner| owner.selected.len())
11830 .collect::<Vec<_>>(),
11831 vec![2, 4, 6, 4]
11832 );
11833 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
11834 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
11835 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
11836 }
11837
11838 #[test]
11839 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
11840 let owner0 = [0usize, 3];
11841 let owner1 = [1usize, 2];
11842 let owners = [owner0.as_slice(), owner1.as_slice()];
11843 assert_eq!(
11844 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
11845 .unwrap(),
11846 WeightedRouteCombineShape {
11847 pairs: 4,
11848 max_pairs: 12,
11849 }
11850 );
11851 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
11852 assert!(
11853 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
11854 .is_err()
11855 );
11856 assert!(
11857 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
11858 .is_err()
11859 );
11860 assert!(
11861 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
11862 .is_err()
11863 );
11864 }
11865
11866 #[test]
11867 fn native_p2p_door_is_strict_and_default_off() {
11868 assert!(!parse_step_tp_native_p2p(None).unwrap());
11869 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
11870 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
11871 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
11872 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
11873 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
11874 }
11875
11876 #[test]
11877 fn bulk_p2p_door_is_strict_and_default_off() {
11878 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
11879 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
11880 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
11881 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
11882 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
11883 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
11884 }
11885
11886 #[test]
11887 fn ep_device_arithmetic_door_is_strict_and_default_off() {
11888 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
11889 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
11890 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
11891 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
11892 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
11893 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
11894 }
11895
11896 #[test]
11897 fn f32_mirror_door_is_strict_and_default_off() {
11898 assert!(!parse_step_tp_f32_mirror(None).unwrap());
11899 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
11900 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
11901 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
11902 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
11903 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
11904 }
11905
11906 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
11907 let codes = (0..out_features * in_features)
11908 .map(|index| (index % 251) as u8)
11909 .collect();
11910 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
11911 .map(|index| index as f32 + 1.0)
11912 .collect();
11913 (codes, scales)
11914 }
11915
11916 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
11917 (0..out_features * in_features)
11918 .flat_map(|value| (value as u16).to_le_bytes())
11919 .collect()
11920 }
11921
11922 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
11923 bytes
11924 .chunks_exact(2)
11925 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
11926 .collect()
11927 }
11928
11929 #[test]
11930 fn bf16_matrix_rejects_wrong_byte_count() {
11931 let bytes = vec![0u8; 4 * 4 * 2 - 1];
11932 let matrix = Bf16Matrix {
11933 bytes: &bytes,
11934 out_features: 4,
11935 in_features: 4,
11936 };
11937 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
11938 }
11939
11940 #[test]
11941 fn replicated_device_rows_require_exact_rank_local_shapes() {
11942 assert_eq!(
11943 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
11944 12_288
11945 );
11946 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
11947 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
11948 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
11949 assert!(
11950 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
11951 );
11952 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
11953 }
11954
11955 #[test]
11956 fn replicated_device_row_refresh_requires_exact_root_source() {
11957 assert_eq!(
11958 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
11959 12_288
11960 );
11961 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
11962 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
11963 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
11964 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
11965 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
11966 }
11967
11968 #[test]
11969 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
11970 for tp in [1, 2, 4, 8] {
11971 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
11972 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
11973 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
11974 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
11975 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
11976 }
11977 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
11978 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
11979 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
11980 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
11981 }
11982
11983 #[test]
11984 fn cache_rows_split_by_token_then_rank() {
11985 let rows = (0u8..24).collect::<Vec<_>>();
11986 assert_eq!(
11987 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
11988 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
11989 );
11990 assert_eq!(
11991 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
11992 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
11993 );
11994 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
11995 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
11996 }
11997
11998 #[test]
11999 fn bf16_column_shard_preserves_contiguous_output_rows() {
12000 let bytes = bf16_matrix_bytes(4, 4);
12001 let matrix = Bf16Matrix {
12002 bytes: &bytes,
12003 out_features: 4,
12004 in_features: 4,
12005 };
12006 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
12007 assert_eq!(shard.out_features, 2);
12008 assert_eq!(shard.in_features, 4);
12009 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
12010 }
12011
12012 #[test]
12013 fn bf16_row_shard_preserves_each_input_column_window() {
12014 let bytes = bf16_matrix_bytes(3, 4);
12015 let matrix = Bf16Matrix {
12016 bytes: &bytes,
12017 out_features: 3,
12018 in_features: 4,
12019 };
12020 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
12021 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
12022 }
12023
12024 #[test]
12025 fn bf16_row_block_preserves_global_column_order() {
12026 let bytes = bf16_matrix_bytes(3, 8);
12027 let matrix = Bf16Matrix {
12028 bytes: &bytes,
12029 out_features: 3,
12030 in_features: 8,
12031 };
12032 let block = bf16_row_block(matrix, 2, 3).unwrap();
12033 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
12034 }
12035
12036 #[test]
12037 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
12038 let (codes, scales) = matrix(1280, 4096);
12039 let matrix = E4m3BlockMatrix {
12040 codes: &codes,
12041 scales: &scales,
12042 out_features: 1280,
12043 in_features: 4096,
12044 };
12045 let shard = column_shard(matrix, 2, 1).unwrap();
12046 assert_eq!(shard.out_features, 640);
12047 assert_eq!(shard.codes, &codes[640 * 4096..]);
12048 assert_eq!(shard.scales, &scales[5 * 32..]);
12049 }
12050
12051 #[test]
12052 fn row_shard_preserves_each_weight_and_scale_column_window() {
12053 let (codes, scales) = matrix(4096, 1280);
12054 let matrix = E4m3BlockMatrix {
12055 codes: &codes,
12056 scales: &scales,
12057 out_features: 4096,
12058 in_features: 1280,
12059 };
12060 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
12061 assert_eq!(shard_codes.len(), 4096 * 640);
12062 assert_eq!(&shard_codes[..640], &codes[640..1280]);
12063 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
12064 assert_eq!(shard_scales.len(), 32 * 5);
12065 assert_eq!(&shard_scales[..5], &scales[5..10]);
12066 assert_eq!(&shard_scales[5..10], &scales[15..20]);
12067 }
12068
12069 #[test]
12070 fn activation_shards_keep_token_rows_separate() {
12071 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
12072 assert_eq!(
12073 activation_shard(&activations, 2, 8, 2, 1),
12074 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
12075 );
12076 }
12077
12078 #[test]
12079 fn expert_bank_selects_expert_major_code_and_scale_planes() {
12080 let expert_count = 2;
12081 let out_features = 128;
12082 let in_features = 128;
12083 let code_stride = out_features * in_features;
12084 let codes: Vec<u8> = (0..expert_count * code_stride)
12085 .map(|index| (index % 251) as u8)
12086 .collect();
12087 let scales = vec![1.0f32, 2.0];
12088 let bank = E4m3ExpertBank {
12089 codes: &codes,
12090 scales: &scales,
12091 expert_count,
12092 out_features,
12093 in_features,
12094 };
12095 bank.validate().unwrap();
12096 let expert = bank.expert(1).unwrap();
12097 assert_eq!(expert.codes, &codes[code_stride..]);
12098 assert_eq!(expert.scales, &[2.0]);
12099 }
12100
12101 #[test]
12102 fn expert_bank_rejects_non_positive_scale() {
12103 let codes = vec![0u8; 128 * 128];
12104 let scales = vec![0.0f32];
12105 let bank = E4m3ExpertBank {
12106 codes: &codes,
12107 scales: &scales,
12108 expert_count: 1,
12109 out_features: 128,
12110 in_features: 128,
12111 };
12112 assert!(bank.validate().unwrap_err().contains("non-positive"));
12113 }
12114
12115 #[test]
12116 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
12117 let expert_count = 2;
12118 let out_features = 256;
12119 let in_features = 128;
12120 let code_stride = out_features * in_features;
12121 let scale_stride = 2;
12122 let codes = (0..expert_count * code_stride)
12123 .map(|index| (index % 251) as u8)
12124 .collect::<Vec<_>>();
12125 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12126 let bank = E4m3ExpertBank {
12127 codes: &codes,
12128 scales: &scales,
12129 expert_count,
12130 out_features,
12131 in_features,
12132 };
12133
12134 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
12135 assert_eq!(rank.out_features, 128);
12136 assert_eq!(rank.in_features, 128);
12137 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12138 assert_eq!(rank.scales, vec![11.0, 21.0]);
12139 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
12140 assert_eq!(
12141 &rank.codes[128 * 128..],
12142 &codes[code_stride + 128 * 128..2 * code_stride]
12143 );
12144 assert_eq!(scale_stride, scales.len() / expert_count);
12145 }
12146
12147 #[test]
12148 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
12149 let expert_count = 2;
12150 let out_features = 128;
12151 let in_features = 256;
12152 let code_stride = out_features * in_features;
12153 let codes = (0..expert_count * code_stride)
12154 .map(|index| (index % 251) as u8)
12155 .collect::<Vec<_>>();
12156 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12157 let bank = E4m3ExpertBank {
12158 codes: &codes,
12159 scales: &scales,
12160 expert_count,
12161 out_features,
12162 in_features,
12163 };
12164
12165 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12166 assert_eq!(rank.out_features, 128);
12167 assert_eq!(rank.in_features, 128);
12168 assert_eq!(rank.k_blocks, Some(1));
12169 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12170 assert_eq!(rank.scales, vec![11.0, 21.0]);
12171 assert_eq!(&rank.codes[..128], &codes[128..256]);
12172 assert_eq!(
12173 &rank.codes[128 * 128..128 * 128 + 128],
12174 &codes[code_stride + 128..code_stride + 256]
12175 );
12176 }
12177
12178 #[test]
12179 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
12180 let expert_count = 2;
12181 let out_features = 256;
12182 let in_features = 512;
12183 let code_stride = out_features * in_features;
12184 let mut codes = vec![0u8; expert_count * code_stride];
12185 for expert in 0..expert_count {
12186 for row in 0..out_features {
12187 for block in 0..4 {
12188 let value = (expert * 80 + block * 16 + row % 16) as u8;
12189 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
12190 codes[start..start + FP8_BLOCK].fill(value);
12191 }
12192 }
12193 }
12194 let scales = vec![
12195 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,
12196 112.0, 113.0, 114.0,
12197 ];
12198 let bank = E4m3ExpertBank {
12199 codes: &codes,
12200 scales: &scales,
12201 expert_count,
12202 out_features,
12203 in_features,
12204 };
12205
12206 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12207 assert_eq!(rank.out_features, out_features);
12208 assert_eq!(rank.in_features, 256);
12209 assert_eq!(rank.k_blocks, Some(2));
12210 assert_eq!(rank.code_stride, out_features * 256);
12211 assert_eq!(rank.scale_stride, 4);
12212 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
12213 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
12214
12215 let block_stride = out_features * FP8_BLOCK;
12216 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
12217 assert!(
12218 rank.codes[block_stride..block_stride + FP8_BLOCK]
12219 .iter()
12220 .all(|&code| code == 48)
12221 );
12222 assert!(
12223 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
12224 .iter()
12225 .all(|&code| code == 112)
12226 );
12227 assert!(
12228 rank.codes
12229 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
12230 .iter()
12231 .all(|&code| code == 128)
12232 );
12233 }
12234
12235 #[test]
12236 fn step_ep_layer_specs_are_literal_and_fail_closed() {
12237 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
12238 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
12239 assert_eq!(
12240 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
12241 vec![StepEpLayerSpec {
12242 layer: 24,
12243 devices: vec![1, 2],
12244 }]
12245 );
12246 assert_eq!(
12247 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
12248 vec![
12249 StepEpLayerSpec {
12250 layer: 24,
12251 devices: vec![1, 2],
12252 },
12253 StepEpLayerSpec {
12254 layer: 25,
12255 devices: vec![1, 2],
12256 },
12257 StepEpLayerSpec {
12258 layer: 31,
12259 devices: vec![0, 2],
12260 },
12261 ]
12262 );
12263 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
12264 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
12265 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
12266 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
12267 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
12268 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12269 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
12270 }
12271
12272 #[test]
12273 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
12274 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
12275 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
12276 assert_eq!(
12277 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
12278 vec![
12279 StepTpLayerSpec {
12280 layer: 24,
12281 devices: vec![1, 2],
12282 },
12283 StepTpLayerSpec {
12284 layer: 25,
12285 devices: vec![1, 2],
12286 },
12287 ]
12288 );
12289 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
12290 assert!(error.contains("MEMRA_STEP_TP"));
12291 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
12292 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12293
12294 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
12295 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
12296 assert_eq!(all.first().unwrap().layer, 0);
12297 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
12298 let devices = (0..8).collect::<Vec<_>>();
12299 assert!(all.iter().all(|spec| spec.devices == devices));
12300 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
12301 }
12302}
12303
12304struct TokenGraphChild {
12316 graph: cudarc::driver::CudaGraph,
12317 node: cudarc::driver::sys::CUgraphNode,
12318 ctx: cudarc::driver::sys::CUcontext,
12319}
12320
12321struct TokenGraphFaSite {
12325 ctx: cudarc::driver::sys::CUcontext,
12326 memset_o: cudarc::driver::sys::CUgraphNode,
12327 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
12328 fa: cudarc::driver::sys::CUgraphNode,
12329 combine: cudarc::driver::sys::CUgraphNode,
12330 window: usize,
12331 n_head: usize,
12332 n_head_kv: usize,
12333 head_dim: usize,
12334}
12335
12336pub struct TokenGraphBuilder {
12337 parent: cudarc::driver::sys::CUgraph,
12338 children: Vec<TokenGraphChild>,
12339 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
12342 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
12345 group: Option<(
12348 u32,
12349 Vec<cudarc::driver::sys::CUgraphNode>,
12350 Vec<cudarc::driver::sys::CUgraphNode>,
12351 )>,
12352}
12353
12354unsafe impl Send for TokenGraphBuilder {}
12356
12357impl TokenGraphBuilder {
12358 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
12359 use cudarc::driver::sys;
12360 let mut parent: sys::CUgraph = std::ptr::null_mut();
12361 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
12362 if r != sys::CUresult::CUDA_SUCCESS {
12363 return Err(format!("token graph create: {r:?}").into());
12364 }
12365 Ok(Self {
12366 parent,
12367 children: Vec::new(),
12368 frontier: Vec::new(),
12369 pending_detached: Vec::new(),
12370 group: None,
12371 })
12372 }
12373
12374 fn push_child(
12375 &mut self,
12376 graph: cudarc::driver::CudaGraph,
12377 parallel_group: Option<u32>,
12378 detached: bool,
12379 absorb: bool,
12380 ctx: cudarc::driver::sys::CUcontext,
12381 ) -> Result<(), Box<dyn std::error::Error>> {
12382 use cudarc::driver::sys;
12383 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
12387 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
12388 (state, Some(group)) => {
12389 if let Some((_, _, members)) = state.take() {
12391 self.frontier = members;
12392 }
12393 let base = self.frontier.clone();
12394 *state = Some((group, base.clone(), Vec::new()));
12395 base
12396 }
12397 (state, None) if detached => match state.as_ref() {
12398 Some((_, base, _)) => base.clone(),
12399 None => self.frontier.clone(),
12400 },
12401 (state, None) => {
12402 if let Some((_, _, members)) = state.take() {
12403 self.frontier = members;
12404 }
12405 let mut deps = self.frontier.clone();
12406 if absorb {
12407 deps.append(&mut self.pending_detached);
12408 }
12409 deps
12410 }
12411 };
12412 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12413 let r = unsafe {
12414 sys::cuGraphAddChildGraphNode(
12415 &mut node,
12416 self.parent,
12417 if deps.is_empty() {
12418 std::ptr::null()
12419 } else {
12420 deps.as_ptr()
12421 },
12422 deps.len(),
12423 graph.cu_graph(),
12424 )
12425 };
12426 if r != sys::CUresult::CUDA_SUCCESS {
12427 return Err(format!("token graph child: {r:?}").into());
12428 }
12429 match (&mut self.group, parallel_group, detached) {
12430 (_, None, true) => self.pending_detached.push(node),
12431 (Some((_, _, members)), Some(_), _) => members.push(node),
12432 _ => self.frontier = vec![node],
12433 }
12434 self.children.push(TokenGraphChild { graph, node, ctx });
12435 Ok(())
12436 }
12437
12438 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
12439 use cudarc::driver::sys;
12440 if let Some((_, _, members)) = self.group.take() {
12441 self.frontier = members;
12442 }
12443 let mut fa_sites = Vec::new();
12446 for child in &self.children {
12447 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
12448 fa_sites.push(site);
12449 }
12450 }
12451 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12452 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
12453 if r != sys::CUresult::CUDA_SUCCESS {
12454 return Err(format!("token graph instantiate: {r:?}").into());
12455 }
12456 Ok(TokenGraph {
12457 exec,
12458 parent: self.parent,
12459 _children: self.children,
12460 fa_sites,
12461 })
12462 }
12463}
12464
12465fn discover_fa_site(
12468 child_node: cudarc::driver::sys::CUgraphNode,
12469 ctx: cudarc::driver::sys::CUcontext,
12470) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
12471 use cudarc::driver::sys;
12472 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12473 if r == sys::CUresult::CUDA_SUCCESS {
12474 Ok(())
12475 } else {
12476 Err(format!("{what}: {r:?}").into())
12477 }
12478 }
12479 let mut graph: sys::CUgraph = std::ptr::null_mut();
12480 unsafe {
12481 cu_try(
12482 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
12483 "fa-site child GetGraph",
12484 )?;
12485 }
12486 let mut count: usize = 0;
12487 unsafe {
12488 cu_try(
12489 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
12490 "fa-site GetNodes(count)",
12491 )?;
12492 }
12493 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
12494 unsafe {
12495 cu_try(
12496 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
12497 "fa-site GetNodes",
12498 )?;
12499 }
12500 nodes.truncate(count);
12501 let node_type =
12502 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
12503 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
12504 unsafe {
12505 cu_try(
12506 sys::cuGraphNodeGetType(node, &mut ty),
12507 "fa-site NodeGetType",
12508 )?;
12509 }
12510 Ok(ty)
12511 };
12512 let memsets: Vec<sys::CUgraphNode> = {
12513 let mut v = Vec::new();
12514 for &node in &nodes {
12515 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
12516 v.push(node);
12517 }
12518 }
12519 v
12520 };
12521 if memsets.len() != 3 {
12522 return Ok(None);
12523 }
12524 let dependents =
12526 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
12527 let mut n: usize = 0;
12528 unsafe {
12529 cu_try(
12530 sys::cuGraphNodeGetDependentNodes_v2(
12531 node,
12532 std::ptr::null_mut(),
12533 std::ptr::null_mut(),
12534 &mut n,
12535 ),
12536 "fa-site GetDependentNodes(count)",
12537 )?;
12538 }
12539 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
12540 unsafe {
12541 cu_try(
12542 sys::cuGraphNodeGetDependentNodes_v2(
12543 node,
12544 v.as_mut_ptr(),
12545 std::ptr::null_mut(),
12546 &mut n,
12547 ),
12548 "fa-site GetDependentNodes",
12549 )?;
12550 }
12551 v.truncate(n);
12552 Ok(v)
12553 };
12554 let mut fa: Option<sys::CUgraphNode> = None;
12557 let mut last_memset: Option<sys::CUgraphNode> = None;
12558 for &ms in &memsets {
12559 for dep in dependents(ms)? {
12560 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12561 fa = Some(dep);
12562 last_memset = Some(ms);
12563 }
12564 }
12565 }
12566 let (Some(fa), Some(_last)) = (fa, last_memset) else {
12567 return Ok(None);
12568 };
12569 let mut combine: Option<sys::CUgraphNode> = None;
12570 for dep in dependents(fa)? {
12571 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12572 combine = Some(dep);
12573 }
12574 }
12575 let Some(combine) = combine else {
12576 return Ok(None);
12577 };
12578 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12581 unsafe {
12582 cu_try(
12583 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
12584 "fa-site KernelNodeGetParams",
12585 )?;
12586 }
12587 let arg_i32 =
12588 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
12589 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
12590 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
12592 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12593 unsafe {
12594 cu_try(
12595 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12596 "fa-site MemsetNodeGetParams",
12597 )?;
12598 }
12599 Ok(mp.width)
12600 };
12601 let mut widest = memsets[0];
12602 for &ms in &memsets[1..] {
12603 if width_of(ms)? > width_of(widest)? {
12604 widest = ms;
12605 }
12606 }
12607 let memset_m: Vec<sys::CUgraphNode> =
12608 memsets.iter().copied().filter(|&m| m != widest).collect();
12609 Ok(Some(TokenGraphFaSite {
12610 ctx,
12611 memset_o: widest,
12612 memset_m: [memset_m[0], memset_m[1]],
12613 fa,
12614 combine,
12615 window: win as usize,
12616 n_head: nh as usize,
12617 n_head_kv: nhkv as usize,
12618 head_dim: hd as usize,
12619 }))
12620}
12621
12622pub struct TokenGraph {
12623 exec: cudarc::driver::sys::CUgraphExec,
12624 parent: cudarc::driver::sys::CUgraph,
12625 _children: Vec<TokenGraphChild>,
12626 fa_sites: Vec<TokenGraphFaSite>,
12627}
12628
12629unsafe impl Send for TokenGraph {}
12630
12631impl TokenGraph {
12632 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
12637 use cudarc::driver::sys;
12638 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12639 if r == sys::CUresult::CUDA_SUCCESS {
12640 Ok(())
12641 } else {
12642 Err(format!("{what}: {r:?}").into())
12643 }
12644 }
12645 for site in &self.fa_sites {
12646 let layer_bucket = if site.window > 0 {
12647 bucket.min(site.window)
12648 } else {
12649 bucket
12650 };
12651 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
12652 let nsp = layer_bucket.div_ceil(sp).max(1);
12653 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12655 unsafe {
12656 cu_try(
12657 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
12658 "retarget fa GetParams",
12659 )?;
12660 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
12661 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
12662 params.gridDimY = nsp as u32;
12663 cu_try(
12664 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
12665 "retarget fa SetParams",
12666 )?;
12667 }
12668 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12670 unsafe {
12671 cu_try(
12672 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
12673 "retarget combine GetParams",
12674 )?;
12675 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
12676 cu_try(
12677 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
12678 "retarget combine SetParams",
12679 )?;
12680 }
12681 let set_width =
12683 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
12684 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12685 unsafe {
12686 cu_try(
12687 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12688 "retarget memset GetParams",
12689 )?;
12690 }
12691 mp.width = width;
12692 unsafe {
12693 cu_try(
12694 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
12695 "retarget memset SetParams",
12696 )?;
12697 }
12698 Ok(())
12699 };
12700 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
12701 set_width(site.memset_m[0], site.n_head * nsp)?;
12702 set_width(site.memset_m[1], site.n_head * nsp)?;
12703 }
12704 Ok(())
12705 }
12706
12707 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
12708 use cudarc::driver::sys;
12709 let _main = e.gpu.enter_main()?;
12710 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
12711 if r != sys::CUresult::CUDA_SUCCESS {
12712 return Err(format!("token graph launch: {r:?}").into());
12713 }
12714 Ok(())
12715 }
12716}
12717
12718impl Drop for TokenGraph {
12719 fn drop(&mut self) {
12720 unsafe {
12721 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
12722 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
12723 }
12724 }
12725}
12726
12727std::thread_local! {
12728 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
12729 const { std::cell::RefCell::new(None) };
12730}
12731
12732pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
12734 let builder = TokenGraphBuilder::new()?;
12735 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
12736 Ok(())
12737}
12738
12739pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
12741 let builder = TOKEN_GRAPH_BUILDER
12742 .with(|cell| cell.borrow_mut().take())
12743 .ok_or("token graph build was not begun")?;
12744 builder.finish()
12745}
12746
12747pub fn token_graph_building() -> bool {
12749 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
12750}
12751
12752pub fn graph_section<F>(
12757 engine: &Engine,
12758 parallel_group: Option<u32>,
12759 f: F,
12760) -> Result<(), Box<dyn std::error::Error>>
12761where
12762 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12763{
12764 graph_section_opts(engine, parallel_group, false, false, f)
12765}
12766
12767pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
12769where
12770 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12771{
12772 graph_section_opts(engine, None, false, true, f)
12773}
12774
12775pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
12778where
12779 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12780{
12781 graph_section_opts(engine, None, true, false, f)
12782}
12783
12784pub fn graph_section_opts<F>(
12785 engine: &Engine,
12786 parallel_group: Option<u32>,
12787 detached: bool,
12788 absorb: bool,
12789 f: F,
12790) -> Result<(), Box<dyn std::error::Error>>
12791where
12792 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12793{
12794 let building = token_graph_building();
12795 if !building {
12796 let mut f = f;
12797 return f();
12798 }
12799 let (child, ctx) = {
12800 let _main = engine.gpu.enter_main()?;
12801 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
12802 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
12803 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
12804 return Err(format!("graph section ctx query: {r:?}").into());
12805 }
12806 let mut f = f;
12807 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
12810 (child, ctx)
12811 };
12812 TOKEN_GRAPH_BUILDER.with(|cell| {
12813 cell.borrow_mut()
12814 .as_mut()
12815 .expect("builder checked above")
12816 .push_child(child, parallel_group, detached, absorb, ctx)
12817 })
12818}