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 pub(crate) fn decode_v2_stash_gated(
5591 &self,
5592 ws: &mut StepTpDecodeV2Ws,
5593 e: &Engine,
5594 col: usize,
5595 ) -> Result<(), Box<dyn std::error::Error>> {
5596 let ranks = self.ranks.len();
5597 if col >= 8 {
5598 return Err("decode_v2_stash_gated column out of range".into());
5599 }
5600 let lq = ws.local_q_dim;
5601 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
5602 ws.tcol_gated.clear();
5603 ws.tcol_opart.clear();
5604 for engine in &self.ranks {
5605 let _m = engine.gpu.enter_main()?;
5606 ws.tcol_gated.push(engine.uninit(8 * lq)?);
5607 ws.tcol_opart.push(engine.uninit(8 * ws.o_out)?);
5608 }
5609 let root = &self.ranks[0];
5610 let _m = root.gpu.enter_main()?;
5611 ws.tcol_opeer = Some(root.uninit(8 * ws.o_out)?);
5612 ws.tcol_omix = Some(root.uninit(8 * ws.o_out)?);
5613 ws.tcol_ocap = 8;
5614 }
5615 for rank in 0..ranks {
5616 let engine = &self.ranks[rank];
5617 let _main = engine.gpu.enter_main()?;
5618 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
5619 engine
5620 .stream()
5621 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
5622 ws.ev_rank[rank].record(&engine.stream())?;
5626 }
5627 {
5628 let _main = e.gpu.enter_main()?;
5629 for ev in ws.ev_rank.iter() {
5630 e.stream().wait(ev)?;
5631 }
5632 }
5633 Ok(())
5634 }
5635
5636 pub(crate) fn decode_v2_oproj_tcol(
5642 &self,
5643 ws_index: usize,
5644 e: &Engine,
5645 o_m: &ResidentStepBf16RowParallel,
5646 t: usize,
5647 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5648 let ranks = self.ranks.len();
5649 let mut guard = self
5650 .decode_v2
5651 .lock()
5652 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5653 let ws = guard
5654 .get_mut(ws_index)
5655 .ok_or("step TP decode v2 workspace index out of range")?;
5656 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 8 || ws.tcol_ocap < t {
5657 return Err("decode_v2_oproj_tcol geometry".into());
5658 }
5659 for rank in 0..ranks {
5660 let engine = &self.ranks[rank];
5661 let _main = engine.gpu.enter_main()?;
5662 let mut weights = Vec::with_capacity(4);
5663 for block in 0..4 {
5664 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
5665 return Err("tcol o_proj requires bf16-resident O blocks".into());
5666 };
5667 weights.push(weight);
5668 }
5669 {
5670 let StepTpDecodeV2Ws {
5671 tcol_gated,
5672 tcol_opart,
5673 local_q_dim,
5674 o_block_cols,
5675 o_out,
5676 ..
5677 } = &mut *ws;
5678 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5681 let refk = *REFK
5682 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
5683 if refk {
5684 let lq = *local_q_dim;
5685 let mut xr = engine.uninit(lq)?;
5686 let mut yr = engine.uninit(*o_out)?;
5687 for c in 0..t {
5688 {
5689 let mut dst = xr.slice_mut(0..lq);
5690 engine.stream().memcpy_dtod(
5691 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
5692 &mut dst,
5693 )?;
5694 }
5695 engine.matvec_bf16_b4_into(
5696 [weights[0], weights[1], weights[2], weights[3]],
5697 &xr,
5698 &mut yr,
5699 *o_block_cols,
5700 *o_out,
5701 )?;
5702 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
5703 engine
5704 .stream()
5705 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
5706 }
5707 } else {
5708 engine.matvec_bf16_b4_tcol_into(
5709 [weights[0], weights[1], weights[2], weights[3]],
5710 &tcol_gated[rank],
5711 &mut tcol_opart[rank],
5712 *o_block_cols,
5713 *o_out,
5714 t,
5715 )?;
5716 }
5717 }
5718 if rank != 0 {
5719 ws.ev_rank[rank].record(&engine.stream())?;
5720 }
5721 }
5722 let root = &self.ranks[0];
5723 {
5724 let _main = root.gpu.enter_main()?;
5725 for ev in ws.ev_rank.iter().skip(1) {
5726 root.stream().wait(ev)?;
5727 }
5728 {
5729 let StepTpDecodeV2Ws {
5730 tcol_opart,
5731 tcol_opeer,
5732 tcol_omix,
5733 o_out,
5734 ..
5735 } = &mut *ws;
5736 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
5737 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
5738 {
5739 let mut dst = opeer.slice_mut(0..t * *o_out);
5740 root.stream()
5741 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
5742 }
5743 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
5746 }
5747 ws.ev_oproj.record(&root.stream())?;
5748 }
5749 let _main = e.gpu.enter_main()?;
5750 e.stream().wait(&ws.ev_oproj)?;
5751 let mut out = e.uninit(t * ws.o_out)?;
5752 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
5753 e.stream().memcpy_dtod(
5754 &omix.slice(0..t * ws.o_out),
5755 &mut out.slice_mut(0..t * ws.o_out),
5756 )?;
5757 Ok(out)
5758 }
5759
5760 pub(crate) fn decode_v2_input_qkv(
5761 &self,
5762 ws: &mut StepTpDecodeV2Ws,
5763 e: &Engine,
5764 h: &CudaSlice<f32>,
5765 pos_d: &CudaSlice<i32>,
5766 gate_raw: Option<&CudaSlice<f32>>,
5767 gate_shards: Option<StepTpGateShards<'_>>,
5768 decode_input: &mut ResidentReplicatedDeviceRows,
5769 q_m: &ResidentBf16ColumnParallel,
5770 k_m: &ResidentBf16ColumnParallel,
5771 v_m: &ResidentBf16ColumnParallel,
5772 q_norm: &[CudaSlice<f32>],
5773 k_norm: &[CudaSlice<f32>],
5774 head_dim: usize,
5775 n_rot: usize,
5776 rope_base: f32,
5777 rope_freqs: &[Option<&CudaSlice<f32>>],
5778 rms_eps: f32,
5779 defer_norm_rope: bool,
5780 tcol_col: Option<usize>,
5781 ) -> Result<(), Box<dyn std::error::Error>> {
5782 let ranks = self.ranks.len();
5783 validate_replicated_device_rows(&self.ranks, decode_input)?;
5784 if decode_input.tokens != 1
5785 || decode_input.width != q_m.in_features
5786 || pos_d.len() != 1
5787 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
5788 || gate_raw.is_none() != gate_shards.is_some()
5789 || gate_shards.as_ref().is_some_and(|shards| match shards {
5790 StepTpGateShards::F32(shards) => shards.len() != ranks,
5791 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
5792 })
5793 || q_norm.len() != ranks
5794 || k_norm.len() != ranks
5795 || rope_freqs.len() != ranks
5796 || e.ctx().ordinal() != ws.e_device
5797 {
5798 return Err("step TP decode v2 input geometry mismatch".into());
5799 }
5800
5801 let qkv_fused = step_tp_qkv_fused_enabled()?;
5802 if gate_shards.is_some() && !qkv_fused {
5803 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
5804 }
5805 let values = decode_input.width;
5806 if h.len() != values {
5807 return Err(format!(
5808 "step TP decode v2 hidden width {} != replicated width {values}",
5809 h.len()
5810 )
5811 .into());
5812 }
5813
5814 if qkv_fused {
5815 if ws.h_stage.is_none() {
5819 use cudarc::driver::DevicePtr;
5820 let _main = e.gpu.enter_main()?;
5821 let h_stage = e.uninit(values)?;
5822 let pos_stage = e.htod_i32(&[0])?;
5823 {
5824 let stream = e.stream();
5825 let (hp, _g0) = h_stage.device_ptr(&stream);
5826 let (pp, _g1) = pos_stage.device_ptr(&stream);
5827 ws.raw_h_stage = hp as u64;
5828 ws.raw_pos_stage = pp as u64;
5829 }
5830 ws.h_stage = Some(h_stage);
5831 ws.pos_stage = Some(pos_stage);
5832 for rank in 0..ranks {
5833 use cudarc::driver::DevicePtr;
5834 let engine = &self.ranks[rank];
5835 let _rmain = engine.gpu.enter_main()?;
5836 let attn_in = engine.uninit(values)?;
5837 let (dp, pp) = {
5838 let stream = engine.stream();
5839 let (dp, _g2) = attn_in.device_ptr(&stream);
5840 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
5841 (dp as u64, pp as u64)
5842 };
5843 ws.raw_attn_in.push(dp);
5844 ws.raw_pos.push(pp);
5845 ws.attn_in.push(attn_in);
5846 }
5847 {
5848 use cudarc::driver::DevicePtr;
5849 let root = &self.ranks[0];
5850 let _rmain = root.gpu.enter_main()?;
5851 let stream = root.stream();
5852 let (a, _g) = ws.peer_partial.device_ptr(&stream);
5853 let (b, _g) = ws.k_shadow.device_ptr(&stream);
5854 let (c, _g) = ws.v_shadow.device_ptr(&stream);
5855 ws.raw_peer_partial = a as u64;
5856 ws.raw_k_shadow = b as u64;
5857 ws.raw_v_shadow = c as u64;
5858 }
5859 {
5860 use cudarc::driver::DevicePtr;
5861 let rank1 = &self.ranks[1];
5862 let _rmain = rank1.gpu.enter_main()?;
5863 let stream = rank1.stream();
5864 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
5865 let (b, _g) = ws.k[1].device_ptr(&stream);
5866 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
5867 ws.raw_o_partial1 = a as u64;
5868 ws.raw_k1 = b as u64;
5869 ws.raw_v1 = c as u64;
5870 }
5871 }
5872 {
5873 let _main = e.gpu.enter_main()?;
5874 {
5875 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
5878 let mut dst = h_stage.slice_mut(0..values);
5879 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
5880 }
5881 {
5882 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
5883 let mut dst = pos_stage.slice_mut(0..1);
5884 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
5885 }
5886 ws.ev_entry.record(&e.stream())?;
5887 }
5888 for rank in 0..ranks {
5889 let engine = &self.ranks[rank];
5890 let _main = engine.gpu.enter_main()?;
5891 engine.stream().wait(&ws.ev_entry)?;
5892 }
5893 } else {
5894 {
5896 let _main = e.gpu.enter_main()?;
5897 if let Some(gate_raw) = gate_raw {
5898 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
5899 e.stream()
5900 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
5901 }
5902 ws.ev_entry.record(&e.stream())?;
5903 }
5904 {
5905 let root = &self.ranks[0];
5906 let _main = root.gpu.enter_main()?;
5907 root.stream().wait(&ws.ev_entry)?;
5908 let mut destination = decode_input.ranks[0].slice_mut(0..values);
5909 root.stream()
5910 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
5911 ws.ev_refresh.record(&root.stream())?;
5912 }
5913 for rank in 1..ranks {
5914 let engine = &self.ranks[rank];
5915 let _main = engine.gpu.enter_main()?;
5916 engine.stream().wait(&ws.ev_refresh)?;
5917 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
5918 let mut destination = peer_rows[0].slice_mut(0..values);
5919 engine
5920 .stream()
5921 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
5922 }
5923 }
5924 for rank in 0..ranks {
5925 self.decode_v2_input_qkv_rank(
5926 ws,
5927 pos_d,
5928 decode_input,
5929 q_m,
5930 k_m,
5931 v_m,
5932 q_norm,
5933 k_norm,
5934 head_dim,
5935 n_rot,
5936 rope_base,
5937 rope_freqs,
5938 rms_eps,
5939 gate_shards.as_ref(),
5940 qkv_fused,
5941 defer_norm_rope,
5942 rank,
5943 tcol_col,
5944 )?;
5945 }
5946 Ok(())
5947 }
5948
5949 #[allow(clippy::too_many_arguments)]
5952 pub(crate) fn decode_v2_input_qkv_rank(
5953 &self,
5954 ws: &mut StepTpDecodeV2Ws,
5955 pos_d: &CudaSlice<i32>,
5956 decode_input: &mut ResidentReplicatedDeviceRows,
5957 q_m: &ResidentBf16ColumnParallel,
5958 k_m: &ResidentBf16ColumnParallel,
5959 v_m: &ResidentBf16ColumnParallel,
5960 q_norm: &[CudaSlice<f32>],
5961 k_norm: &[CudaSlice<f32>],
5962 head_dim: usize,
5963 n_rot: usize,
5964 rope_base: f32,
5965 rope_freqs: &[Option<&CudaSlice<f32>>],
5966 rms_eps: f32,
5967 gate_shards: Option<&StepTpGateShards<'_>>,
5968 qkv_fused: bool,
5969 defer_norm_rope: bool,
5970 rank: usize,
5971 tcol_col: Option<usize>,
5972 ) -> Result<(), Box<dyn std::error::Error>> {
5973 let ranks = self.ranks.len();
5974 let local_heads = ws.local_q_dim / head_dim;
5975 let local_kv_heads = ws.local_kv_dim / head_dim;
5976 let engine = &self.ranks[rank];
5977 let _main = engine.gpu.enter_main()?;
5978 let ws_e_device = ws.e_device;
5979 if qkv_fused && tcol_col.is_some() {
5984 let c = tcol_col.expect("checked");
5985 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
5986 return Err("tcol select without precompute".into());
5987 }
5988 if engine.ctx().ordinal() != ws_e_device {
5992 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
5993 }
5994 let StepTpDecodeV2Ws {
5995 tcol_q,
5996 tcol_k,
5997 tcol_v,
5998 tcol_g,
5999 q_raw,
6000 k_raw,
6001 v_raw,
6002 gate,
6003 local_q_dim,
6004 local_kv_dim,
6005 heads,
6006 ..
6007 } = &mut *ws;
6008 let lg = *heads / ranks;
6009 let stream = engine.stream();
6010 {
6011 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6012 stream.memcpy_dtod(
6013 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6014 &mut dst,
6015 )?;
6016 }
6017 {
6018 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6019 stream.memcpy_dtod(
6020 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6021 &mut dst,
6022 )?;
6023 }
6024 {
6025 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6026 stream.memcpy_dtod(
6027 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6028 &mut dst,
6029 )?;
6030 }
6031 if lg > 0 {
6032 let mut dst = gate[rank].slice_mut(0..lg);
6033 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6034 }
6035 if !defer_norm_rope {
6036 } else {
6040 return Ok(());
6041 }
6042 }
6043 if qkv_fused {
6044 let same_dev = engine.ctx().ordinal() == ws.e_device;
6049 if !same_dev {
6050 raw_copy_bytes(
6051 ws.raw_attn_in[rank],
6052 ws.raw_h_stage,
6053 q_m.in_features * 4,
6054 engine,
6055 )?;
6056 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6057 }
6058 let StepTpDecodeV2Ws {
6059 q_raw,
6060 k_raw,
6061 v_raw,
6062 gate,
6063 gate_e,
6064 attn_in,
6065 h_stage,
6066 heads,
6067 local_q_dim,
6068 local_kv_dim,
6069 ..
6070 } = &mut *ws;
6071 let input_ref: &CudaSlice<f32> = if same_dev {
6072 h_stage
6073 .as_ref()
6074 .ok_or("step TP decode v2 stage not armed")?
6075 } else {
6076 &attn_in[rank]
6077 };
6078 match (
6079 &q_m.ranks[rank].weight,
6080 &k_m.ranks[rank].weight,
6081 &v_m.ranks[rank].weight,
6082 ) {
6083 (
6084 ResidentBf16Weight::F32(wq),
6085 ResidentBf16Weight::F32(wk),
6086 ResidentBf16Weight::F32(wv),
6087 ) => {
6088 let (wg, out_g) = match &gate_shards {
6089 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6090 Some(StepTpGateShards::Bf16(_)) => {
6091 return Err("step TP decode v2 gate shard class does not \
6092 match the F32 projections"
6093 .into());
6094 }
6095 None => (&*gate_e, 0),
6097 };
6098 engine.matvec_f32_qkv_into(
6099 wq,
6100 wk,
6101 wv,
6102 wg,
6103 input_ref,
6104 &mut q_raw[rank],
6105 &mut k_raw[rank],
6106 &mut v_raw[rank],
6107 &mut gate[rank],
6108 q_m.in_features,
6109 *local_q_dim,
6110 *local_kv_dim,
6111 out_g,
6112 )?;
6113 }
6114 (
6115 ResidentBf16Weight::Bf16(wq),
6116 ResidentBf16Weight::Bf16(wk),
6117 ResidentBf16Weight::Bf16(wv),
6118 ) => {
6119 let (wg, out_g) = match &gate_shards {
6120 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6121 Some(StepTpGateShards::F32(_)) => {
6122 return Err("step TP decode v2 gate shard class does not \
6123 match the bf16 projections"
6124 .into());
6125 }
6126 None => (wq, 0),
6127 };
6128 engine.matvec_bf16_qkvg_into(
6129 wq,
6130 wk,
6131 wv,
6132 wg,
6133 input_ref,
6134 &mut q_raw[rank],
6135 &mut k_raw[rank],
6136 &mut v_raw[rank],
6137 &mut gate[rank],
6138 q_m.in_features,
6139 *local_q_dim,
6140 *local_kv_dim,
6141 out_g,
6142 )?;
6143 }
6144 _ => {
6145 return Err("step TP decode v2 QKV projections mix residency classes".into());
6146 }
6147 }
6148 } else {
6149 for (matrix, local_out, raw) in [
6150 (q_m, ws.local_q_dim, &mut ws.q_raw),
6151 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6152 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6153 ] {
6154 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6155 return Err("step TP decode v2 lost its F32 projection residency".into());
6156 };
6157 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6158 engine.linear_f32_resident_canonical_rows_t1_into(
6159 &decode_input.ranks[rank],
6160 values_w,
6161 &mut raw[rank],
6162 matrix.in_features,
6163 local_out,
6164 chunk_rows,
6165 )?;
6166 }
6167 }
6168 if qkv_fused && defer_norm_rope {
6169 } else if qkv_fused {
6171 let StepTpDecodeV2Ws {
6174 q_raw,
6175 k_raw,
6176 q,
6177 k,
6178 pos,
6179 pos_stage,
6180 ..
6181 } = &mut *ws;
6182 let same_dev = engine.ctx().ordinal() == ws_e_device;
6183 let pos_ref: &CudaSlice<i32> = if same_dev {
6184 pos_stage
6185 .as_ref()
6186 .ok_or("step TP decode v2 pos stage not armed")?
6187 } else {
6188 &pos[rank]
6189 };
6190 engine.qk_norm_rope_into(
6191 &q_raw[rank],
6192 &k_raw[rank],
6193 &q_norm[rank],
6194 &k_norm[rank],
6195 &mut q[rank],
6196 &mut k[rank],
6197 pos_ref,
6198 head_dim,
6199 n_rot,
6200 local_heads,
6201 local_kv_heads,
6202 rms_eps,
6203 rope_base,
6204 1.0,
6205 rope_freqs[rank],
6206 )?;
6207 } else {
6208 engine.rms_norm(
6209 &ws.q_raw[rank],
6210 &q_norm[rank],
6211 &mut ws.q[rank],
6212 head_dim,
6213 local_heads,
6214 rms_eps,
6215 )?;
6216 engine.rms_norm(
6217 &ws.k_raw[rank],
6218 &k_norm[rank],
6219 &mut ws.k[rank],
6220 head_dim,
6221 local_kv_heads,
6222 rms_eps,
6223 )?;
6224 {
6225 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6226 engine
6227 .stream()
6228 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6229 }
6230 engine.rope_neox2(
6231 &mut ws.q[rank],
6232 &mut ws.k[rank],
6233 &ws.pos[rank],
6234 head_dim,
6235 n_rot,
6236 local_heads,
6237 local_kv_heads,
6238 1,
6239 rope_base,
6240 1.0,
6241 rope_freqs[rank],
6242 )?;
6243 }
6244 if gate_shards.is_none() {
6245 let gate_start = rank * (ws.heads / ranks);
6246 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6247 engine.stream().memcpy_dtod(
6248 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6249 &mut gate_dst,
6250 )?;
6251 }
6252 Ok(())
6253 }
6254
6255 pub(crate) fn decode_v2_finish_rank_partial(
6259 &self,
6260 ws: &mut StepTpDecodeV2Ws,
6261 o_m: &ResidentStepBf16RowParallel,
6262 o_fused: bool,
6263 rank: usize,
6264 ) -> Result<(), Box<dyn std::error::Error>> {
6265 let engine = &self.ranks[rank];
6266 let _main = engine.gpu.enter_main()?;
6267 if o_fused {
6268 let StepTpDecodeV2Ws {
6269 gated,
6270 o_partials,
6271 o_block_cols,
6272 o_out,
6273 ..
6274 } = &mut *ws;
6275 let all_f32 = o_m.ranks[rank]
6276 .iter()
6277 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6278 if all_f32 {
6279 let mut weights = Vec::with_capacity(4);
6280 for block in 0..4 {
6281 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6282 unreachable!("all_f32 checked above");
6283 };
6284 weights.push(weight);
6285 }
6286 engine.matvec_f32_b4_into(
6287 [weights[0], weights[1], weights[2], weights[3]],
6288 &gated[rank],
6289 &mut o_partials[rank][0],
6290 *o_block_cols,
6291 *o_out,
6292 )?;
6293 } else {
6294 let mut weights = Vec::with_capacity(4);
6295 for block in 0..4 {
6296 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6297 return Err("step TP decode v2 O projections mix residency classes".into());
6298 };
6299 weights.push(weight);
6300 }
6301 engine.matvec_bf16_b4_into(
6302 [weights[0], weights[1], weights[2], weights[3]],
6303 &gated[rank],
6304 &mut o_partials[rank][0],
6305 *o_block_cols,
6306 *o_out,
6307 )?;
6308 }
6309 } else {
6310 for block in 0..ws.blocks_per_rank {
6311 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6312 return Err("step TP decode v2 lost its F32 O residency".into());
6313 };
6314 let x =
6315 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
6316 let w = weight.slice(0..weight.len());
6317 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
6318 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
6319 }
6320 }
6321 Ok(())
6322 }
6323
6324 pub(crate) fn decode_v2_finish(
6332 &self,
6333 ws: &mut StepTpDecodeV2Ws,
6334 e: &Engine,
6335 o_m: &ResidentStepBf16RowParallel,
6336 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6337 let ranks = self.ranks.len();
6338 if e.ctx().ordinal() != ws.e_device {
6339 return Err("step TP decode v2 finish engine changed".into());
6340 }
6341 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
6346
6347 for rank in 0..ranks {
6350 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
6351 if rank == 0 {
6352 continue;
6355 }
6356 let engine = &self.ranks[rank];
6357 let _main = engine.gpu.enter_main()?;
6358 ws.ev_rank[rank].record(&engine.stream())?;
6359 }
6360
6361 let root = &self.ranks[0];
6363 #[allow(unused_assignments)]
6364 let mut final_in_a = false;
6365 {
6366 let _main = root.gpu.enter_main()?;
6367 for ev in ws.ev_rank.iter().skip(1) {
6368 root.stream().wait(ev)?;
6369 }
6370 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
6371 ws.ev_oproj.record(&root.stream())?;
6377 let _main = e.gpu.enter_main()?;
6378 e.stream().wait(&ws.ev_oproj)?;
6379 let mut output = e.uninit(ws.o_out)?;
6380 if oproj_tail_on() && oproj_tail_eligible() {
6381 use cudarc::driver::DevicePtr;
6384 let stream = e.stream();
6385 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
6386 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
6387 set_oproj_tail((p0 as u64, p1 as u64));
6388 return Ok(output);
6389 }
6390 e.add(
6391 &ws.o_partials[0][0],
6392 &ws.o_partials[1][0],
6393 &mut output,
6394 ws.o_out,
6395 )?;
6396 return Ok(output);
6397 }
6398 if o_fused {
6399 self.decode_v2_finish_root_fused(ws)?;
6400 ws.ev_oproj.record(&root.stream())?;
6401 let _main = e.gpu.enter_main()?;
6402 e.stream().wait(&ws.ev_oproj)?;
6403 let mut output = e.uninit(ws.o_out)?;
6404 e.stream().memcpy_dtod(
6405 &ws.reduce_a.slice(0..ws.o_out),
6406 &mut output.slice_mut(0..ws.o_out),
6407 )?;
6408 return Ok(output);
6409 }
6410 let mut first = true;
6411 let mut current_is_a = false;
6412 for rank in 0..ranks {
6413 for block in 0..ws.blocks_per_rank {
6414 let use_peer = rank != 0;
6415 if use_peer {
6416 root.stream()
6417 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
6418 }
6419 match (first, current_is_a, use_peer) {
6421 (true, _, true) => {
6422 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6423 }
6424 (true, _, false) => root.add(
6425 &ws.zeros,
6426 &ws.o_partials[0][block],
6427 &mut ws.reduce_a,
6428 ws.o_out,
6429 )?,
6430 (false, true, true) => {
6431 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
6432 }
6433 (false, true, false) => root.add(
6434 &ws.reduce_a,
6435 &ws.o_partials[0][block],
6436 &mut ws.reduce_b,
6437 ws.o_out,
6438 )?,
6439 (false, false, true) => {
6440 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6441 }
6442 (false, false, false) => root.add(
6443 &ws.reduce_b,
6444 &ws.o_partials[0][block],
6445 &mut ws.reduce_a,
6446 ws.o_out,
6447 )?,
6448 }
6449 current_is_a = first || !current_is_a;
6450 first = false;
6451 }
6452 }
6453 final_in_a = current_is_a;
6454
6455 for rank in 0..ranks {
6456 let start = rank * ws.local_kv_dim;
6457 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
6458 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
6459 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
6460 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
6461 }
6462 ws.ev_oproj.record(&root.stream())?;
6463 }
6464
6465 let _main = e.gpu.enter_main()?;
6469 e.stream().wait(&ws.ev_oproj)?;
6470 let mut output = e.uninit(ws.o_out)?;
6471 let source = if final_in_a {
6472 &ws.reduce_a
6473 } else {
6474 &ws.reduce_b
6475 };
6476 e.stream().memcpy_dtod(
6477 &source.slice(0..ws.o_out),
6478 &mut output.slice_mut(0..ws.o_out),
6479 )?;
6480 Ok(output)
6481 }
6482
6483 pub fn run_routed_experts(
6484 &self,
6485 experts: &ResidentExpertParallel,
6486 input: &[f32],
6487 tokens: usize,
6488 selected: &[usize],
6489 route_weights: &[f32],
6490 experts_per_token: usize,
6491 activation_limit: Option<f32>,
6492 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6493 validate_step_expert_activation_limit(activation_limit)?;
6494 validate_ep_residency(&self.ranks, experts)?;
6495 validate_activations(input, tokens, experts.input_width)?;
6496 let pairs = tokens
6497 .checked_mul(experts_per_token)
6498 .ok_or("EP route count overflow")?;
6499 if selected.len() != pairs || route_weights.len() != pairs {
6500 return Err(format!(
6501 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
6502 {experts_per_token} ({pairs})",
6503 selected.len(),
6504 route_weights.len(),
6505 )
6506 .into());
6507 }
6508 if !route_weights.iter().all(|weight| weight.is_finite()) {
6509 return Err("EP route weights contain a non-finite value".into());
6510 }
6511 if self.native_p2p {
6512 return self.run_routed_experts_native(
6513 experts,
6514 input,
6515 tokens,
6516 selected,
6517 route_weights,
6518 experts_per_token,
6519 activation_limit,
6520 );
6521 }
6522
6523 let mut output = vec![0.0f32; tokens * experts.input_width];
6524 let per_rank = experts.expert_count / experts.ranks.len();
6525 for token in 0..tokens {
6526 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6527 for slot in 0..experts_per_token {
6528 let pair = token * experts_per_token + slot;
6529 let expert = selected[pair];
6530 if expert >= experts.expert_count {
6531 return Err(format!(
6532 "EP selected expert {expert} outside 0..{}",
6533 experts.expert_count
6534 )
6535 .into());
6536 }
6537 let owner = expert / per_rank;
6538 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6539 let rank = &experts.ranks[owner];
6540 let engine = &self.ranks[owner];
6541 let gate =
6542 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
6543 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
6544 let activated: Vec<f32> = gate
6545 .iter()
6546 .zip(&up)
6547 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6548 .collect();
6549 debug_assert_eq!(activated.len(), experts.expert_width);
6550 let down =
6551 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
6552 let weight = route_weights[pair];
6553 for (sum, value) in output
6554 [token * experts.input_width..(token + 1) * experts.input_width]
6555 .iter_mut()
6556 .zip(down)
6557 {
6558 *sum += weight * value;
6559 }
6560 }
6561 }
6562 Ok(output)
6563 }
6564
6565 fn run_routed_experts_native(
6566 &self,
6567 experts: &ResidentExpertParallel,
6568 input: &[f32],
6569 tokens: usize,
6570 selected: &[usize],
6571 route_weights: &[f32],
6572 experts_per_token: usize,
6573 activation_limit: Option<f32>,
6574 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6575 if !self.native_p2p || self.ranks.len() < 2 {
6576 return Err("native EP execution requires at least two P2P ranks".into());
6577 }
6578 if self.ep_device_arithmetic {
6579 return self.run_routed_experts_native_device(
6580 experts,
6581 input,
6582 tokens,
6583 selected,
6584 route_weights,
6585 experts_per_token,
6586 activation_limit,
6587 );
6588 }
6589 let mut output = vec![0.0f32; tokens * experts.input_width];
6590 let per_rank = experts.expert_count / experts.ranks.len();
6591 for token in 0..tokens {
6592 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6593 let mut rank_inputs = (0..self.ranks.len())
6594 .map(|_| None)
6595 .collect::<Vec<Option<CudaSlice<f32>>>>();
6596 rank_inputs[0] = Some({
6597 let root = &self.ranks[0];
6598 let _main = root.gpu.enter_main()?;
6599 root.htod(input_row)?
6600 });
6601
6602 for slot in 0..experts_per_token {
6603 let pair = token * experts_per_token + slot;
6604 let expert = selected[pair];
6605 if expert >= experts.expert_count {
6606 return Err(format!(
6607 "EP selected expert {expert} outside 0..{}",
6608 experts.expert_count
6609 )
6610 .into());
6611 }
6612 let owner = expert / per_rank;
6613 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6614 if rank_inputs[owner].is_none() {
6615 let peer_input = {
6616 let root_input = rank_inputs[0]
6617 .as_ref()
6618 .ok_or("native EP lost its root input")?;
6619 let engine = &self.ranks[owner];
6620 let _main = engine.gpu.enter_main()?;
6621 let mut peer_input = engine.uninit(experts.input_width)?;
6622 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6623 peer_input
6624 };
6625 rank_inputs[owner] = Some(peer_input);
6626 }
6627
6628 let rank = &experts.ranks[owner];
6629 let engine = &self.ranks[owner];
6630 let owner_input = rank_inputs[owner]
6631 .as_ref()
6632 .ok_or("native EP owner input is absent after dispatch")?;
6633 let gate = run_resident_bank_expert_device(
6634 engine,
6635 &rank.gate,
6636 local_expert,
6637 owner_input,
6638 1,
6639 )?;
6640 let up = run_resident_bank_expert_device(
6641 engine,
6642 &rank.up,
6643 local_expert,
6644 owner_input,
6645 1,
6646 )?;
6647 let (gate, up) = {
6648 let _main = engine.gpu.enter_main()?;
6649 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
6650 };
6651 let activated = gate
6652 .iter()
6653 .zip(&up)
6654 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6655 .collect::<Vec<_>>();
6656 debug_assert_eq!(activated.len(), experts.expert_width);
6657 let activated = {
6658 let _main = engine.gpu.enter_main()?;
6659 engine.htod(&activated)?
6660 };
6661 let down = run_resident_bank_expert_device(
6662 engine,
6663 &rank.down,
6664 local_expert,
6665 &activated,
6666 1,
6667 )?;
6668 let down = if owner == 0 {
6669 let _main = engine.gpu.enter_main()?;
6670 engine.dtoh(&down)?
6671 } else {
6672 let root = &self.ranks[0];
6673 let _main = root.gpu.enter_main()?;
6674 let mut root_down = root.uninit(experts.input_width)?;
6675 root.stream().memcpy_dtod(&down, &mut root_down)?;
6676 root.dtoh(&root_down)?
6677 };
6678 let weight = route_weights[pair];
6679 for (sum, value) in output
6680 [token * experts.input_width..(token + 1) * experts.input_width]
6681 .iter_mut()
6682 .zip(down)
6683 {
6684 *sum += weight * value;
6685 }
6686 }
6687 }
6688 Ok(output)
6689 }
6690
6691 fn run_routed_experts_native_device(
6692 &self,
6693 experts: &ResidentExpertParallel,
6694 input: &[f32],
6695 tokens: usize,
6696 selected: &[usize],
6697 route_weights: &[f32],
6698 experts_per_token: usize,
6699 activation_limit: Option<f32>,
6700 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6701 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
6702 return Err(
6703 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
6704 );
6705 }
6706 let mut output = Vec::with_capacity(tokens * experts.input_width);
6707 let per_rank = experts.expert_count / experts.ranks.len();
6708 let root = &self.ranks[0];
6709 for token in 0..tokens {
6710 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6711 let mut rank_inputs = (0..self.ranks.len())
6712 .map(|_| None)
6713 .collect::<Vec<Option<CudaSlice<f32>>>>();
6714 rank_inputs[0] = Some({
6715 let _main = root.gpu.enter_main()?;
6716 root.htod(input_row)?
6717 });
6718 let mut root_output = {
6719 let _main = root.gpu.enter_main()?;
6720 root.zeros(experts.input_width)?
6721 };
6722 let mut remote_down_keepalive = Vec::new();
6723
6724 for slot in 0..experts_per_token {
6725 let pair = token * experts_per_token + slot;
6726 let expert = selected[pair];
6727 if expert >= experts.expert_count {
6728 return Err(format!(
6729 "EP selected expert {expert} outside 0..{}",
6730 experts.expert_count
6731 )
6732 .into());
6733 }
6734 let owner = expert / per_rank;
6735 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6736 if rank_inputs[owner].is_none() {
6737 let peer_input = {
6738 let root_input = rank_inputs[0]
6739 .as_ref()
6740 .ok_or("native EP lost its root input")?;
6741 let engine = &self.ranks[owner];
6742 let _main = engine.gpu.enter_main()?;
6743 let mut peer_input = engine.uninit(experts.input_width)?;
6744 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6745 peer_input
6746 };
6747 rank_inputs[owner] = Some(peer_input);
6748 }
6749
6750 let rank = &experts.ranks[owner];
6751 let engine = &self.ranks[owner];
6752 let owner_input = rank_inputs[owner]
6753 .as_ref()
6754 .ok_or("native EP owner input is absent after dispatch")?;
6755 let gate = run_resident_bank_expert_device(
6756 engine,
6757 &rank.gate,
6758 local_expert,
6759 owner_input,
6760 1,
6761 )?;
6762 let up = run_resident_bank_expert_device(
6763 engine,
6764 &rank.up,
6765 local_expert,
6766 owner_input,
6767 1,
6768 )?;
6769 let activated = {
6770 let _main = engine.gpu.enter_main()?;
6771 let mut activated = engine.uninit(experts.expert_width)?;
6772 if let Some(limit) = activation_limit {
6773 engine.silu_clamped_mul_host_expf(
6774 &gate,
6775 &up,
6776 limit,
6777 &mut activated,
6778 experts.expert_width,
6779 )?;
6780 } else {
6781 engine.silu_mul_host_expf(
6782 &gate,
6783 &up,
6784 &mut activated,
6785 experts.expert_width,
6786 )?;
6787 }
6788 activated
6789 };
6790 let down = run_resident_bank_expert_device(
6791 engine,
6792 &rank.down,
6793 local_expert,
6794 &activated,
6795 1,
6796 )?;
6797 let root_down = if owner == 0 {
6798 down
6799 } else {
6800 let _main = root.gpu.enter_main()?;
6801 let mut root_down = root.uninit(experts.input_width)?;
6802 root.stream().memcpy_dtod(&down, &mut root_down)?;
6803 remote_down_keepalive.push(down);
6807 root_down
6808 };
6809 let _main = root.gpu.enter_main()?;
6810 let mut destination = root_output.slice_mut(0..experts.input_width);
6811 root.axpy_host_into(
6812 &root_down.slice(0..root_down.len()),
6813 route_weights[pair],
6814 &mut destination,
6815 experts.input_width,
6816 )?;
6817 }
6818
6819 let _main = root.gpu.enter_main()?;
6820 let root_output = root.dtoh(&root_output)?;
6821 drop(remote_down_keepalive);
6822 output.extend(root_output);
6823 }
6824 Ok(output)
6825 }
6826}
6827
6828fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
6829 if matrix.out_features % tp != 0 {
6830 return Err(format!(
6831 "column-parallel out_features {} is not divisible by TP={tp}",
6832 matrix.out_features
6833 ));
6834 }
6835 let local_out = matrix.out_features / tp;
6836 if local_out % FP8_BLOCK != 0 {
6837 return Err(format!(
6838 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
6839 E4M3 scale block"
6840 ));
6841 }
6842 Ok(())
6843}
6844
6845fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
6846 if !matches!(tp, 1 | 2 | 4 | 8) {
6847 return Err(format!(
6848 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
6849 ));
6850 }
6851 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
6852 return Err(format!(
6853 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
6854 ));
6855 }
6856 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
6857 let local_out = out_features / tp;
6858 if local_out % canonical_rows != 0 {
6859 return Err(format!(
6860 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
6861 {canonical_rows}-row chunks"
6862 ));
6863 }
6864 Ok(canonical_rows)
6865}
6866
6867fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
6868 if !matches!(tp, 1 | 2 | 4 | 8) {
6869 return Err(format!(
6870 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
6871 ));
6872 }
6873 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
6874 return Err(format!(
6875 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
6876 ));
6877 }
6878 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
6879 let local_in = in_features / tp;
6880 if local_in % canonical_cols != 0 {
6881 return Err(format!(
6882 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
6883 {canonical_cols}-column chunks"
6884 ));
6885 }
6886 Ok(canonical_cols)
6887}
6888
6889fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
6890 if matrix.in_features % tp != 0 {
6891 return Err(format!(
6892 "row-parallel in_features {} is not divisible by TP={tp}",
6893 matrix.in_features
6894 ));
6895 }
6896 let local_in = matrix.in_features / tp;
6897 if local_in % FP8_BLOCK != 0 {
6898 return Err(format!(
6899 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
6900 E4M3 scale block"
6901 ));
6902 }
6903 Ok(())
6904}
6905
6906fn upload_rank(
6907 engine: &Engine,
6908 matrix: E4m3BlockMatrix<'_>,
6909) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
6910 let _main = engine.gpu.enter_main()?;
6911 matrix.validate()?;
6912 Ok(ResidentE4m3Rank {
6913 codes: engine.htod_bytes(matrix.codes)?,
6914 scales: engine.htod(matrix.scales)?,
6915 out_features: matrix.out_features,
6916 in_features: matrix.in_features,
6917 })
6918}
6919
6920fn upload_bf16_rank(
6921 engine: &Engine,
6922 matrix: Bf16Matrix<'_>,
6923 f32_mirror: bool,
6924) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
6925 let _main = engine.gpu.enter_main()?;
6926 matrix.validate()?;
6927 let bytes = engine.htod_bytes(matrix.bytes)?;
6928 let weight = if f32_mirror {
6929 let values = matrix
6930 .out_features
6931 .checked_mul(matrix.in_features)
6932 .ok_or("resident BF16 mirror element count overflow")?;
6933 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
6934 } else {
6935 ResidentBf16Weight::Bf16(bytes)
6936 };
6937 Ok(ResidentBf16Rank {
6938 weight,
6939 out_features: matrix.out_features,
6940 in_features: matrix.in_features,
6941 })
6942}
6943
6944fn upload_expert_bank_rank(
6945 engine: &Engine,
6946 bank: E4m3ExpertBank<'_>,
6947 expert_range: Range<usize>,
6948) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
6949 let _main = engine.gpu.enter_main()?;
6950 bank.validate()?;
6951 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
6952 return Err(format!(
6953 "invalid EP expert range {expert_range:?} for {} experts",
6954 bank.expert_count
6955 )
6956 .into());
6957 }
6958 let code_stride = bank.out_features * bank.in_features;
6959 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
6960 Ok(ResidentE4m3ExpertBankRank {
6961 codes: engine.htod_bytes(
6962 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
6963 )?,
6964 scales: engine.htod(
6965 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
6966 )?,
6967 expert_range,
6968 out_features: bank.out_features,
6969 in_features: bank.in_features,
6970 code_stride,
6971 scale_stride,
6972 k_blocks: None,
6973 })
6974}
6975
6976fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
6977 if bank.out_features % tp != 0 {
6978 return Err(format!(
6979 "TP expert output width {} is not divisible by TP={tp}",
6980 bank.out_features
6981 ));
6982 }
6983 let local_out = bank.out_features / tp;
6984 if local_out % FP8_BLOCK != 0 {
6985 return Err(format!(
6986 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
6987 ));
6988 }
6989 Ok(())
6990}
6991
6992fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
6993 if bank.in_features % tp != 0 {
6994 return Err(format!(
6995 "TP expert input width {} is not divisible by TP={tp}",
6996 bank.in_features
6997 ));
6998 }
6999 let local_in = bank.in_features / tp;
7000 if local_in % FP8_BLOCK != 0 {
7001 return Err(format!(
7002 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7003 ));
7004 }
7005 Ok(())
7006}
7007
7008fn upload_column_bank_rank(
7009 engine: &Engine,
7010 bank: E4m3ExpertBank<'_>,
7011 tp: usize,
7012 rank: usize,
7013) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7014 let _main = engine.gpu.enter_main()?;
7015 let packed = pack_column_bank_rank(bank, tp, rank)?;
7016 Ok(ResidentE4m3ExpertBankRank {
7017 codes: engine.htod_bytes(&packed.codes)?,
7018 scales: engine.htod(&packed.scales)?,
7019 expert_range: packed.expert_range,
7020 out_features: packed.out_features,
7021 in_features: packed.in_features,
7022 code_stride: packed.code_stride,
7023 scale_stride: packed.scale_stride,
7024 k_blocks: packed.k_blocks,
7025 })
7026}
7027
7028fn pack_column_bank_rank(
7029 bank: E4m3ExpertBank<'_>,
7030 tp: usize,
7031 rank: usize,
7032) -> Result<PackedE4m3ExpertBankRank, String> {
7033 bank.validate()?;
7034 validate_column_bank_shape(bank, tp)?;
7035 if rank >= tp {
7036 return Err(format!("TP rank {rank} outside 0..{tp}"));
7037 }
7038 let local_out = bank.out_features / tp;
7039 let full_code_stride = bank.out_features * bank.in_features;
7040 let local_code_stride = local_out * bank.in_features;
7041 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7042 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7043 let local_scale_rows = local_out / FP8_BLOCK;
7044 let local_scale_stride = local_scale_rows * scale_cols;
7045 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7046 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7047 let row_start = rank * local_out;
7048 let scale_row_start = rank * local_scale_rows;
7049 for expert in 0..bank.expert_count {
7050 let code_start = expert * full_code_stride + row_start * bank.in_features;
7051 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7052 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7053 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7054 }
7055 Ok(PackedE4m3ExpertBankRank {
7056 codes,
7057 scales,
7058 expert_range: 0..bank.expert_count,
7059 out_features: local_out,
7060 in_features: bank.in_features,
7061 code_stride: local_code_stride,
7062 scale_stride: local_scale_stride,
7063 k_blocks: None,
7064 })
7065}
7066
7067fn upload_row_bank_rank(
7068 engine: &Engine,
7069 bank: E4m3ExpertBank<'_>,
7070 tp: usize,
7071 rank: usize,
7072) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7073 let _main = engine.gpu.enter_main()?;
7074 let packed = pack_row_bank_rank(bank, tp, rank)?;
7075 Ok(ResidentE4m3ExpertBankRank {
7076 codes: engine.htod_bytes(&packed.codes)?,
7077 scales: engine.htod(&packed.scales)?,
7078 expert_range: packed.expert_range,
7079 out_features: packed.out_features,
7080 in_features: packed.in_features,
7081 code_stride: packed.code_stride,
7082 scale_stride: packed.scale_stride,
7083 k_blocks: packed.k_blocks,
7084 })
7085}
7086
7087fn pack_row_bank_rank(
7088 bank: E4m3ExpertBank<'_>,
7089 tp: usize,
7090 rank: usize,
7091) -> Result<PackedE4m3ExpertBankRank, String> {
7092 bank.validate()?;
7093 validate_row_bank_shape(bank, tp)?;
7094 if rank >= tp {
7095 return Err(format!("TP rank {rank} outside 0..{tp}"));
7096 }
7097 let local_in = bank.in_features / tp;
7098 let full_code_stride = bank.out_features * bank.in_features;
7099 let local_code_stride = bank.out_features * local_in;
7100 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7101 let local_scale_cols = local_in / FP8_BLOCK;
7102 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7103 let full_scale_stride = scale_rows * full_scale_cols;
7104 let local_scale_stride = scale_rows * local_scale_cols;
7105 let global_block_start = rank * local_scale_cols;
7106 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7107 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7108 for expert in 0..bank.expert_count {
7109 let expert_code_start = expert * full_code_stride;
7110 let expert_scale_start = expert * full_scale_stride;
7111 for local_block in 0..local_scale_cols {
7112 let global_block = global_block_start + local_block;
7113 let column_start = global_block * FP8_BLOCK;
7114 for row in 0..bank.out_features {
7115 let start = expert_code_start + row * bank.in_features + column_start;
7116 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7117 }
7118 for row in 0..scale_rows {
7119 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7120 }
7121 }
7122 }
7123 Ok(PackedE4m3ExpertBankRank {
7124 codes,
7125 scales,
7126 expert_range: 0..bank.expert_count,
7127 out_features: bank.out_features,
7128 in_features: local_in,
7129 code_stride: local_code_stride,
7130 scale_stride: local_scale_stride,
7131 k_blocks: Some(local_scale_cols),
7132 })
7133}
7134
7135fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7136 if engines.len() != ranks.len() {
7137 return Err(format!(
7138 "resident TP rank count {} != runtime rank count {}",
7139 ranks.len(),
7140 engines.len()
7141 ));
7142 }
7143 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7144 let device = engine.ctx().ordinal();
7145 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7146 return Err(format!(
7147 "resident TP rank {rank} is not owned by runtime device {device}"
7148 ));
7149 }
7150 }
7151 Ok(())
7152}
7153
7154fn validate_tp_bank_residency(
7155 engines: &[Engine],
7156 experts: &ResidentTpExpertBank,
7157) -> Result<(), String> {
7158 if engines.len() != experts.gate.len()
7159 || engines.len() != experts.up.len()
7160 || engines.len() != experts.down.len()
7161 {
7162 return Err(format!(
7163 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7164 experts.gate.len(),
7165 experts.up.len(),
7166 experts.down.len(),
7167 engines.len()
7168 ));
7169 }
7170 for (rank, engine) in engines.iter().enumerate() {
7171 let device = engine.ctx().ordinal();
7172 for (projection, bank) in [
7173 ("gate", &experts.gate[rank]),
7174 ("up", &experts.up[rank]),
7175 ("down", &experts.down[rank]),
7176 ] {
7177 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7178 return Err(format!(
7179 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7180 {device}"
7181 ));
7182 }
7183 }
7184 }
7185 Ok(())
7186}
7187
7188fn validate_ep_residency(
7189 engines: &[Engine],
7190 experts: &ResidentExpertParallel,
7191) -> Result<(), String> {
7192 if engines.len() != experts.ranks.len() {
7193 return Err(format!(
7194 "resident EP rank count {} != runtime rank count {}",
7195 experts.ranks.len(),
7196 engines.len()
7197 ));
7198 }
7199 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7200 let device = engine.ctx().ordinal();
7201 for (projection, bank) in [
7202 ("gate", &resident.gate),
7203 ("up", &resident.up),
7204 ("down", &resident.down),
7205 ] {
7206 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7207 return Err(format!(
7208 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7209 {device}"
7210 ));
7211 }
7212 }
7213 }
7214 Ok(())
7215}
7216
7217fn run_rank(
7218 engine: &Engine,
7219 matrix: E4m3BlockMatrix<'_>,
7220 activations: &[f32],
7221 tokens: usize,
7222) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7223 let _main = engine.gpu.enter_main()?;
7224 let codes = engine.htod_bytes(matrix.codes)?;
7225 let scales = engine.htod(matrix.scales)?;
7226 let activations = engine.htod(activations)?;
7227 let output = engine.qmatvec_mmq_fp8_blk(
7228 &codes,
7229 &scales,
7230 &activations,
7231 tokens,
7232 matrix.in_features,
7233 matrix.out_features,
7234 )?;
7235 engine.dtoh(&output)
7236}
7237
7238fn run_resident_rank(
7239 engine: &Engine,
7240 matrix: &ResidentE4m3Rank,
7241 activations: &[f32],
7242 tokens: usize,
7243) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7244 let _main = engine.gpu.enter_main()?;
7245 let activations = engine.htod(activations)?;
7246 let output = engine.qmatvec_mmq_fp8_blk(
7247 &matrix.codes,
7248 &matrix.scales,
7249 &activations,
7250 tokens,
7251 matrix.in_features,
7252 matrix.out_features,
7253 )?;
7254 engine.dtoh(&output)
7255}
7256
7257fn run_resident_bf16_rank(
7258 engine: &Engine,
7259 matrix: &ResidentBf16Rank,
7260 activations: &[f32],
7261 tokens: usize,
7262 canonical_chunk_rows: Option<usize>,
7263) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7264 let _main = engine.gpu.enter_main()?;
7265 let activations = engine.htod(activations)?;
7266 let output = run_resident_bf16_rank_device(
7267 engine,
7268 matrix,
7269 &activations,
7270 tokens,
7271 canonical_chunk_rows,
7272 false,
7273 )?;
7274 engine.dtoh(&output)
7275}
7276
7277fn run_resident_bf16_rank_device(
7278 engine: &Engine,
7279 matrix: &ResidentBf16Rank,
7280 activations: &CudaSlice<f32>,
7281 tokens: usize,
7282 canonical_chunk_rows: Option<usize>,
7283 strided_chunk_output: bool,
7284) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7285 let _main = engine.gpu.enter_main()?;
7286 if activations.ordinal() != engine.ctx().ordinal() {
7287 return Err(format!(
7288 "resident BF16 activation device {} != rank device {}",
7289 activations.ordinal(),
7290 engine.ctx().ordinal()
7291 )
7292 .into());
7293 }
7294 if activations.len() != tokens * matrix.in_features {
7295 return Err(format!(
7296 "resident BF16 activation count {} != {tokens}x{}",
7297 activations.len(),
7298 matrix.in_features
7299 )
7300 .into());
7301 }
7302 match (&matrix.weight, canonical_chunk_rows) {
7303 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
7304 .linear_bf16_resident_canonical_rows(
7305 activations,
7306 bytes,
7307 tokens,
7308 matrix.in_features,
7309 matrix.out_features,
7310 rows,
7311 ),
7312 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
7313 activations,
7314 bytes,
7315 tokens,
7316 matrix.in_features,
7317 matrix.out_features,
7318 ),
7319 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
7320 .linear_f32_resident_canonical_rows_strided(
7321 activations,
7322 values,
7323 tokens,
7324 matrix.in_features,
7325 matrix.out_features,
7326 rows,
7327 ),
7328 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
7329 activations,
7330 values,
7331 tokens,
7332 matrix.in_features,
7333 matrix.out_features,
7334 rows,
7335 ),
7336 (ResidentBf16Weight::F32(values), None) => engine.linear(
7337 activations,
7338 values,
7339 tokens,
7340 matrix.in_features,
7341 matrix.out_features,
7342 ),
7343 }
7344}
7345
7346fn validate_resident_bf16_ranks(
7347 engines: &[Engine],
7348 ranks: &[ResidentBf16Rank],
7349) -> Result<(), String> {
7350 if engines.len() != ranks.len() {
7351 return Err(format!(
7352 "resident BF16 TP rank count {} != runtime rank count {}",
7353 ranks.len(),
7354 engines.len(),
7355 ));
7356 }
7357 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7358 let device = engine.ctx().ordinal();
7359 if matrix.weight.ordinal() != device {
7360 return Err(format!(
7361 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
7362 ));
7363 }
7364 }
7365 Ok(())
7366}
7367
7368fn validate_step_bf16_row_residency(
7369 engines: &[Engine],
7370 matrix: &ResidentStepBf16RowParallel,
7371) -> Result<(), String> {
7372 if engines.len() != matrix.ranks.len() {
7373 return Err(format!(
7374 "resident Step BF16 row rank count {} != runtime rank count {}",
7375 matrix.ranks.len(),
7376 engines.len(),
7377 ));
7378 }
7379 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
7380 if matrix.canonical_chunk_cols != canonical_cols {
7381 return Err(format!(
7382 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
7383 matrix.canonical_chunk_cols
7384 ));
7385 }
7386 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
7387 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
7388 if blocks.len() != blocks_per_rank {
7389 return Err(format!(
7390 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
7391 blocks.len()
7392 ));
7393 }
7394 let device = engine.ctx().ordinal();
7395 for (block, resident) in blocks.iter().enumerate() {
7396 if resident.weight.ordinal() != device
7397 || resident.in_features != canonical_cols
7398 || resident.out_features != matrix.out_features
7399 {
7400 return Err(format!(
7401 "resident Step BF16 row rank {rank} block {block} has inconsistent \
7402 device or geometry"
7403 ));
7404 }
7405 }
7406 }
7407 Ok(())
7408}
7409
7410fn validate_replicated_device_rows(
7411 engines: &[Engine],
7412 rows: &ResidentReplicatedDeviceRows,
7413) -> Result<(), String> {
7414 let rank_lengths = rows
7415 .ranks
7416 .iter()
7417 .map(|rank_rows| rank_rows.len())
7418 .collect::<Vec<_>>();
7419 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
7420 if rows
7421 .ranks
7422 .iter()
7423 .zip(engines)
7424 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
7425 {
7426 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
7427 }
7428 Ok(())
7429}
7430
7431fn replicated_device_row_values(
7432 tokens: usize,
7433 width: usize,
7434 expected_ranks: usize,
7435 rank_lengths: &[usize],
7436) -> Result<usize, String> {
7437 let values = tokens
7438 .checked_mul(width)
7439 .ok_or("replicated device row size overflow")?;
7440 if tokens == 0
7441 || width == 0
7442 || expected_ranks == 0
7443 || rank_lengths.len() != expected_ranks
7444 || rank_lengths.iter().any(|&rank_len| rank_len != values)
7445 {
7446 return Err(format!(
7447 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
7448 tokens,
7449 width,
7450 rank_lengths.len(),
7451 expected_ranks
7452 ));
7453 }
7454 Ok(values)
7455}
7456
7457fn replicated_device_row_source_values(
7458 tokens: usize,
7459 width: usize,
7460 source_len: usize,
7461 source_device: usize,
7462 root_device: usize,
7463) -> Result<usize, String> {
7464 let values = tokens
7465 .checked_mul(width)
7466 .ok_or("replicated device row size overflow")?;
7467 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
7468 return Err(format!(
7469 "replicated device row source has inconsistent geometry/device \
7470 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
7471 ));
7472 }
7473 Ok(values)
7474}
7475
7476fn bf16_column_shard(
7477 matrix: Bf16Matrix<'_>,
7478 tp: usize,
7479 rank: usize,
7480) -> Result<Bf16Matrix<'_>, String> {
7481 matrix.validate()?;
7482 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
7483 return Err(format!(
7484 "invalid BF16 column shard out={} TP={tp} rank={rank}",
7485 matrix.out_features
7486 ));
7487 }
7488 let local_out = matrix.out_features / tp;
7489 let row_bytes = matrix.in_features * 2;
7490 let start = rank * local_out * row_bytes;
7491 Ok(Bf16Matrix {
7492 bytes: &matrix.bytes[start..start + local_out * row_bytes],
7493 out_features: local_out,
7494 in_features: matrix.in_features,
7495 })
7496}
7497
7498fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
7499 matrix.validate()?;
7500 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
7501 return Err(format!(
7502 "invalid BF16 row shard in={} TP={tp} rank={rank}",
7503 matrix.in_features
7504 ));
7505 }
7506 let local_in = matrix.in_features / tp;
7507 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
7508 for row in 0..matrix.out_features {
7509 let start = (row * matrix.in_features + rank * local_in) * 2;
7510 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
7511 }
7512 Ok(bytes)
7513}
7514
7515fn bf16_row_block(
7516 matrix: Bf16Matrix<'_>,
7517 col_start: usize,
7518 block_cols: usize,
7519) -> Result<Vec<u8>, String> {
7520 matrix.validate()?;
7521 let col_end = col_start
7522 .checked_add(block_cols)
7523 .ok_or("BF16 row block column overflow")?;
7524 if block_cols == 0 || col_end > matrix.in_features {
7525 return Err(format!(
7526 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
7527 matrix.in_features
7528 ));
7529 }
7530 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
7531 for row in 0..matrix.out_features {
7532 let start = (row * matrix.in_features + col_start) * 2;
7533 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
7534 }
7535 Ok(bytes)
7536}
7537
7538fn run_resident_bank_expert(
7539 engine: &Engine,
7540 bank: &ResidentE4m3ExpertBankRank,
7541 local_expert: usize,
7542 activations: &[f32],
7543 tokens: usize,
7544) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7545 let _main = engine.gpu.enter_main()?;
7546 if bank.k_blocks.is_some() {
7547 return Err("block-major TP row bank requires canonical block execution".into());
7548 }
7549 let local_count = bank.expert_range.end - bank.expert_range.start;
7550 if local_expert >= local_count {
7551 return Err(format!(
7552 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
7553 bank.expert_range
7554 )
7555 .into());
7556 }
7557 validate_activations(activations, tokens, bank.in_features)?;
7558 let activations = engine.htod(activations)?;
7559 let weight = bank
7560 .codes
7561 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7562 let scales = bank
7563 .scales
7564 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7565 let input = activations.slice(0..activations.len());
7566 let output = engine.qmatvec_mmq_fp8_blk_view(
7567 &weight,
7568 &scales,
7569 &input,
7570 tokens,
7571 bank.in_features,
7572 bank.out_features,
7573 )?;
7574 engine.dtoh(&output)
7575}
7576
7577fn run_resident_bank_expert_block(
7578 engine: &Engine,
7579 bank: &ResidentE4m3ExpertBankRank,
7580 local_expert: usize,
7581 block: usize,
7582 activations: &[f32],
7583) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7584 let _main = engine.gpu.enter_main()?;
7585 let local_count = bank.expert_range.end - bank.expert_range.start;
7586 if local_expert >= local_count {
7587 return Err(format!(
7588 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7589 bank.expert_range
7590 )
7591 .into());
7592 }
7593 let blocks = bank
7594 .k_blocks
7595 .ok_or("TP row bank is not packed in native K-block order")?;
7596 if block >= blocks {
7597 return Err(format!("TP row block {block} outside 0..{blocks}").into());
7598 }
7599 validate_activations(activations, 1, FP8_BLOCK)?;
7600 let block_code_stride = bank.out_features * FP8_BLOCK;
7601 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7602 if bank.in_features != blocks * FP8_BLOCK
7603 || bank.code_stride != blocks * block_code_stride
7604 || bank.scale_stride != blocks * block_scale_stride
7605 {
7606 return Err("TP row bank block-major geometry is inconsistent".into());
7607 }
7608
7609 let expert_code_start = local_expert * bank.code_stride;
7610 let expert_scale_start = local_expert * bank.scale_stride;
7611 let weight = bank.codes.slice(
7612 expert_code_start + block * block_code_stride
7613 ..expert_code_start + (block + 1) * block_code_stride,
7614 );
7615 let scales = bank.scales.slice(
7616 expert_scale_start + block * block_scale_stride
7617 ..expert_scale_start + (block + 1) * block_scale_stride,
7618 );
7619 let activations = engine.htod(activations)?;
7620 let input = activations.slice(0..activations.len());
7621 let output = engine.qmatvec_mmq_fp8_blk_view(
7622 &weight,
7623 &scales,
7624 &input,
7625 1,
7626 FP8_BLOCK,
7627 bank.out_features,
7628 )?;
7629 engine.dtoh(&output)
7630}
7631
7632fn run_resident_bank_expert_device(
7633 engine: &Engine,
7634 bank: &ResidentE4m3ExpertBankRank,
7635 local_expert: usize,
7636 activations: &CudaSlice<f32>,
7637 tokens: usize,
7638) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7639 let _main = engine.gpu.enter_main()?;
7640 if bank.k_blocks.is_some() {
7641 return Err("block-major TP row bank requires canonical block execution".into());
7642 }
7643 let local_count = bank.expert_range.end - bank.expert_range.start;
7644 if local_expert >= local_count {
7645 return Err(format!(
7646 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7647 bank.expert_range
7648 )
7649 .into());
7650 }
7651 let expected = tokens
7652 .checked_mul(bank.in_features)
7653 .ok_or("native TP activation size overflow")?;
7654 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
7655 return Err(format!(
7656 "native TP activation len/device {}/{} != expected {expected}/{}",
7657 activations.len(),
7658 activations.ordinal(),
7659 engine.ctx().ordinal()
7660 )
7661 .into());
7662 }
7663 let weight = bank
7664 .codes
7665 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7666 let scales = bank
7667 .scales
7668 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7669 let input = activations.slice(0..activations.len());
7670 engine.qmatvec_mmq_fp8_blk_view(
7671 &weight,
7672 &scales,
7673 &input,
7674 tokens,
7675 bank.in_features,
7676 bank.out_features,
7677 )
7678}
7679
7680fn run_resident_bank_expert_block_device(
7681 engine: &Engine,
7682 bank: &ResidentE4m3ExpertBankRank,
7683 local_expert: usize,
7684 block: usize,
7685 activations: &cudarc::driver::CudaView<'_, f32>,
7686) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7687 let _main = engine.gpu.enter_main()?;
7688 let local_count = bank.expert_range.end - bank.expert_range.start;
7689 if local_expert >= local_count {
7690 return Err(format!(
7691 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7692 bank.expert_range
7693 )
7694 .into());
7695 }
7696 let blocks = bank
7697 .k_blocks
7698 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
7699 if block >= blocks {
7700 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
7701 }
7702 let activation_device = activations.stream().context().ordinal();
7703 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
7704 return Err(format!(
7705 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
7706 activations.len(),
7707 activation_device,
7708 engine.ctx().ordinal()
7709 )
7710 .into());
7711 }
7712 let block_code_stride = bank.out_features * FP8_BLOCK;
7713 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7714 if bank.in_features != blocks * FP8_BLOCK
7715 || bank.code_stride != blocks * block_code_stride
7716 || bank.scale_stride != blocks * block_scale_stride
7717 {
7718 return Err("native TP row bank block-major geometry is inconsistent".into());
7719 }
7720 let expert_code_start = local_expert * bank.code_stride;
7721 let expert_scale_start = local_expert * bank.scale_stride;
7722 let weight = bank.codes.slice(
7723 expert_code_start + block * block_code_stride
7724 ..expert_code_start + (block + 1) * block_code_stride,
7725 );
7726 let scales = bank.scales.slice(
7727 expert_scale_start + block * block_scale_stride
7728 ..expert_scale_start + (block + 1) * block_scale_stride,
7729 );
7730 engine.qmatvec_mmq_fp8_blk_view(
7731 &weight,
7732 &scales,
7733 activations,
7734 1,
7735 FP8_BLOCK,
7736 bank.out_features,
7737 )
7738}
7739
7740fn configure_native_p2p(
7741 ranks: &[Engine],
7742 devices: &[usize],
7743) -> Result<(), Box<dyn std::error::Error>> {
7744 if ranks.len() != devices.len() || ranks.len() < 2 {
7745 return Err("native TP P2P setup requires matching multi-rank devices".into());
7746 }
7747 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
7748 if engine.ctx().ordinal() != device {
7749 return Err(format!(
7750 "native TP rank {rank} context device {} != requested device {device}",
7751 engine.ctx().ordinal()
7752 )
7753 .into());
7754 }
7755 }
7756
7757 for src in 0..ranks.len() {
7758 for dst in 0..ranks.len() {
7759 if src == dst {
7760 continue;
7761 }
7762 let mut can_access = 0;
7763 unsafe {
7764 cudarc::driver::sys::cuDeviceCanAccessPeer(
7765 &mut can_access,
7766 ranks[src].ctx().cu_device(),
7767 ranks[dst].ctx().cu_device(),
7768 )
7769 .result()?;
7770 }
7771 if can_access == 0 {
7772 return Err(format!(
7773 "native TP requires P2P, but dev{} cannot access dev{}",
7774 devices[src], devices[dst]
7775 )
7776 .into());
7777 }
7778 ranks[src].ctx().bind_to_thread()?;
7779 let rc =
7780 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
7781 use cudarc::driver::sys::cudaError_enum as E;
7782 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
7783 return Err(format!(
7784 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
7785 devices[src], devices[dst]
7786 )
7787 .into());
7788 }
7789 }
7790 }
7791
7792 for &owner in devices {
7793 for &accessor in devices {
7794 if owner == accessor {
7795 continue;
7796 }
7797 let device = cudarc::driver::result::device::get(owner as i32)?;
7798 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
7799 unsafe {
7800 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
7801 }
7802 let desc = cudarc::driver::sys::CUmemAccessDesc {
7803 location: cudarc::driver::sys::CUmemLocation {
7804 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
7805 id: accessor as i32,
7806 },
7807 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
7808 };
7809 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
7810 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
7811 return Err(format!(
7812 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
7813 {rc:?}"
7814 )
7815 .into());
7816 }
7817 }
7818 }
7819
7820 for src in 0..ranks.len() {
7821 for dst in 0..ranks.len() {
7822 if src == dst {
7823 continue;
7824 }
7825 let expected = (0..NATIVE_P2P_PROBE_WORDS)
7826 .map(|index| {
7827 (index as u32)
7828 .wrapping_mul(0x9e37_79b9)
7829 .wrapping_add(((src as u32) << 16) | dst as u32)
7830 })
7831 .collect::<Vec<_>>();
7832 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
7833 let source = ranks[src].htod_u32_v(&expected)?;
7834 let mut destination = ranks[dst].htod_u32_v(&poison)?;
7835 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
7836 let actual = ranks[dst].dtoh_u32(&destination)?;
7837 if actual != expected {
7838 let mismatches = actual
7839 .iter()
7840 .zip(&expected)
7841 .filter(|(actual, expected)| actual != expected)
7842 .count();
7843 return Err(format!(
7844 "native TP peer probe dev{}->dev{} failed: {mismatches}/{} words differ",
7845 devices[src],
7846 devices[dst],
7847 expected.len()
7848 )
7849 .into());
7850 }
7851 }
7852 }
7853 ranks[0].ctx().bind_to_thread()?;
7854 eprintln!(
7855 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
7856 directions={} bytes={} mismatches=0",
7857 ranks.len() * (ranks.len() - 1),
7858 NATIVE_P2P_PROBE_WORDS * std::mem::size_of::<u32>(),
7859 );
7860 Ok(())
7861}
7862
7863fn validate_activations(
7864 activations: &[f32],
7865 tokens: usize,
7866 in_features: usize,
7867) -> Result<(), String> {
7868 let expected = tokens
7869 .checked_mul(in_features)
7870 .ok_or_else(|| "activation size overflow".to_string())?;
7871 if activations.len() != expected {
7872 return Err(format!(
7873 "activation count {} != {tokens}x{in_features} ({expected})",
7874 activations.len()
7875 ));
7876 }
7877 if !activations.iter().all(|value| value.is_finite()) {
7878 return Err("activations contain a non-finite value".to_string());
7879 }
7880 Ok(())
7881}
7882
7883fn column_shard(
7884 matrix: E4m3BlockMatrix<'_>,
7885 tp: usize,
7886 rank: usize,
7887) -> Result<E4m3BlockMatrix<'_>, String> {
7888 let local_out = matrix.out_features / tp;
7889 let row_start = rank * local_out;
7890 let code_start = row_start * matrix.in_features;
7891 let code_end = code_start + local_out * matrix.in_features;
7892 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
7893 let local_scale_rows = local_out / FP8_BLOCK;
7894 let scale_start = rank * local_scale_rows * scale_cols;
7895 let scale_end = scale_start + local_scale_rows * scale_cols;
7896 Ok(E4m3BlockMatrix {
7897 codes: &matrix.codes[code_start..code_end],
7898 scales: &matrix.scales[scale_start..scale_end],
7899 out_features: local_out,
7900 in_features: matrix.in_features,
7901 })
7902}
7903
7904fn row_shard(
7905 matrix: E4m3BlockMatrix<'_>,
7906 tp: usize,
7907 rank: usize,
7908) -> Result<(Vec<u8>, Vec<f32>), String> {
7909 let local_in = matrix.in_features / tp;
7910 let col_start = rank * local_in;
7911 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
7912 for row in 0..matrix.out_features {
7913 let start = row * matrix.in_features + col_start;
7914 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
7915 }
7916
7917 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
7918 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
7919 let local_scale_cols = local_in / FP8_BLOCK;
7920 let scale_col_start = rank * local_scale_cols;
7921 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
7922 for row in 0..scale_rows {
7923 let start = row * scale_cols + scale_col_start;
7924 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
7925 }
7926 Ok((codes, scales))
7927}
7928
7929fn activation_shard(
7930 activations: &[f32],
7931 tokens: usize,
7932 in_features: usize,
7933 tp: usize,
7934 rank: usize,
7935) -> Vec<f32> {
7936 let local_in = in_features / tp;
7937 let col_start = rank * local_in;
7938 let mut shard = Vec::with_capacity(tokens * local_in);
7939 for token in 0..tokens {
7940 let start = token * in_features + col_start;
7941 shard.extend_from_slice(&activations[start..start + local_in]);
7942 }
7943 shard
7944}
7945
7946#[derive(Clone, Copy)]
7966pub struct Nvfp4BlockMatrix<'a> {
7967 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
7971 pub in_features: usize,
7972}
7973
7974impl Nvfp4BlockMatrix<'_> {
7975 pub fn validate(&self) -> Result<(), String> {
7976 if self.in_features == 0 || self.out_features == 0 {
7977 return Err("NVFP4 matrix has a zero dimension".to_string());
7978 }
7979 if self.in_features % 64 != 0 {
7980 return Err(format!(
7981 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
7982 self.in_features
7983 ));
7984 }
7985 if self.codes.len() != self.out_features * self.in_features / 2 {
7986 return Err(format!(
7987 "NVFP4 code bytes {} != {}x{}/2",
7988 self.codes.len(),
7989 self.out_features,
7990 self.in_features
7991 ));
7992 }
7993 if self.scales.len() != self.out_features * self.in_features / 16 {
7994 return Err(format!(
7995 "NVFP4 scale bytes {} != {}x{}/16",
7996 self.scales.len(),
7997 self.out_features,
7998 self.in_features
7999 ));
8000 }
8001 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8002 return Err(format!(
8003 "NVFP4 macro scale {} is not finite-positive",
8004 self.macro_scale
8005 ));
8006 }
8007 Ok(())
8008 }
8009}
8010
8011#[derive(Clone, Copy)]
8013pub struct Nvfp4ExpertBank<'a> {
8014 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8018 pub out_features: usize,
8019 pub in_features: usize,
8020}
8021
8022impl Nvfp4ExpertBank<'_> {
8023 pub fn validate(&self) -> Result<(), String> {
8024 if self.expert_count == 0 {
8025 return Err("NVFP4 expert bank is empty".to_string());
8026 }
8027 if self.macros.len() != self.expert_count {
8028 return Err(format!(
8029 "NVFP4 bank macros {} != expert count {}",
8030 self.macros.len(),
8031 self.expert_count
8032 ));
8033 }
8034 self.expert(0).map(|_| ())
8035 }
8036
8037 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8038 if expert >= self.expert_count {
8039 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8040 }
8041 let code_stride = self.out_features * self.in_features / 2;
8042 let scale_stride = self.out_features * self.in_features / 16;
8043 if self.codes.len() != self.expert_count * code_stride
8044 || self.scales.len() != self.expert_count * scale_stride
8045 {
8046 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8047 }
8048 let matrix = Nvfp4BlockMatrix {
8049 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8050 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8051 macro_scale: self.macros[expert],
8052 out_features: self.out_features,
8053 in_features: self.in_features,
8054 };
8055 matrix.validate()?;
8056 Ok(matrix)
8057 }
8058}
8059
8060pub struct ResidentNvfp4Rank {
8062 blocks: crate::CudaSlice<u8>,
8063 macro_scale: f32,
8064 out_features: usize,
8065 in_features: usize,
8066 row_bytes: usize,
8067}
8068
8069pub struct ResidentNvfp4ColumnParallel {
8070 ranks: Vec<ResidentNvfp4Rank>,
8071 pub out_features: usize,
8072 pub in_features: usize,
8073}
8074
8075pub struct ResidentNvfp4RowParallel {
8076 ranks: Vec<ResidentNvfp4Rank>,
8077 pub out_features: usize,
8078 pub in_features: usize,
8079}
8080
8081pub struct ResidentTpNvfp4Expert {
8082 gate: ResidentNvfp4ColumnParallel,
8083 up: ResidentNvfp4ColumnParallel,
8084 down: ResidentNvfp4RowParallel,
8085 pub input_width: usize,
8086 pub expert_width: usize,
8087}
8088
8089pub struct ResidentNvfp4ColumnBankRank {
8093 bank: crate::CudaSlice<u8>,
8097 expert_bytes: usize,
8098 local_out: usize,
8099 in_features: usize,
8100 row_bytes: usize,
8101}
8102
8103impl ResidentNvfp4ColumnBankRank {
8104 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8105 self.bank
8106 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8107 }
8108}
8109
8110pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8116
8117pub struct ResidentNvfp4RowBankRank {
8118 bank: crate::CudaSlice<u8>,
8120 expert_bytes: usize,
8121 device_rank: usize, out_features: usize,
8123 local_in: usize,
8124 row_bytes: usize,
8125}
8126
8127impl ResidentNvfp4RowBankRank {
8128 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8129 self.bank
8130 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8131 }
8132}
8133
8134impl ResidentNvfp4TensorParallel {
8135 pub(crate) fn device_workspace_handle(
8136 &self,
8137 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8138 &self.device_workspace
8139 }
8140}
8141
8142pub struct ResidentNvfp4TensorParallel {
8143 gate: Vec<ResidentNvfp4ColumnBankRank>,
8144 up: Vec<ResidentNvfp4ColumnBankRank>,
8145 down: Vec<ResidentNvfp4RowBankRank>,
8146 macros_gate: Vec<f32>,
8147 macros_up: Vec<f32>,
8148 macros_down: Vec<f32>,
8149 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8153 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8154 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8155 pub expert_count: usize,
8156 pub input_width: usize,
8157 pub expert_width: usize,
8158 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8161 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8165 pub(crate) ep2: bool,
8169}
8170
8171pub struct Nvfp4T2Workspace {
8175 input2: Vec<crate::CudaSlice<f32>>,
8176 in_q2: Vec<crate::CudaSlice<i8>>,
8177 in_d2: Vec<crate::CudaSlice<f32>>,
8178 sel2: Vec<crate::CudaSlice<i32>>,
8179 route_w2: Vec<crate::CudaSlice<f32>>,
8180 gate_out2: Vec<crate::CudaSlice<f32>>,
8181 up_out2: Vec<crate::CudaSlice<f32>>,
8182 act_q2: Vec<crate::CudaSlice<i8>>,
8183 act_d2: Vec<crate::CudaSlice<f32>>,
8184 partial2: Vec<crate::CudaSlice<f32>>,
8185 acc_a: Vec<crate::CudaSlice<f32>>,
8187 acc_b: Vec<crate::CudaSlice<f32>>,
8188 peer_a: crate::CudaSlice<f32>,
8190 peer_b: crate::CudaSlice<f32>,
8191 omix_a: crate::CudaSlice<f32>,
8192 omix_b: crate::CudaSlice<f32>,
8193 ev_entry: CudaEvent,
8194 ev_rank: Vec<CudaEvent>,
8195 ev_root: CudaEvent,
8196 n_sel: usize,
8197 e_device: usize,
8198}
8199
8200struct RoutesGraph {
8207 exec: cudarc::driver::sys::CUgraphExec,
8208 parent: cudarc::driver::sys::CUgraph,
8209 _children: Vec<cudarc::driver::CudaGraph>,
8210}
8211unsafe impl Send for RoutesGraph {}
8214
8215impl Drop for RoutesGraph {
8216 fn drop(&mut self) {
8217 unsafe {
8218 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8219 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8220 }
8221 }
8222}
8223
8224impl Nvfp4DeviceRoutesWorkspace {
8225 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8226 self.in_stage_e.as_ref()
8227 }
8228 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8229 self.in_stage_e.as_mut()
8230 }
8231 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8232 self.out_stage_e.as_mut()
8233 }
8234 pub(crate) fn arm_stages(
8236 &mut self,
8237 e: &Engine,
8238 width: usize,
8239 n_sel: usize,
8240 ) -> Result<(), Box<dyn std::error::Error>> {
8241 let _main = e.gpu.enter_main()?;
8242 if self.in_stage_e.is_none() {
8243 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8244 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8245 }
8246 if self.dev_route_e.is_none() {
8247 self.dev_route_e = Some((
8248 e.htod_i32(&vec![0i32; n_sel])?,
8249 e.htod(&vec![0.0f32; n_sel])?,
8250 ));
8251 }
8252 Ok(())
8253 }
8254
8255 pub(crate) fn in_and_out_stages_mut(
8257 &mut self,
8258 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
8259 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
8260 (Some(input), Some(output)) => Some((input, output)),
8261 _ => None,
8262 }
8263 }
8264 pub(crate) fn dev_route_e_mut(
8265 &mut self,
8266 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
8267 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
8268 }
8269}
8270
8271pub struct Nvfp4DeviceRoutesWorkspace {
8272 gate_out: Vec<crate::CudaSlice<f32>>,
8275 up_out: Vec<crate::CudaSlice<f32>>,
8276 act_q: Vec<crate::CudaSlice<i8>>,
8277 act_d: Vec<crate::CudaSlice<f32>>,
8278 sel: Vec<crate::CudaSlice<i32>>,
8279 partial: Vec<crate::CudaSlice<f32>>,
8280 accumulator: Vec<crate::CudaSlice<f32>>,
8281 combine_w: Vec<crate::CudaSlice<f32>>,
8283 route_w: Vec<crate::CudaSlice<f32>>,
8286 in_q: Vec<crate::CudaSlice<i8>>,
8289 in_d: Vec<crate::CudaSlice<f32>>,
8290 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
8294 prestaged: bool,
8297 rank1_routed: bool,
8300 fence_flags_raw: u64,
8304 fence_ticket: u32,
8305 ev_input: Option<(CudaEvent, usize)>,
8307 in_stage_e: Option<crate::CudaSlice<f32>>,
8310 out_stage_e: Option<crate::CudaSlice<f32>>,
8311 routes_graph: Option<RoutesGraph>,
8312 raw_dev_route_e: Option<(u64, u64)>,
8314 raw_combine: Option<(u64, u64, u64, u64)>,
8315 raw_input: Vec<u64>,
8316 raw_sel: Vec<u64>,
8317 raw_route_w: Vec<u64>,
8318 remote: crate::CudaSlice<f32>,
8319 combined: crate::CudaSlice<f32>,
8320 n_sel: usize,
8321 input: Vec<crate::CudaSlice<f32>>,
8325 ev_rank: Vec<CudaEvent>,
8326 ev_done: Option<CudaEvent>,
8327 ev_entry: Option<(CudaEvent, usize)>,
8328}
8329
8330struct ResidentNvfp4EpRank {
8332 gate: Vec<crate::CudaSlice<u8>>,
8333 up: Vec<crate::CudaSlice<u8>>,
8334 down: Vec<crate::CudaSlice<u8>>,
8335 #[allow(dead_code)]
8336 expert_range: Range<usize>,
8337}
8338
8339pub struct ResidentNvfp4ExpertParallel {
8340 ranks: Vec<ResidentNvfp4EpRank>,
8341 macros_gate: Vec<f32>,
8342 macros_up: Vec<f32>,
8343 macros_down: Vec<f32>,
8344 pub expert_count: usize,
8345 pub input_width: usize,
8346 pub expert_width: usize,
8347 gate_row_bytes: usize,
8348 down_row_bytes: usize,
8349}
8350
8351fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8352 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
8353 matrix.codes,
8354 matrix.scales,
8355 matrix.out_features,
8356 matrix.in_features,
8357 )
8358}
8359
8360fn nvfp4_row_bytes(in_features: usize) -> usize {
8361 in_features / 64 * 36 }
8363
8364pub(crate) fn fuse_rope_append_on() -> bool {
8372 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8373 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
8374}
8375
8376pub(crate) fn no_local_shadow_on() -> bool {
8377 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8378 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
8379}
8380
8381pub(crate) fn nvfp4_bank_v2_on() -> bool {
8382 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8383 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
8384}
8385
8386fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
8390 let row_bytes = nvfp4_row_bytes(in_features);
8391 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
8392 let n_slots = in_features / 32;
8393 let mut out = Vec::with_capacity(v1.len());
8394 for row in 0..out_features {
8395 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
8396 for g in 0..n_slots {
8397 let (sblk, h) = (g / 2, g % 2);
8398 let b = &r[sblk * 36..sblk * 36 + 36];
8399 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
8400 }
8401 for g in 0..n_slots {
8402 let (sblk, h) = (g / 2, g % 2);
8403 let b = &r[sblk * 36..sblk * 36 + 36];
8404 out.push(b[2 * h]);
8405 out.push(b[2 * h + 1]);
8406 }
8407 }
8408 out
8409}
8410
8411fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8413 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
8414 let v1 = nvfp4_repack_matrix(matrix);
8415 if nvfp4_bank_v2_on() {
8416 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
8417 } else {
8418 v1
8419 }
8420}
8421
8422fn nvfp4_column_shard<'a>(
8425 matrix: Nvfp4BlockMatrix<'a>,
8426 tp: usize,
8427 rank: usize,
8428) -> Result<Nvfp4BlockMatrix<'a>, String> {
8429 if matrix.out_features % tp != 0 {
8430 return Err(format!(
8431 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
8432 matrix.out_features
8433 ));
8434 }
8435 let local_out = matrix.out_features / tp;
8436 let code_row = matrix.in_features / 2;
8437 let scale_row = matrix.in_features / 16;
8438 Ok(Nvfp4BlockMatrix {
8439 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
8440 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
8441 macro_scale: matrix.macro_scale,
8442 out_features: local_out,
8443 in_features: matrix.in_features,
8444 })
8445}
8446
8447fn nvfp4_row_shard(
8450 matrix: Nvfp4BlockMatrix<'_>,
8451 tp: usize,
8452 rank: usize,
8453) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
8454 if matrix.in_features % tp != 0 {
8455 return Err(format!(
8456 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
8457 matrix.in_features
8458 ));
8459 }
8460 let local_in = matrix.in_features / tp;
8461 if local_in % 64 != 0 {
8462 return Err(format!(
8463 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
8464 ));
8465 }
8466 let code_row = matrix.in_features / 2;
8467 let scale_row = matrix.in_features / 16;
8468 let local_code = local_in / 2;
8469 let local_scale = local_in / 16;
8470 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
8471 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
8472 for row in 0..matrix.out_features {
8473 let code_start = row * code_row + rank * local_code;
8474 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
8475 let scale_start = row * scale_row + rank * local_scale;
8476 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
8477 }
8478 Ok((codes, scales, local_in))
8479}
8480
8481fn run_rank_nvfp4(
8485 engine: &Engine,
8486 matrix: Nvfp4BlockMatrix<'_>,
8487 activations: &[f32],
8488 tokens: usize,
8489) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8490 matrix.validate()?;
8491 validate_activations(activations, tokens, matrix.in_features)?;
8492 let _main = engine.gpu.enter_main()?;
8493 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
8494 let activations = engine.htod(activations)?;
8495 let output = engine.qmatvec_nvfp4_fast(
8496 &blocks.slice(0..blocks.len()),
8497 &activations,
8498 tokens,
8499 matrix.in_features,
8500 matrix.out_features,
8501 nvfp4_row_bytes(matrix.in_features),
8502 )?;
8503 engine.dtoh(&output)
8504}
8505
8506fn upload_rank_nvfp4(
8507 engine: &Engine,
8508 matrix: Nvfp4BlockMatrix<'_>,
8509) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
8510 matrix.validate()?;
8511 let _main = engine.gpu.enter_main()?;
8512 Ok(ResidentNvfp4Rank {
8513 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
8514 macro_scale: matrix.macro_scale,
8515 out_features: matrix.out_features,
8516 in_features: matrix.in_features,
8517 row_bytes: nvfp4_row_bytes(matrix.in_features),
8518 })
8519}
8520
8521fn run_resident_rank_nvfp4(
8522 engine: &Engine,
8523 rank: &ResidentNvfp4Rank,
8524 activations: &[f32],
8525 tokens: usize,
8526) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8527 validate_activations(activations, tokens, rank.in_features)?;
8528 let _main = engine.gpu.enter_main()?;
8529 let activations = engine.htod(activations)?;
8530 let output = engine.qmatvec_nvfp4_fast(
8531 &rank.blocks.slice(0..rank.blocks.len()),
8532 &activations,
8533 tokens,
8534 rank.in_features,
8535 rank.out_features,
8536 rank.row_bytes,
8537 )?;
8538 engine.dtoh(&output)
8539}
8540
8541fn apply_macro(values: &mut [f32], macro_scale: f32) {
8542 for value in values.iter_mut() {
8543 *value *= macro_scale;
8544 }
8545}
8546
8547impl TpE4m3HostBounce {
8548 pub fn full_nvfp4(
8550 &self,
8551 matrix: Nvfp4BlockMatrix<'_>,
8552 activations: &[f32],
8553 tokens: usize,
8554 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8555 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
8556 apply_macro(&mut output, matrix.macro_scale);
8557 Ok(output)
8558 }
8559
8560 pub fn column_parallel_nvfp4(
8563 &self,
8564 matrix: Nvfp4BlockMatrix<'_>,
8565 activations: &[f32],
8566 tokens: usize,
8567 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
8568 matrix.validate()?;
8569 validate_activations(activations, tokens, matrix.in_features)?;
8570 let tp = self.ranks.len();
8571 let local_out = matrix.out_features / tp;
8572 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8573 let mut rank_outputs = Vec::with_capacity(tp);
8574 for (rank_index, rank) in self.ranks.iter().enumerate() {
8575 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
8576 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
8577 let row_start = rank_index * local_out;
8578 for token in 0..tokens {
8579 gathered[token * matrix.out_features + row_start
8580 ..token * matrix.out_features + row_start + local_out]
8581 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8582 }
8583 rank_outputs.push(output);
8584 }
8585 apply_macro(&mut gathered, matrix.macro_scale);
8586 Ok(ColumnParallelResult {
8587 gathered,
8588 rank_outputs,
8589 })
8590 }
8591
8592 pub fn row_parallel_nvfp4(
8595 &self,
8596 matrix: Nvfp4BlockMatrix<'_>,
8597 activations: &[f32],
8598 tokens: usize,
8599 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
8600 matrix.validate()?;
8601 validate_activations(activations, tokens, matrix.in_features)?;
8602 let tp = self.ranks.len();
8603 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8604 let mut rank_partials = Vec::with_capacity(tp);
8605 for (rank_index, rank) in self.ranks.iter().enumerate() {
8606 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
8607 let local_activations =
8608 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8609 let shard = Nvfp4BlockMatrix {
8610 codes: &codes,
8611 scales: &scales,
8612 macro_scale: matrix.macro_scale,
8613 out_features: matrix.out_features,
8614 in_features: local_in,
8615 };
8616 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
8617 for (sum, value) in reduced.iter_mut().zip(&partial) {
8618 *sum += *value;
8619 }
8620 rank_partials.push(partial);
8621 }
8622 apply_macro(&mut reduced, matrix.macro_scale);
8623 Ok(RowParallelResult {
8624 reduced,
8625 rank_partials,
8626 })
8627 }
8628
8629 pub fn upload_expert_nvfp4(
8630 &self,
8631 gate: Nvfp4BlockMatrix<'_>,
8632 up: Nvfp4BlockMatrix<'_>,
8633 down: Nvfp4BlockMatrix<'_>,
8634 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
8635 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8636 return Err("NVFP4 TP expert gate/up dimensions differ".into());
8637 }
8638 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8639 return Err(format!(
8640 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
8641 down.out_features, down.in_features, gate.out_features, gate.in_features
8642 )
8643 .into());
8644 }
8645 let tp = self.ranks.len();
8646 let mut gate_ranks = Vec::with_capacity(tp);
8647 let mut up_ranks = Vec::with_capacity(tp);
8648 let mut down_ranks = Vec::with_capacity(tp);
8649 for (rank_index, engine) in self.ranks.iter().enumerate() {
8650 gate_ranks.push(upload_rank_nvfp4(
8651 engine,
8652 nvfp4_column_shard(gate, tp, rank_index)?,
8653 )?);
8654 up_ranks.push(upload_rank_nvfp4(
8655 engine,
8656 nvfp4_column_shard(up, tp, rank_index)?,
8657 )?);
8658 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
8659 down_ranks.push(upload_rank_nvfp4(
8660 engine,
8661 Nvfp4BlockMatrix {
8662 codes: &codes,
8663 scales: &scales,
8664 macro_scale: down.macro_scale,
8665 out_features: down.out_features,
8666 in_features: local_in,
8667 },
8668 )?);
8669 }
8670 Ok(ResidentTpNvfp4Expert {
8671 gate: ResidentNvfp4ColumnParallel {
8672 ranks: gate_ranks,
8673 out_features: gate.out_features,
8674 in_features: gate.in_features,
8675 },
8676 up: ResidentNvfp4ColumnParallel {
8677 ranks: up_ranks,
8678 out_features: up.out_features,
8679 in_features: up.in_features,
8680 },
8681 down: ResidentNvfp4RowParallel {
8682 ranks: down_ranks,
8683 out_features: down.out_features,
8684 in_features: down.in_features,
8685 },
8686 input_width: gate.in_features,
8687 expert_width: gate.out_features,
8688 })
8689 }
8690
8691 fn column_parallel_resident_nvfp4(
8692 &self,
8693 matrix: &ResidentNvfp4ColumnParallel,
8694 activations: &[f32],
8695 tokens: usize,
8696 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8697 validate_activations(activations, tokens, matrix.in_features)?;
8698 let local_out = matrix.out_features / self.ranks.len();
8699 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8700 let mut macro_scale = None;
8701 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8702 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
8703 let row_start = rank_index * local_out;
8704 for token in 0..tokens {
8705 gathered[token * matrix.out_features + row_start
8706 ..token * matrix.out_features + row_start + local_out]
8707 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8708 }
8709 macro_scale = Some(shard.macro_scale);
8710 }
8711 apply_macro(
8712 &mut gathered,
8713 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
8714 );
8715 Ok(gathered)
8716 }
8717
8718 fn row_parallel_resident_nvfp4(
8719 &self,
8720 matrix: &ResidentNvfp4RowParallel,
8721 activations: &[f32],
8722 tokens: usize,
8723 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8724 validate_activations(activations, tokens, matrix.in_features)?;
8725 let tp = self.ranks.len();
8726 let local_in = matrix.in_features / tp;
8727 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8728 let mut macro_scale = None;
8729 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8730 if shard.in_features != local_in {
8731 return Err(format!(
8732 "NVFP4 resident row shard in_features {} != expected {local_in}",
8733 shard.in_features
8734 )
8735 .into());
8736 }
8737 let local_activations =
8738 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8739 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
8740 for (sum, value) in reduced.iter_mut().zip(&partial) {
8741 *sum += *value;
8742 }
8743 macro_scale = Some(shard.macro_scale);
8744 }
8745 apply_macro(
8746 &mut reduced,
8747 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
8748 );
8749 Ok(reduced)
8750 }
8751
8752 pub fn run_expert_nvfp4(
8753 &self,
8754 expert: &ResidentTpNvfp4Expert,
8755 input: &[f32],
8756 tokens: usize,
8757 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8758 validate_activations(input, tokens, expert.input_width)?;
8759 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
8760 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
8761 let activated: Vec<f32> = gate
8762 .iter()
8763 .zip(&up)
8764 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
8765 .collect();
8766 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
8767 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
8768 }
8769
8770 pub fn upload_tensor_parallel_nvfp4(
8772 &self,
8773 gate: Nvfp4ExpertBank<'_>,
8774 up: Nvfp4ExpertBank<'_>,
8775 down: Nvfp4ExpertBank<'_>,
8776 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
8777 gate.validate()?;
8778 up.validate()?;
8779 down.validate()?;
8780 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
8781 return Err("NVFP4 TP gate/up/down expert counts differ".into());
8782 }
8783 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8784 return Err("NVFP4 TP gate/up dimensions differ".into());
8785 }
8786 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8787 return Err(format!(
8788 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
8789 down.out_features, down.in_features, gate.out_features, gate.in_features
8790 )
8791 .into());
8792 }
8793 let tp = self.ranks.len();
8794 if gate.out_features % tp != 0 {
8795 return Err(format!(
8796 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
8797 gate.out_features
8798 )
8799 .into());
8800 }
8801 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
8802 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
8803 {
8804 return Err(format!(
8805 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
8806 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
8807 down.in_features
8808 )
8809 .into());
8810 }
8811 if tp > NVFP4_CANONICAL_ROW_SHARDS {
8812 return Err(format!(
8813 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
8814 ({NVFP4_CANONICAL_ROW_SHARDS})"
8815 )
8816 .into());
8817 }
8818
8819 let ep2 = step_nvfp4_ep2_on() && tp == 2;
8820 let mut gate_ranks = Vec::with_capacity(tp);
8821 let mut up_ranks = Vec::with_capacity(tp);
8822 let mut macros_gate_dev = Vec::with_capacity(tp);
8823 let mut macros_up_dev = Vec::with_capacity(tp);
8824 let mut macros_down_dev = Vec::with_capacity(tp);
8825 for (rank_index, engine) in self.ranks.iter().enumerate() {
8826 let _main = engine.gpu.enter_main()?;
8827 let mut gate_host: Vec<u8> = Vec::new();
8833 let mut up_host: Vec<u8> = Vec::new();
8834 let mut owned = 0usize;
8835 for expert in 0..gate.expert_count {
8836 if ep2 {
8837 if expert % 2 != rank_index {
8838 continue;
8839 }
8840 owned += 1;
8841 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
8842 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
8843 } else {
8844 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
8845 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
8846 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
8847 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
8848 }
8849 }
8850 let bank_experts = if ep2 { owned } else { gate.expert_count };
8851 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
8852 let up_expert_bytes = up_host.len() / bank_experts.max(1);
8853 let local_out = if ep2 {
8854 gate.out_features
8855 } else {
8856 gate.out_features / tp
8857 };
8858 gate_ranks.push(ResidentNvfp4ColumnBankRank {
8859 bank: engine.htod_bytes(&gate_host)?,
8860 expert_bytes: gate_expert_bytes,
8861 local_out,
8862 in_features: gate.in_features,
8863 row_bytes: nvfp4_row_bytes(gate.in_features),
8864 });
8865 up_ranks.push(ResidentNvfp4ColumnBankRank {
8866 bank: engine.htod_bytes(&up_host)?,
8867 expert_bytes: up_expert_bytes,
8868 local_out,
8869 in_features: up.in_features,
8870 row_bytes: nvfp4_row_bytes(up.in_features),
8871 });
8872 macros_gate_dev.push(engine.htod(gate.macros)?);
8873 macros_up_dev.push(engine.htod(up.macros)?);
8874 macros_down_dev.push(engine.htod(down.macros)?);
8875 }
8876 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
8880 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
8881 let device_rank = shard_index % tp;
8882 let engine = &self.ranks[device_rank];
8883 let _main = engine.gpu.enter_main()?;
8884 let mut down_host: Vec<u8> = Vec::new();
8885 let mut owned = 0usize;
8886 for expert in 0..down.expert_count {
8887 let down_matrix = down.expert(expert)?;
8888 if ep2 {
8889 if expert % 2 != device_rank {
8892 continue;
8893 }
8894 owned += 1;
8895 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
8896 } else {
8897 let (codes, scales, local_in) =
8898 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
8899 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
8900 codes: &codes,
8901 scales: &scales,
8902 macro_scale: down_matrix.macro_scale,
8903 out_features: down_matrix.out_features,
8904 in_features: local_in,
8905 }));
8906 }
8907 }
8908 let bank_experts = if ep2 { owned } else { down.expert_count };
8909 let down_expert_bytes = down_host.len() / bank_experts.max(1);
8910 let local_in = if ep2 {
8911 down.in_features
8912 } else {
8913 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
8914 };
8915 down_ranks.push(ResidentNvfp4RowBankRank {
8916 bank: engine.htod_bytes(&down_host)?,
8917 expert_bytes: down_expert_bytes,
8918 device_rank,
8919 out_features: down.out_features,
8920 local_in,
8921 row_bytes: nvfp4_row_bytes(local_in),
8922 });
8923 }
8924 Ok(ResidentNvfp4TensorParallel {
8925 gate: gate_ranks,
8926 up: up_ranks,
8927 down: down_ranks,
8928 macros_gate: gate.macros.to_vec(),
8929 macros_up: up.macros.to_vec(),
8930 macros_down: down.macros.to_vec(),
8931 macros_gate_dev,
8932 macros_up_dev,
8933 macros_down_dev,
8934 expert_count: gate.expert_count,
8935 input_width: gate.in_features,
8936 expert_width: gate.out_features,
8937 device_workspace: std::sync::Mutex::new(None),
8938 t2_workspace: std::sync::Mutex::new(None),
8939 ep2,
8940 })
8941 }
8942
8943 fn run_full_bank_expert_nvfp4(
8947 &self,
8948 ranks: &[ResidentNvfp4ColumnBankRank],
8949 macros: &[f32],
8950 expert: usize,
8951 input: &[f32],
8952 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8953 let owner = expert & 1;
8954 let slot = expert >> 1;
8955 let bank = ranks
8956 .get(owner)
8957 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
8958 let engine = &self.ranks[owner];
8959 let _main = engine.gpu.enter_main()?;
8960 let activations = engine.htod(input)?;
8961 let output = if nvfp4_bank_v2_on() {
8962 engine.qmatvec_nvfp4_fast_v2(
8963 &bank.expert(slot),
8964 &activations,
8965 1,
8966 bank.in_features,
8967 bank.local_out,
8968 bank.row_bytes,
8969 )?
8970 } else {
8971 engine.qmatvec_nvfp4_fast(
8972 &bank.expert(slot),
8973 &activations,
8974 1,
8975 bank.in_features,
8976 bank.local_out,
8977 bank.row_bytes,
8978 )?
8979 };
8980 let mut out = engine.dtoh(&output)?;
8981 apply_macro(&mut out, macros[expert]);
8982 Ok(out)
8983 }
8984
8985 fn run_full_down_expert_nvfp4(
8988 &self,
8989 shards: &[ResidentNvfp4RowBankRank],
8990 macros: &[f32],
8991 expert: usize,
8992 input: &[f32],
8993 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8994 let owner = expert & 1;
8995 let slot = expert >> 1;
8996 let shard = shards
8997 .get(owner)
8998 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
8999 let engine = &self.ranks[owner];
9000 let _main = engine.gpu.enter_main()?;
9001 let activations = engine.htod(input)?;
9002 let output = if nvfp4_bank_v2_on() {
9003 engine.qmatvec_nvfp4_fast_v2(
9004 &shard.expert(slot),
9005 &activations,
9006 1,
9007 shard.local_in,
9008 shard.out_features,
9009 shard.row_bytes,
9010 )?
9011 } else {
9012 engine.qmatvec_nvfp4_fast(
9013 &shard.expert(slot),
9014 &activations,
9015 1,
9016 shard.local_in,
9017 shard.out_features,
9018 shard.row_bytes,
9019 )?
9020 };
9021 let mut out = engine.dtoh(&output)?;
9022 apply_macro(&mut out, macros[expert]);
9023 Ok(out)
9024 }
9025
9026 fn run_column_bank_expert_nvfp4(
9027 &self,
9028 ranks: &[ResidentNvfp4ColumnBankRank],
9029 macros: &[f32],
9030 expert: usize,
9031 input: &[f32],
9032 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9033 let local_out = ranks
9034 .first()
9035 .ok_or("NVFP4 TP column bank has no ranks")?
9036 .local_out;
9037 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9038 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9039 let _main = engine.gpu.enter_main()?;
9040 let activations = engine.htod(input)?;
9041 let output = if nvfp4_bank_v2_on() {
9042 engine.qmatvec_nvfp4_fast_v2(
9043 &bank.expert(expert),
9044 &activations,
9045 1,
9046 bank.in_features,
9047 bank.local_out,
9048 bank.row_bytes,
9049 )?
9050 } else {
9051 engine.qmatvec_nvfp4_fast(
9052 &bank.expert(expert),
9053 &activations,
9054 1,
9055 bank.in_features,
9056 bank.local_out,
9057 bank.row_bytes,
9058 )?
9059 };
9060 let output = engine.dtoh(&output)?;
9061 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9062 }
9063 apply_macro(&mut gathered, macros[expert]);
9064 Ok(gathered)
9065 }
9066
9067 fn run_row_bank_expert_nvfp4(
9071 &self,
9072 shards: &[ResidentNvfp4RowBankRank],
9073 macros: &[f32],
9074 expert: usize,
9075 input: &[f32],
9076 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9077 let out_features = shards
9078 .first()
9079 .ok_or("NVFP4 TP row bank has no canonical shards")?
9080 .out_features;
9081 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
9082 let mut reduced = vec![0.0f32; out_features];
9083 for (shard_index, shard) in shards.iter().enumerate() {
9084 let engine = self
9085 .ranks
9086 .get(shard.device_rank)
9087 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
9088 let _main = engine.gpu.enter_main()?;
9089 let local_activations =
9090 activation_shard(input, 1, in_features, shards.len(), shard_index);
9091 let activations = engine.htod(&local_activations)?;
9092 let output = if nvfp4_bank_v2_on() {
9093 engine.qmatvec_nvfp4_fast_v2(
9094 &shard.expert(expert),
9095 &activations,
9096 1,
9097 shard.local_in,
9098 shard.out_features,
9099 shard.row_bytes,
9100 )?
9101 } else {
9102 engine.qmatvec_nvfp4_fast(
9103 &shard.expert(expert),
9104 &activations,
9105 1,
9106 shard.local_in,
9107 shard.out_features,
9108 shard.row_bytes,
9109 )?
9110 };
9111 let partial = engine.dtoh(&output)?;
9112 for (sum, value) in reduced.iter_mut().zip(&partial) {
9113 *sum += *value;
9114 }
9115 }
9116 apply_macro(&mut reduced, macros[expert]);
9117 Ok(reduced)
9118 }
9119
9120 pub fn upload_expert_parallel_nvfp4(
9124 &self,
9125 gate: Nvfp4ExpertBank<'_>,
9126 up: Nvfp4ExpertBank<'_>,
9127 down: Nvfp4ExpertBank<'_>,
9128 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
9129 gate.validate()?;
9130 up.validate()?;
9131 down.validate()?;
9132 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9133 return Err("NVFP4 EP gate/up/down expert counts differ".into());
9134 }
9135 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9136 return Err("NVFP4 EP gate/up dimensions differ".into());
9137 }
9138 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9139 return Err(format!(
9140 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9141 down.out_features, down.in_features, gate.out_features, gate.in_features
9142 )
9143 .into());
9144 }
9145 let world = self.ranks.len();
9146 if gate.expert_count % world != 0 {
9147 return Err(format!(
9148 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9149 gate.expert_count
9150 )
9151 .into());
9152 }
9153 let experts_per_rank = gate.expert_count / world;
9154 let mut ranks = Vec::with_capacity(world);
9155 for (rank_index, engine) in self.ranks.iter().enumerate() {
9156 let _main = engine.gpu.enter_main()?;
9157 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9158 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9159 let mut up_experts = Vec::with_capacity(experts_per_rank);
9160 let mut down_experts = Vec::with_capacity(experts_per_rank);
9161 for expert in expert_range.clone() {
9162 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9163 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9164 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9165 }
9166 ranks.push(ResidentNvfp4EpRank {
9167 gate: gate_experts,
9168 up: up_experts,
9169 down: down_experts,
9170 expert_range,
9171 });
9172 }
9173 Ok(ResidentNvfp4ExpertParallel {
9174 ranks,
9175 macros_gate: gate.macros.to_vec(),
9176 macros_up: up.macros.to_vec(),
9177 macros_down: down.macros.to_vec(),
9178 expert_count: gate.expert_count,
9179 input_width: gate.in_features,
9180 expert_width: gate.out_features,
9181 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9182 down_row_bytes: nvfp4_row_bytes(down.in_features),
9183 })
9184 }
9185
9186 #[allow(clippy::too_many_arguments)]
9192 pub fn run_routed_experts_nvfp4(
9193 &self,
9194 experts: &ResidentNvfp4ExpertParallel,
9195 input: &[f32],
9196 tokens: usize,
9197 selected: &[usize],
9198 route_weights: &[f32],
9199 experts_per_token: usize,
9200 activation_limit: Option<f32>,
9201 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9202 validate_activations(input, tokens, experts.input_width)?;
9203 let pairs = tokens
9204 .checked_mul(experts_per_token)
9205 .ok_or("NVFP4 EP route count overflow")?;
9206 if selected.len() != pairs || route_weights.len() != pairs {
9207 return Err(format!(
9208 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9209 {experts_per_token} ({pairs})",
9210 selected.len(),
9211 route_weights.len(),
9212 )
9213 .into());
9214 }
9215 if !route_weights.iter().all(|weight| weight.is_finite()) {
9216 return Err("NVFP4 EP route weights contain a non-finite value".into());
9217 }
9218 let experts_per_rank = experts.expert_count / experts.ranks.len();
9219 let mut output = vec![0.0f32; tokens * experts.input_width];
9220 for token in 0..tokens {
9221 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9222 for slot in 0..experts_per_token {
9223 let pair = token * experts_per_token + slot;
9224 let expert = selected[pair];
9225 if expert >= experts.expert_count {
9226 return Err(format!(
9227 "NVFP4 EP selected expert {expert} outside 0..{}",
9228 experts.expert_count
9229 )
9230 .into());
9231 }
9232 let owner = expert / experts_per_rank;
9233 let local = expert - owner * experts_per_rank;
9234 let rank = &experts.ranks[owner];
9235 let engine = &self.ranks[owner];
9236 let _main = engine.gpu.enter_main()?;
9237 let device_input = engine.htod(input_row)?;
9238 let gate_out = engine.qmatvec_nvfp4_fast(
9239 &rank.gate[local].slice(0..rank.gate[local].len()),
9240 &device_input,
9241 1,
9242 experts.input_width,
9243 experts.expert_width,
9244 experts.gate_row_bytes,
9245 )?;
9246 let up_out = engine.qmatvec_nvfp4_fast(
9247 &rank.up[local].slice(0..rank.up[local].len()),
9248 &device_input,
9249 1,
9250 experts.input_width,
9251 experts.expert_width,
9252 experts.gate_row_bytes,
9253 )?;
9254 let mut gate_host = engine.dtoh(&gate_out)?;
9255 let mut up_host = engine.dtoh(&up_out)?;
9256 apply_macro(&mut gate_host, experts.macros_gate[expert]);
9257 apply_macro(&mut up_host, experts.macros_up[expert]);
9258 let activated: Vec<f32> = gate_host
9259 .iter()
9260 .zip(&up_host)
9261 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
9262 .collect();
9263 let device_activated = engine.htod(&activated)?;
9264 let down_out = engine.qmatvec_nvfp4_fast(
9265 &rank.down[local].slice(0..rank.down[local].len()),
9266 &device_activated,
9267 1,
9268 experts.expert_width,
9269 experts.input_width,
9270 experts.down_row_bytes,
9271 )?;
9272 let mut down_host = engine.dtoh(&down_out)?;
9273 apply_macro(&mut down_host, experts.macros_down[expert]);
9274 let weight = route_weights[pair];
9275 for (sum, value) in output
9276 [token * experts.input_width..(token + 1) * experts.input_width]
9277 .iter_mut()
9278 .zip(down_host)
9279 {
9280 *sum += weight * value;
9281 }
9282 }
9283 }
9284 Ok(output)
9285 }
9286
9287 pub fn run_tensor_parallel_routes_nvfp4_device(
9301 &self,
9302 experts: &ResidentNvfp4TensorParallel,
9303 input: &[f32],
9304 selected: &[usize],
9305 route_weights: &[f32],
9306 experts_per_token: usize,
9307 activation_limit: Option<f32>,
9308 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9309 validate_activations(input, 1, experts.input_width)?;
9310 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9311 return Err(format!(
9312 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
9313 selected.len(),
9314 route_weights.len(),
9315 )
9316 .into());
9317 }
9318 if !route_weights.iter().all(|weight| weight.is_finite()) {
9319 return Err("NVFP4 device route weights contain a non-finite value".into());
9320 }
9321 let world = self.ranks.len();
9322 if world != NVFP4_CANONICAL_ROW_SHARDS {
9323 return Err(format!(
9324 "NVFP4 device routes require world == canonical shard grid \
9325 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9326 )
9327 .into());
9328 }
9329 let local_out = if experts.ep2 {
9330 experts.expert_width
9331 } else {
9332 experts.expert_width / world
9333 };
9334
9335 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9339 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9340 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9341 let started = timing.then(std::time::Instant::now);
9342
9343 let n_sel = experts_per_token;
9344 let mut workspace_guard = experts
9345 .device_workspace
9346 .lock()
9347 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9348 if workspace_guard.is_none() {
9349 let mut gate_out = Vec::with_capacity(world);
9350 let mut up_out = Vec::with_capacity(world);
9351 let mut act_q = Vec::with_capacity(world);
9352 let mut act_d = Vec::with_capacity(world);
9353 let mut sel = Vec::with_capacity(world);
9354 let mut partial = Vec::with_capacity(world);
9355 let mut accumulator = Vec::with_capacity(world);
9356 let mut combine_w = Vec::with_capacity(world);
9357 let mut route_w = Vec::with_capacity(world);
9358 let mut in_q = Vec::with_capacity(world);
9359 let mut in_d = Vec::with_capacity(world);
9360 let mut input = Vec::with_capacity(world);
9361 let mut ev_rank = Vec::with_capacity(world);
9362 let moe_direct = moe_direct_on();
9363 for (rank, engine) in self.ranks.iter().enumerate() {
9364 let _main = engine.gpu.enter_main()?;
9365 gate_out.push(engine.uninit(n_sel * local_out)?);
9366 up_out.push(engine.uninit(n_sel * local_out)?);
9367 act_q.push(engine.uninit_i8(n_sel * local_out)?);
9368 act_d.push(engine.uninit(n_sel * local_out / 32)?);
9369 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
9370 partial.push(engine.uninit(n_sel * experts.input_width)?);
9371 if moe_direct && rank != 0 {
9373 let root = &self.ranks[0];
9374 let _root_main = root.gpu.enter_main()?;
9375 accumulator.push(root.zeros(experts.input_width)?);
9376 } else {
9377 accumulator.push(engine.zeros(experts.input_width)?);
9378 }
9379 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9380 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9381 in_q.push(engine.uninit_i8(experts.input_width)?);
9382 in_d.push(engine.uninit(experts.input_width / 32)?);
9383 input.push(engine.uninit(experts.input_width)?);
9384 ev_rank.push(engine.ctx().new_event(None)?);
9385 }
9386 let root = &self.ranks[0];
9387 let _main = root.gpu.enter_main()?;
9388 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
9389 prestaged: false,
9390 rank1_routed: false,
9391 ev_input: None,
9392 fence_flags_raw: 0,
9393 fence_ticket: 0,
9394 gate_out,
9395 up_out,
9396 act_q,
9397 act_d,
9398 sel,
9399 partial,
9400 accumulator,
9401 combine_w,
9402 route_w,
9403 in_q,
9404 in_d,
9405 dev_route_e: None,
9406 in_stage_e: None,
9407 out_stage_e: None,
9408 routes_graph: None,
9409 raw_dev_route_e: None,
9410 raw_combine: None,
9411 raw_input: Vec::new(),
9412 raw_sel: Vec::new(),
9413 raw_route_w: Vec::new(),
9414 remote: root.uninit(experts.input_width)?,
9415 combined: root.uninit(experts.input_width)?,
9416 n_sel,
9417 input,
9418 ev_rank,
9419 ev_done: Some(root.ctx().new_event(None)?),
9420 ev_entry: None,
9421 });
9422 }
9423 let workspace = workspace_guard
9424 .as_mut()
9425 .expect("NVFP4 device routes workspace initialized above");
9426 if experts.ep2 {
9429 return Ok(vec![0.0f32; experts.input_width]);
9430 }
9431 if workspace.n_sel != n_sel {
9432 return Err(format!(
9433 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
9434 workspace.n_sel
9435 )
9436 .into());
9437 }
9438 for &expert in selected {
9439 if expert >= experts.expert_count {
9440 return Err(format!(
9441 "NVFP4 device selected expert {expert} outside 0..{}",
9442 experts.expert_count
9443 )
9444 .into());
9445 }
9446 }
9447 let sel_i32 = selected
9448 .iter()
9449 .map(|&expert| expert as i32)
9450 .collect::<Vec<_>>();
9451
9452 for (rank_index, engine) in self.ranks.iter().enumerate() {
9459 let _main = engine.gpu.enter_main()?;
9460 let device_input = engine.htod(input)?;
9461 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
9462 engine.quantize_q8_1_into(
9463 &device_input,
9464 1,
9465 experts.input_width,
9466 &mut in_q[rank_index],
9467 &mut in_d[rank_index],
9468 )?;
9469 }
9471 self.nvfp4_routes_batched_sweeps(
9472 experts,
9473 workspace,
9474 selected,
9475 route_weights,
9476 &sel_i32,
9477 local_out,
9478 n_sel,
9479 activation_limit,
9480 false,
9481 )?;
9482
9483 let root = &self.ranks[0];
9486 for engine in &self.ranks[1..] {
9487 let _main = engine.gpu.enter_main()?;
9488 engine.stream().synchronize()?;
9489 }
9490 let _main = root.gpu.enter_main()?;
9491 root.stream()
9492 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
9493 root.add(
9494 &workspace.accumulator[0],
9495 &workspace.remote,
9496 &mut workspace.combined,
9497 experts.input_width,
9498 )?;
9499 let output = root.dtoh(&workspace.combined)?;
9500 if let Some(started) = started {
9501 use std::sync::atomic::Ordering;
9502 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9503 + started.elapsed().as_nanos() as u64;
9504 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9505 if calls % 430 == 0 {
9506 eprintln!(
9507 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9508 ns as f64 / 1.0e6,
9509 ns as f64 / calls as f64 / 1.0e3,
9510 );
9511 }
9512 }
9513 Ok(output)
9514 }
9515
9516 #[allow(clippy::too_many_arguments)]
9521 fn nvfp4_routes_batched_sweeps(
9522 &self,
9523 experts: &ResidentNvfp4TensorParallel,
9524 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9525 selected: &[usize],
9526 route_weights: &[f32],
9527 sel_i32: &[i32],
9528 local_out: usize,
9529 n_sel: usize,
9530 activation_limit: Option<f32>,
9531 device_routed: bool,
9532 ) -> Result<(), Box<dyn std::error::Error>> {
9533 for rank_index in 0..self.ranks.len() {
9534 self.nvfp4_routes_batched_sweeps_rank(
9535 experts,
9536 workspace,
9537 selected,
9538 route_weights,
9539 sel_i32,
9540 local_out,
9541 n_sel,
9542 activation_limit,
9543 device_routed,
9544 rank_index,
9545 )?;
9546 }
9547 Ok(())
9548 }
9549
9550 #[allow(clippy::too_many_arguments)]
9553 fn nvfp4_routes_batched_sweeps_rank(
9554 &self,
9555 experts: &ResidentNvfp4TensorParallel,
9556 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9557 selected: &[usize],
9558 route_weights: &[f32],
9559 sel_i32: &[i32],
9560 local_out: usize,
9561 n_sel: usize,
9562 activation_limit: Option<f32>,
9563 device_routed: bool,
9564 rank_index: usize,
9565 ) -> Result<(), Box<dyn std::error::Error>> {
9566 {
9567 let engine = &self.ranks[rank_index];
9568 let _main = engine.gpu.enter_main()?;
9569 if experts.ep2 {
9574 if !device_routed {
9575 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
9576 }
9577 let gate_bank = &experts.gate[rank_index];
9578 let up_bank = &experts.up[rank_index];
9579 if gate_bank.local_out != experts.expert_width
9580 || gate_bank.expert_bytes != up_bank.expert_bytes
9581 {
9582 return Err("NVFP4 EP2 bank geometry drifted".into());
9583 }
9584 {
9585 let Nvfp4DeviceRoutesWorkspace {
9586 sel,
9587 gate_out,
9588 up_out,
9589 in_q,
9590 in_d,
9591 ..
9592 } = &mut *workspace;
9593 engine.qmatvec_nvfp4_sel_gu_ep_into(
9594 &gate_bank.bank,
9595 &up_bank.bank,
9596 &sel[rank_index],
9597 &in_q[rank_index],
9598 &in_d[rank_index],
9599 &mut gate_out[rank_index],
9600 &mut up_out[rank_index],
9601 n_sel,
9602 gate_bank.in_features,
9603 gate_bank.local_out,
9604 gate_bank.row_bytes,
9605 gate_bank.expert_bytes,
9606 rank_index,
9607 )?;
9608 }
9609 {
9610 let Nvfp4DeviceRoutesWorkspace {
9611 gate_out,
9612 up_out,
9613 sel,
9614 act_q,
9615 act_d,
9616 ..
9617 } = &mut *workspace;
9618 engine.silu_mul_scaled_q8_1_sel_ep_into(
9619 &gate_out[rank_index],
9620 &up_out[rank_index],
9621 &experts.macros_gate_dev[rank_index],
9622 &experts.macros_up_dev[rank_index],
9623 &sel[rank_index],
9624 activation_limit,
9625 &mut act_q[rank_index],
9626 &mut act_d[rank_index],
9627 local_out,
9628 n_sel,
9629 rank_index,
9630 )?;
9631 }
9632 let shard = &experts.down[rank_index];
9633 if shard.device_rank != rank_index || shard.local_in != local_out {
9634 return Err("NVFP4 EP2 down bank placement drifted".into());
9635 }
9636 {
9637 let Nvfp4DeviceRoutesWorkspace {
9638 sel,
9639 act_q,
9640 act_d,
9641 route_w,
9642 accumulator,
9643 ..
9644 } = &mut *workspace;
9645 engine.qmatvec_nvfp4_sel_down8_ep_into(
9646 &shard.bank,
9647 &sel[rank_index],
9648 &act_q[rank_index],
9649 &act_d[rank_index],
9650 &route_w[rank_index],
9651 &experts.macros_down_dev[rank_index],
9652 &mut accumulator[rank_index],
9653 n_sel,
9654 shard.local_in,
9655 shard.out_features,
9656 shard.row_bytes,
9657 shard.expert_bytes,
9658 local_out,
9659 local_out / 32,
9660 rank_index,
9661 )?;
9662 }
9663 return Ok(());
9664 }
9665 if !device_routed {
9666 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
9667 let folded = (0..n_sel)
9670 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
9671 .collect::<Vec<_>>();
9672 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
9673 engine.stream().memcpy_htod(&folded, &mut view)?;
9674 }
9675 let gate_bank = &experts.gate[rank_index];
9676 let up_bank = &experts.up[rank_index];
9677 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
9678 let gu_fused = nvfp4_bank_v2_on()
9681 && gate_bank.in_features == up_bank.in_features
9682 && gate_bank.local_out == up_bank.local_out
9683 && gate_bank.row_bytes == up_bank.row_bytes
9684 && gate_bank.expert_bytes == up_bank.expert_bytes;
9685 if gu_fused {
9686 let Nvfp4DeviceRoutesWorkspace {
9687 sel,
9688 gate_out,
9689 up_out,
9690 in_q,
9691 in_d,
9692 ..
9693 } = &mut *workspace;
9694 engine.qmatvec_nvfp4_sel_gu_into(
9695 &gate_bank.bank,
9696 &up_bank.bank,
9697 &sel[rank_index],
9698 &in_q[rank_index],
9699 &in_d[rank_index],
9700 &mut gate_out[rank_index],
9701 &mut up_out[rank_index],
9702 n_sel,
9703 gate_bank.in_features,
9704 gate_bank.local_out,
9705 gate_bank.row_bytes,
9706 gate_bank.expert_bytes,
9707 )?;
9708 } else {
9709 engine.qmatvec_nvfp4_sel_into(
9710 &gate_bank.bank,
9711 &workspace.sel[rank_index],
9712 aq,
9713 ad,
9714 &mut workspace.gate_out[rank_index],
9715 n_sel,
9716 gate_bank.in_features,
9717 gate_bank.local_out,
9718 gate_bank.row_bytes,
9719 gate_bank.expert_bytes,
9720 0,
9721 0,
9722 )?;
9723 engine.qmatvec_nvfp4_sel_into(
9724 &up_bank.bank,
9725 &workspace.sel[rank_index],
9726 aq,
9727 ad,
9728 &mut workspace.up_out[rank_index],
9729 n_sel,
9730 up_bank.in_features,
9731 up_bank.local_out,
9732 up_bank.row_bytes,
9733 up_bank.expert_bytes,
9734 0,
9735 0,
9736 )?;
9737 }
9738 {
9742 let Nvfp4DeviceRoutesWorkspace {
9743 gate_out,
9744 up_out,
9745 sel,
9746 act_q,
9747 act_d,
9748 ..
9749 } = &mut *workspace;
9750 engine.silu_mul_scaled_q8_1_sel_into(
9751 &gate_out[rank_index],
9752 &up_out[rank_index],
9753 &experts.macros_gate_dev[rank_index],
9754 &experts.macros_up_dev[rank_index],
9755 &sel[rank_index],
9756 activation_limit,
9757 &mut act_q[rank_index],
9758 &mut act_d[rank_index],
9759 local_out,
9760 n_sel,
9761 )?;
9762 }
9763 let shard = &experts.down[rank_index];
9764 if shard.device_rank != rank_index || shard.local_in != local_out {
9765 return Err(
9766 "NVFP4 device routes: down canonical shard placement drifted from \
9767 the gate/up column split"
9768 .into(),
9769 );
9770 }
9771 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
9778 if down8 {
9779 let Nvfp4DeviceRoutesWorkspace {
9780 sel,
9781 act_q,
9782 act_d,
9783 route_w,
9784 accumulator,
9785 ..
9786 } = &mut *workspace;
9787 engine.qmatvec_nvfp4_sel_down8_into(
9788 &shard.bank,
9789 &sel[rank_index],
9790 &act_q[rank_index],
9791 &act_d[rank_index],
9792 &route_w[rank_index],
9793 &experts.macros_down_dev[rank_index],
9794 &mut accumulator[rank_index],
9795 n_sel,
9796 shard.local_in,
9797 shard.out_features,
9798 shard.row_bytes,
9799 shard.expert_bytes,
9800 local_out,
9801 local_out / 32,
9802 )?;
9803 } else {
9804 let Nvfp4DeviceRoutesWorkspace {
9805 sel,
9806 act_q,
9807 act_d,
9808 partial,
9809 ..
9810 } = &mut *workspace;
9811 engine.qmatvec_nvfp4_sel_into(
9812 &shard.bank,
9813 &sel[rank_index],
9814 &act_q[rank_index],
9815 &act_d[rank_index],
9816 &mut partial[rank_index],
9817 n_sel,
9818 shard.local_in,
9819 shard.out_features,
9820 shard.row_bytes,
9821 shard.expert_bytes,
9822 local_out,
9823 local_out / 32,
9824 )?;
9825 }
9826 if !down8 {
9831 let Nvfp4DeviceRoutesWorkspace {
9832 partial,
9833 combine_w,
9834 route_w,
9835 sel,
9836 accumulator,
9837 ..
9838 } = &mut *workspace;
9839 if device_routed {
9840 engine.axpy_rows_seq_md_into(
9841 &partial[rank_index],
9842 &route_w[rank_index],
9843 &experts.macros_down_dev[rank_index],
9844 &sel[rank_index],
9845 &mut accumulator[rank_index],
9846 experts.input_width,
9847 n_sel,
9848 )?;
9849 } else {
9850 engine.axpy_rows_seq_into(
9851 &partial[rank_index],
9852 &combine_w[rank_index],
9853 &mut accumulator[rank_index],
9854 experts.input_width,
9855 n_sel,
9856 )?;
9857 }
9858 }
9859 }
9860 Ok(())
9861 }
9862
9863 pub fn run_tensor_parallel_routes_nvfp4_device_io(
9871 &self,
9872 experts: &ResidentNvfp4TensorParallel,
9873 e: &Engine,
9874 input_dev: &crate::CudaSlice<f32>,
9875 selected: &[usize],
9876 route_weights: &[f32],
9877 experts_per_token: usize,
9878 activation_limit: Option<f32>,
9879 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9880 if input_dev.len() != experts.input_width {
9881 return Err(format!(
9882 "NVFP4 device-io routes input {} != width {}",
9883 input_dev.len(),
9884 experts.input_width
9885 )
9886 .into());
9887 }
9888 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9889 return Err(format!(
9890 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
9891 selected.len(),
9892 route_weights.len(),
9893 )
9894 .into());
9895 }
9896 if !route_weights.iter().all(|weight| weight.is_finite()) {
9897 return Err("NVFP4 device route weights contain a non-finite value".into());
9898 }
9899 let world = self.ranks.len();
9900 if world != NVFP4_CANONICAL_ROW_SHARDS {
9901 return Err(format!(
9902 "NVFP4 device routes require world == canonical shard grid \
9903 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9904 )
9905 .into());
9906 }
9907 let local_out = experts.expert_width / world;
9908 let n_sel = experts_per_token;
9909
9910 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9911 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9912 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9913 let started = timing.then(std::time::Instant::now);
9914
9915 let mut workspace_guard = experts
9916 .device_workspace
9917 .lock()
9918 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9919 if workspace_guard.is_none() {
9920 drop(workspace_guard);
9921 let zero = vec![0.0f32; experts.input_width];
9924 let zero_sel = vec![0usize; n_sel];
9925 let zero_w = vec![0.0f32; n_sel];
9926 let _ = self.run_tensor_parallel_routes_nvfp4_device(
9927 experts,
9928 &zero,
9929 &zero_sel,
9930 &zero_w,
9931 n_sel,
9932 activation_limit,
9933 )?;
9934 workspace_guard = experts
9935 .device_workspace
9936 .lock()
9937 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9938 }
9939 let workspace = workspace_guard
9940 .as_mut()
9941 .expect("NVFP4 device routes workspace initialized above");
9942 if workspace.n_sel != n_sel {
9943 return Err(format!(
9944 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
9945 workspace.n_sel
9946 )
9947 .into());
9948 }
9949 for &expert in selected {
9950 if expert >= experts.expert_count {
9951 return Err(format!(
9952 "NVFP4 device selected expert {expert} outside 0..{}",
9953 experts.expert_count
9954 )
9955 .into());
9956 }
9957 }
9958 let sel_i32 = selected
9959 .iter()
9960 .map(|&expert| expert as i32)
9961 .collect::<Vec<_>>();
9962
9963 if let Some((_, device)) = workspace.ev_entry.as_ref() {
9967 if *device != e.ctx().ordinal() {
9968 return Err("NVFP4 device-io routes engine changed".into());
9969 }
9970 } else {
9971 let _main = e.gpu.enter_main()?;
9972 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
9973 }
9974 {
9975 let _main = e.gpu.enter_main()?;
9976 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
9977 ev_entry.record(&e.stream())?;
9978 }
9979 for (rank_index, engine) in self.ranks.iter().enumerate() {
9980 let _main = engine.gpu.enter_main()?;
9981 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
9982 engine.stream().wait(ev_entry)?;
9983 {
9984 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
9985 engine
9986 .stream()
9987 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
9988 }
9989 {
9990 let Nvfp4DeviceRoutesWorkspace {
9991 input, in_q, in_d, ..
9992 } = &mut *workspace;
9993 engine.quantize_q8_1_into(
9994 &input[rank_index],
9995 1,
9996 experts.input_width,
9997 &mut in_q[rank_index],
9998 &mut in_d[rank_index],
9999 )?;
10000 }
10001 }
10002 self.nvfp4_routes_batched_sweeps(
10003 experts,
10004 workspace,
10005 selected,
10006 route_weights,
10007 &sel_i32,
10008 local_out,
10009 n_sel,
10010 activation_limit,
10011 false,
10012 )?;
10013
10014 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10020 let _main = engine.gpu.enter_main()?;
10021 workspace.ev_rank[rank_index].record(&engine.stream())?;
10022 }
10023 if moe_direct_on() && self.ranks.len() == 2 {
10024 {
10031 let root = &self.ranks[0];
10032 let _main = root.gpu.enter_main()?;
10033 workspace
10034 .ev_done
10035 .as_ref()
10036 .expect("device routes done event")
10037 .record(&root.stream())?;
10038 }
10039 let _main = e.gpu.enter_main()?;
10040 e.stream().wait(
10041 workspace
10042 .ev_done
10043 .as_ref()
10044 .expect("device routes done event"),
10045 )?;
10046 for ev in workspace.ev_rank.iter().skip(1) {
10047 e.stream().wait(ev)?;
10048 }
10049 let mut output = e.uninit(experts.input_width)?;
10050 e.add(
10051 &workspace.accumulator[0],
10052 &workspace.accumulator[1],
10053 &mut output,
10054 experts.input_width,
10055 )?;
10056 let output = output;
10057 if let Some(started) = started {
10058 use std::sync::atomic::Ordering;
10059 let ns = TIMING_NS
10060 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10061 + started.elapsed().as_nanos() as u64;
10062 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10063 if calls % 430 == 0 {
10064 eprintln!(
10065 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10066 ns as f64 / 1.0e6,
10067 ns as f64 / calls as f64 / 1.0e3,
10068 );
10069 }
10070 }
10071 return Ok(output);
10072 }
10073 {
10074 let root = &self.ranks[0];
10075 let _main = root.gpu.enter_main()?;
10076 for ev in workspace.ev_rank.iter().skip(1) {
10077 root.stream().wait(ev)?;
10078 }
10079 root.stream()
10080 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10081 {
10082 let Nvfp4DeviceRoutesWorkspace {
10083 accumulator,
10084 remote,
10085 combined,
10086 ..
10087 } = &mut *workspace;
10088 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10089 }
10090 workspace
10091 .ev_done
10092 .as_ref()
10093 .expect("device routes done event")
10094 .record(&root.stream())?;
10095 }
10096 let output = {
10097 let _main = e.gpu.enter_main()?;
10098 e.stream().wait(
10099 workspace
10100 .ev_done
10101 .as_ref()
10102 .expect("device routes done event"),
10103 )?;
10104 let mut output = e.uninit(experts.input_width)?;
10107 e.stream().memcpy_dtod(
10108 &workspace.combined.slice(0..experts.input_width),
10109 &mut output.slice_mut(0..experts.input_width),
10110 )?;
10111 output
10112 };
10113 if let Some(started) = started {
10114 use std::sync::atomic::Ordering;
10115 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10116 + started.elapsed().as_nanos() as u64;
10117 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10118 if calls % 430 == 0 {
10119 eprintln!(
10120 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10121 ns as f64 / 1.0e6,
10122 ns as f64 / calls as f64 / 1.0e3,
10123 );
10124 }
10125 }
10126 Ok(output)
10127 }
10128
10129 #[allow(clippy::too_many_arguments)]
10135 pub fn nvfp4_routes_prestage(
10140 &self,
10141 experts: &ResidentNvfp4TensorParallel,
10142 e: &Engine,
10143 input_dev: &crate::CudaSlice<f32>,
10144 ) -> Result<bool, Box<dyn std::error::Error>> {
10145 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
10146 }
10147
10148 pub fn nvfp4_routes_prestage_with(
10154 &self,
10155 experts: &ResidentNvfp4TensorParallel,
10156 e: &Engine,
10157 input_dev: &crate::CudaSlice<f32>,
10158 rank1_router: impl FnOnce(
10159 &Engine,
10160 &crate::CudaSlice<f32>,
10161 &mut crate::CudaSlice<i32>,
10162 &mut crate::CudaSlice<f32>,
10163 ) -> Result<bool, Box<dyn std::error::Error>>,
10164 ) -> Result<bool, Box<dyn std::error::Error>> {
10165 if !routes_prestage_on() || step_tp_graph_enabled()? {
10166 return Ok(false);
10167 }
10168 if input_dev.len() != experts.input_width {
10169 return Err("NVFP4 prestage input width mismatch".into());
10170 }
10171 let mut workspace_guard = experts
10172 .device_workspace
10173 .lock()
10174 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10175 let Some(workspace) = workspace_guard.as_mut() else {
10176 return Ok(false);
10177 };
10178 if workspace.ev_input.is_none() {
10179 let _main = e.gpu.enter_main()?;
10180 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10181 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
10182 return Err("NVFP4 prestage engine changed".into());
10183 }
10184 {
10185 let _main = e.gpu.enter_main()?;
10186 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10187 ev.record(&e.stream())?;
10188 }
10189 for (rank_index, engine) in self.ranks.iter().enumerate() {
10190 let _main = engine.gpu.enter_main()?;
10191 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10192 engine.stream().wait(ev)?;
10193 {
10194 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10195 engine
10196 .stream()
10197 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10198 }
10199 {
10200 let Nvfp4DeviceRoutesWorkspace {
10201 input, in_q, in_d, ..
10202 } = &mut *workspace;
10203 engine.quantize_q8_1_into(
10204 &input[rank_index],
10205 1,
10206 experts.input_width,
10207 &mut in_q[rank_index],
10208 &mut in_d[rank_index],
10209 )?;
10210 }
10211 }
10212 if self.ranks.len() == 2 {
10213 let rank1 = &self.ranks[1];
10214 let _r1 = rank1.gpu.enter_main()?;
10215 let Nvfp4DeviceRoutesWorkspace {
10216 input,
10217 sel,
10218 route_w,
10219 ..
10220 } = &mut *workspace;
10221 let (in1, rest_sel) = (&input[1], &mut sel[1]);
10222 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
10223 workspace.rank1_routed = true;
10224 }
10225 }
10226 workspace.prestaged = true;
10227 Ok(true)
10228 }
10229
10230 #[allow(clippy::too_many_arguments)]
10241 pub fn run_tensor_parallel_routes_nvfp4_device_routed_t2(
10242 &self,
10243 experts: &ResidentNvfp4TensorParallel,
10244 e: &Engine,
10245 z2: &crate::CudaSlice<f32>,
10246 sel_d: &crate::CudaSlice<i32>,
10247 w_d: &crate::CudaSlice<f32>,
10248 n_sel_col: usize,
10249 activation_limit: Option<f32>,
10250 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10251 let world = self.ranks.len();
10252 if world != NVFP4_CANONICAL_ROW_SHARDS {
10253 return Err("NVFP4 t2 routes require the canonical 2-shard grid".into());
10254 }
10255 let width = experts.input_width;
10256 let n_sel = 2 * n_sel_col;
10257 if z2.len() < 2 * width || sel_d.len() < n_sel || w_d.len() < n_sel {
10258 return Err("NVFP4 t2 routes geometry".into());
10259 }
10260 if !nvfp4_bank_v2_on() {
10261 return Err("NVFP4 t2 routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
10262 }
10263 let local_out = experts.expert_width / world;
10264 let mut guard = experts
10265 .t2_workspace
10266 .lock()
10267 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
10268 if guard.as_ref().is_none_or(|ws| ws.n_sel != n_sel) {
10269 let mut input2 = Vec::new();
10270 let mut in_q2 = Vec::new();
10271 let mut in_d2 = Vec::new();
10272 let mut sel2 = Vec::new();
10273 let mut route_w2 = Vec::new();
10274 let mut gate_out2 = Vec::new();
10275 let mut up_out2 = Vec::new();
10276 let mut act_q2 = Vec::new();
10277 let mut act_d2 = Vec::new();
10278 let mut partial2 = Vec::new();
10279 let mut acc_a = Vec::new();
10280 let mut acc_b = Vec::new();
10281 let mut ev_rank = Vec::new();
10282 for engine in &self.ranks {
10283 let _m = engine.gpu.enter_main()?;
10284 input2.push(engine.uninit(2 * width)?);
10285 in_q2.push(engine.alloc_i8_uninit(2 * width)?);
10286 in_d2.push(engine.uninit(2 * (width / 32))?);
10287 sel2.push(engine.htod_i32(&vec![0i32; n_sel])?);
10288 route_w2.push(engine.uninit(n_sel)?);
10289 gate_out2.push(engine.uninit(n_sel * local_out)?);
10290 up_out2.push(engine.uninit(n_sel * local_out)?);
10291 act_q2.push(engine.alloc_i8_uninit(n_sel * local_out)?);
10292 act_d2.push(engine.uninit(n_sel * (local_out / 32))?);
10293 partial2.push(engine.uninit(n_sel * width)?);
10294 acc_a.push(engine.uninit(width)?);
10295 acc_b.push(engine.uninit(width)?);
10296 ev_rank.push(engine.ctx().new_event(None)?);
10297 }
10298 let root = &self.ranks[0];
10299 let (peer_a, peer_b, omix_a, omix_b, ev_root) = {
10300 let _m = root.gpu.enter_main()?;
10301 (
10302 root.uninit(width)?,
10303 root.uninit(width)?,
10304 root.uninit(width)?,
10305 root.uninit(width)?,
10306 root.ctx().new_event(None)?,
10307 )
10308 };
10309 let ev_entry = {
10310 let _m = e.gpu.enter_main()?;
10311 e.ctx().new_event(None)?
10312 };
10313 *guard = Some(Nvfp4T2Workspace {
10314 input2,
10315 in_q2,
10316 in_d2,
10317 sel2,
10318 route_w2,
10319 gate_out2,
10320 up_out2,
10321 act_q2,
10322 act_d2,
10323 partial2,
10324 acc_a,
10325 acc_b,
10326 peer_a,
10327 peer_b,
10328 omix_a,
10329 omix_b,
10330 ev_entry,
10331 ev_rank,
10332 ev_root,
10333 n_sel,
10334 e_device: e.ctx().ordinal(),
10335 });
10336 }
10337 let ws = guard.as_mut().expect("armed above");
10338 if ws.e_device != e.ctx().ordinal() {
10339 return Err("NVFP4 t2 routes engine changed".into());
10340 }
10341 {
10342 let _main = e.gpu.enter_main()?;
10343 ws.ev_entry.record(&e.stream())?;
10344 }
10345 for rank in 0..world {
10346 let engine = &self.ranks[rank];
10347 let _main = engine.gpu.enter_main()?;
10348 engine.stream().wait(&ws.ev_entry)?;
10349 {
10350 let mut dst = ws.input2[rank].slice_mut(0..2 * width);
10351 engine
10352 .stream()
10353 .memcpy_dtod(&z2.slice(0..2 * width), &mut dst)?;
10354 }
10355 {
10356 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
10357 engine
10358 .stream()
10359 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10360 }
10361 {
10362 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
10363 engine
10364 .stream()
10365 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10366 }
10367 {
10368 let Nvfp4T2Workspace {
10369 input2,
10370 in_q2,
10371 in_d2,
10372 ..
10373 } = &mut *ws;
10374 engine.quantize_q8_1_into(
10375 &input2[rank],
10376 2,
10377 width,
10378 &mut in_q2[rank],
10379 &mut in_d2[rank],
10380 )?;
10381 }
10382 let gate_bank = &experts.gate[rank];
10383 let up_bank = &experts.up[rank];
10384 if gate_bank.in_features != up_bank.in_features
10385 || gate_bank.local_out != up_bank.local_out
10386 || gate_bank.row_bytes != up_bank.row_bytes
10387 || gate_bank.expert_bytes != up_bank.expert_bytes
10388 {
10389 return Err("NVFP4 t2 routes need matched gate/up bank geometry".into());
10390 }
10391 {
10392 let Nvfp4T2Workspace {
10393 sel2,
10394 in_q2,
10395 in_d2,
10396 gate_out2,
10397 up_out2,
10398 ..
10399 } = &mut *ws;
10400 engine.qmatvec_nvfp4_sel_gu_tcol_into(
10401 &gate_bank.bank,
10402 &up_bank.bank,
10403 &sel2[rank],
10404 &in_q2[rank],
10405 &in_d2[rank],
10406 &mut gate_out2[rank],
10407 &mut up_out2[rank],
10408 n_sel,
10409 n_sel_col,
10410 gate_bank.in_features,
10411 gate_bank.local_out,
10412 gate_bank.row_bytes,
10413 gate_bank.expert_bytes,
10414 width,
10415 width / 32,
10416 )?;
10417 }
10418 {
10419 let Nvfp4T2Workspace {
10420 gate_out2,
10421 up_out2,
10422 sel2,
10423 act_q2,
10424 act_d2,
10425 ..
10426 } = &mut *ws;
10427 engine.silu_mul_scaled_q8_1_sel_into(
10428 &gate_out2[rank],
10429 &up_out2[rank],
10430 &experts.macros_gate_dev[rank],
10431 &experts.macros_up_dev[rank],
10432 &sel2[rank],
10433 activation_limit,
10434 &mut act_q2[rank],
10435 &mut act_d2[rank],
10436 local_out,
10437 n_sel,
10438 )?;
10439 }
10440 let shard = &experts.down[rank];
10441 if shard.device_rank != rank || shard.local_in != local_out {
10442 return Err("NVFP4 t2 routes: down shard placement drifted".into());
10443 }
10444 {
10445 let Nvfp4T2Workspace {
10446 sel2,
10447 act_q2,
10448 act_d2,
10449 partial2,
10450 ..
10451 } = &mut *ws;
10452 engine.qmatvec_nvfp4_sel_into(
10453 &shard.bank,
10454 &sel2[rank],
10455 &act_q2[rank],
10456 &act_d2[rank],
10457 &mut partial2[rank],
10458 n_sel,
10459 shard.local_in,
10460 shard.out_features,
10461 shard.row_bytes,
10462 shard.expert_bytes,
10463 local_out,
10464 local_out / 32,
10465 )?;
10466 }
10467 {
10468 let Nvfp4T2Workspace {
10469 partial2,
10470 route_w2,
10471 sel2,
10472 acc_a,
10473 acc_b,
10474 ..
10475 } = &mut *ws;
10476 engine.axpy_rows_seq_md_off_into(
10477 &partial2[rank],
10478 &route_w2[rank],
10479 &experts.macros_down_dev[rank],
10480 &sel2[rank],
10481 &mut acc_a[rank],
10482 width,
10483 n_sel_col,
10484 0,
10485 )?;
10486 engine.axpy_rows_seq_md_off_into(
10487 &partial2[rank],
10488 &route_w2[rank],
10489 &experts.macros_down_dev[rank],
10490 &sel2[rank],
10491 &mut acc_b[rank],
10492 width,
10493 n_sel_col,
10494 n_sel_col,
10495 )?;
10496 }
10497 if rank != 0 {
10498 ws.ev_rank[rank].record(&engine.stream())?;
10499 }
10500 }
10501 let root = &self.ranks[0];
10502 {
10503 let _main = root.gpu.enter_main()?;
10504 for ev in ws.ev_rank.iter().skip(1) {
10505 root.stream().wait(ev)?;
10506 }
10507 {
10508 let Nvfp4T2Workspace {
10509 acc_a,
10510 acc_b,
10511 peer_a,
10512 peer_b,
10513 omix_a,
10514 omix_b,
10515 ..
10516 } = &mut *ws;
10517 {
10518 let mut dst = peer_a.slice_mut(0..width);
10519 root.stream()
10520 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
10521 }
10522 {
10523 let mut dst = peer_b.slice_mut(0..width);
10524 root.stream()
10525 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
10526 }
10527 root.add(&acc_a[0], peer_a, omix_a, width)?;
10528 root.add(&acc_b[0], peer_b, omix_b, width)?;
10529 }
10530 ws.ev_root.record(&root.stream())?;
10531 }
10532 let _main = e.gpu.enter_main()?;
10533 e.stream().wait(&ws.ev_root)?;
10534 let mut out = e.uninit(2 * width)?;
10535 e.stream()
10536 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
10537 e.stream().memcpy_dtod(
10538 &ws.omix_b.slice(0..width),
10539 &mut out.slice_mut(width..2 * width),
10540 )?;
10541 Ok(out)
10542 }
10543
10544 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
10545 &self,
10546 experts: &ResidentNvfp4TensorParallel,
10547 e: &Engine,
10548 input_dev: &crate::CudaSlice<f32>,
10549 sel_d: &crate::CudaSlice<i32>,
10550 w_d: &crate::CudaSlice<f32>,
10551 experts_per_token: usize,
10552 activation_limit: Option<f32>,
10553 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10554 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10555 experts,
10556 e,
10557 input_dev,
10558 sel_d,
10559 w_d,
10560 experts_per_token,
10561 activation_limit,
10562 || Ok(()),
10563 )
10564 }
10565
10566 #[allow(clippy::too_many_arguments)]
10572 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10573 &self,
10574 experts: &ResidentNvfp4TensorParallel,
10575 e: &Engine,
10576 input_dev: &crate::CudaSlice<f32>,
10577 sel_d: &crate::CudaSlice<i32>,
10578 w_d: &crate::CudaSlice<f32>,
10579 experts_per_token: usize,
10580 activation_limit: Option<f32>,
10581 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10582 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10583 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10584 experts,
10585 e,
10586 input_dev,
10587 sel_d,
10588 w_d,
10589 experts_per_token,
10590 activation_limit,
10591 pre_join,
10592 None,
10593 )
10594 }
10595
10596 #[allow(clippy::too_many_arguments)]
10601 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10602 &self,
10603 experts: &ResidentNvfp4TensorParallel,
10604 e: &Engine,
10605 input_dev: &crate::CudaSlice<f32>,
10606 sel_d: &crate::CudaSlice<i32>,
10607 w_d: &crate::CudaSlice<f32>,
10608 experts_per_token: usize,
10609 activation_limit: Option<f32>,
10610 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10611 post_add: Option<(u64, u64)>,
10612 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10613 if input_dev.len() != experts.input_width {
10614 return Err(format!(
10615 "NVFP4 device-routed input {} != width {}",
10616 input_dev.len(),
10617 experts.input_width
10618 )
10619 .into());
10620 }
10621 let n_sel = experts_per_token;
10622 if sel_d.len() < n_sel || w_d.len() < n_sel {
10623 return Err(format!(
10624 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
10625 sel_d.len(),
10626 w_d.len()
10627 )
10628 .into());
10629 }
10630 let world = self.ranks.len();
10631 if world != NVFP4_CANONICAL_ROW_SHARDS {
10632 return Err(format!(
10633 "NVFP4 device routes require world == canonical shard grid \
10634 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10635 )
10636 .into());
10637 }
10638 let local_out = if experts.ep2 {
10639 experts.expert_width
10640 } else {
10641 experts.expert_width / world
10642 };
10643
10644 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10645 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10646 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10647 let started = timing.then(std::time::Instant::now);
10648
10649 let mut workspace_guard = experts
10650 .device_workspace
10651 .lock()
10652 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10653 if workspace_guard.is_none() {
10654 drop(workspace_guard);
10655 let zero = vec![0.0f32; experts.input_width];
10656 let zero_sel = vec![0usize; n_sel];
10657 let zero_w = vec![0.0f32; n_sel];
10658 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10659 experts,
10660 &zero,
10661 &zero_sel,
10662 &zero_w,
10663 n_sel,
10664 activation_limit,
10665 )?;
10666 workspace_guard = experts
10667 .device_workspace
10668 .lock()
10669 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10670 }
10671 let workspace = workspace_guard
10672 .as_mut()
10673 .expect("NVFP4 device routes workspace initialized above");
10674 if workspace.n_sel != n_sel {
10675 return Err(format!(
10676 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10677 workspace.n_sel
10678 )
10679 .into());
10680 }
10681
10682 if step_tp_graph_enabled()? {
10687 if experts.ep2 {
10688 return Err(
10689 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
10690 co-gated; unset one"
10691 .into(),
10692 );
10693 }
10694 if workspace.dev_route_e.is_none() {
10695 let _main = e.gpu.enter_main()?;
10696 workspace.dev_route_e = Some((
10697 e.htod_i32(&vec![0i32; n_sel])?,
10698 e.htod(&vec![0.0f32; n_sel])?,
10699 ));
10700 }
10701 if workspace.in_stage_e.is_none() {
10702 let _main = e.gpu.enter_main()?;
10703 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
10704 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
10705 }
10706 if workspace.routes_graph.is_none() {
10707 let graph = self.nvfp4_routes_build_graph(
10708 experts,
10709 workspace,
10710 local_out,
10711 n_sel,
10712 activation_limit,
10713 )?;
10714 workspace.routes_graph = Some(graph);
10715 eprintln!(
10716 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
10717 children=3 updates=none performance_claim=false"
10718 );
10719 }
10720 let output = {
10721 let _main = e.gpu.enter_main()?;
10722 {
10723 let (sel_e, w_e) = workspace
10724 .dev_route_e
10725 .as_mut()
10726 .expect("device route staging set above");
10727 {
10728 let mut dst = sel_e.slice_mut(0..n_sel);
10729 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10730 }
10731 {
10732 let mut dst = w_e.slice_mut(0..n_sel);
10733 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10734 }
10735 }
10736 {
10737 let in_stage = workspace
10738 .in_stage_e
10739 .as_mut()
10740 .expect("graph staging set above");
10741 let mut dst = in_stage.slice_mut(0..experts.input_width);
10742 e.stream()
10743 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
10744 }
10745 unsafe {
10746 let r = cudarc::driver::sys::cuGraphLaunch(
10747 workspace
10748 .routes_graph
10749 .as_ref()
10750 .expect("routes graph built above")
10751 .exec,
10752 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
10753 );
10754 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
10755 return Err(format!("routes graph launch: {r:?}").into());
10756 }
10757 }
10758 let mut output = e.uninit(experts.input_width)?;
10759 {
10760 let out_stage = workspace
10761 .out_stage_e
10762 .as_ref()
10763 .expect("graph staging set above");
10764 e.stream().memcpy_dtod(
10765 &out_stage.slice(0..experts.input_width),
10766 &mut output.slice_mut(0..experts.input_width),
10767 )?;
10768 }
10769 output
10770 };
10771 if let Some(started) = started {
10772 use std::sync::atomic::Ordering;
10773 let ns = TIMING_NS
10774 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10775 + started.elapsed().as_nanos() as u64;
10776 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10777 if calls % 430 == 0 {
10778 eprintln!(
10779 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10780 ns as f64 / 1.0e6,
10781 ns as f64 / calls as f64 / 1.0e3,
10782 );
10783 }
10784 }
10785 return Ok(output);
10786 }
10787
10788 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10792 if *device != e.ctx().ordinal() {
10793 return Err("NVFP4 device-routed routes engine changed".into());
10794 }
10795 } else {
10796 let _main = e.gpu.enter_main()?;
10797 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10798 }
10799 if workspace.dev_route_e.is_none() {
10800 let _main = e.gpu.enter_main()?;
10801 workspace.dev_route_e = Some((
10802 e.htod_i32(&vec![0i32; n_sel])?,
10803 e.htod(&vec![0.0f32; n_sel])?,
10804 ));
10805 }
10806 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
10812 let e_device = e.ctx().ordinal();
10813 let rank1_routed_peek = workspace.rank1_routed;
10815 let stage_needed = !mirror
10816 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
10817 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
10818 });
10819 {
10820 let _main = e.gpu.enter_main()?;
10821 if stage_needed {
10822 let (sel_e, w_e) = workspace
10823 .dev_route_e
10824 .as_mut()
10825 .expect("device route staging set above");
10826 {
10827 let mut dst = sel_e.slice_mut(0..n_sel);
10828 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10829 }
10830 {
10831 let mut dst = w_e.slice_mut(0..n_sel);
10832 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10833 }
10834 }
10835 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10836 ev_entry.record(&e.stream())?;
10837 }
10838 let prestaged = std::mem::take(&mut workspace.prestaged);
10841 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
10842 for (rank_index, engine) in self.ranks.iter().enumerate() {
10843 let _main = engine.gpu.enter_main()?;
10844 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10845 engine.stream().wait(ev_entry)?;
10846 if !prestaged {
10847 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10848 engine
10849 .stream()
10850 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10851 }
10852 if !(rank1_routed && rank_index == 1) {
10853 let same_dev = engine.ctx().ordinal() == e_device;
10857 if mirror {
10858 let Nvfp4DeviceRoutesWorkspace {
10861 sel,
10862 route_w,
10863 dev_route_e,
10864 ..
10865 } = &mut *workspace;
10866 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
10867 if same_dev {
10868 (sel_d, w_d)
10869 } else {
10870 let (sel_e, w_e) = dev_route_e
10871 .as_ref()
10872 .expect("device route staging set above");
10873 (sel_e, w_e)
10874 };
10875 engine.moe_sel_w_mirror(
10876 src_sel,
10877 src_w,
10878 &mut sel[rank_index],
10879 &mut route_w[rank_index],
10880 n_sel,
10881 )?;
10882 } else {
10883 let (sel_e, w_e) = workspace
10884 .dev_route_e
10885 .as_ref()
10886 .expect("device route staging set above");
10887 {
10888 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
10889 engine
10890 .stream()
10891 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
10892 }
10893 {
10894 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
10895 engine
10896 .stream()
10897 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
10898 }
10899 }
10900 }
10901 if !prestaged {
10902 let Nvfp4DeviceRoutesWorkspace {
10903 input, in_q, in_d, ..
10904 } = &mut *workspace;
10905 engine.quantize_q8_1_into(
10906 &input[rank_index],
10907 1,
10908 experts.input_width,
10909 &mut in_q[rank_index],
10910 &mut in_d[rank_index],
10911 )?;
10912 }
10913 }
10914 self.nvfp4_routes_batched_sweeps(
10915 experts,
10916 workspace,
10917 &[],
10918 &[],
10919 &[],
10920 local_out,
10921 n_sel,
10922 activation_limit,
10923 true,
10924 )?;
10925
10926 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10929 let _main = engine.gpu.enter_main()?;
10930 workspace.ev_rank[rank_index].record(&engine.stream())?;
10931 }
10932 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
10935 let mut ticket = 0u32;
10936 if memops {
10937 use cudarc::driver::sys;
10938 if workspace.fence_flags_raw == 0 {
10939 let root = &self.ranks[0];
10940 let _main = root.gpu.enter_main()?;
10941 let mut ptr: sys::CUdeviceptr = 0;
10942 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
10943 if r != sys::CUresult::CUDA_SUCCESS {
10944 return Err(format!("fence flag alloc: {r:?}").into());
10945 }
10946 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
10947 if r != sys::CUresult::CUDA_SUCCESS {
10948 return Err(format!("fence flag memset: {r:?}").into());
10949 }
10950 workspace.fence_flags_raw = ptr as u64;
10951 }
10952 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
10953 ticket = workspace.fence_ticket;
10954 let base = workspace.fence_flags_raw;
10955 if fence_rank1_on() {
10961 let peer = &self.ranks[1];
10962 let _pmain = peer.gpu.enter_main()?;
10963 peer.ring_flag_raw(base, ticket)?;
10964 }
10965 {
10966 let root = &self.ranks[0];
10967 let _main = root.gpu.enter_main()?;
10968 let r = unsafe {
10969 sys::cuStreamWriteValue32_v2(
10970 root.stream().cu_stream() as sys::CUstream,
10971 (base + 4) as sys::CUdeviceptr,
10972 ticket,
10973 0,
10974 )
10975 };
10976 if r != sys::CUresult::CUDA_SUCCESS {
10977 return Err(format!("fence write root: {r:?}").into());
10978 }
10979 }
10980 }
10981 pre_join()?;
10984
10985 if moe_direct_on() && self.ranks.len() == 2 {
10986 let _main = e.gpu.enter_main()?;
10993 if memops {
10994 use cudarc::driver::sys;
10995 let base = workspace.fence_flags_raw;
10996 let r = unsafe {
10997 sys::cuStreamWaitValue32_v2(
10998 e.stream().cu_stream() as sys::CUstream,
10999 (base + 4) as sys::CUdeviceptr,
11000 ticket,
11001 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11002 )
11003 };
11004 if r != sys::CUresult::CUDA_SUCCESS {
11005 return Err(format!("fence wait: {r:?}").into());
11006 }
11007 if fence_rank1_on() {
11008 let r = unsafe {
11010 sys::cuStreamWaitValue32_v2(
11011 e.stream().cu_stream() as sys::CUstream,
11012 base as sys::CUdeviceptr,
11013 ticket,
11014 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11015 )
11016 };
11017 if r != sys::CUresult::CUDA_SUCCESS {
11018 return Err(format!("fence wait rank1: {r:?}").into());
11019 }
11020 } else {
11021 for ev in workspace.ev_rank.iter().skip(1) {
11022 e.stream().wait(ev)?;
11023 }
11024 }
11025 } else {
11026 {
11027 let root = &self.ranks[0];
11028 let _rmain = root.gpu.enter_main()?;
11029 workspace
11030 .ev_done
11031 .as_ref()
11032 .expect("device routes done event")
11033 .record(&root.stream())?;
11034 }
11035 e.stream().wait(
11036 workspace
11037 .ev_done
11038 .as_ref()
11039 .expect("device routes done event"),
11040 )?;
11041 for ev in workspace.ev_rank.iter().skip(1) {
11042 e.stream().wait(ev)?;
11043 }
11044 }
11045 let mut output = e.uninit(experts.input_width)?;
11046 if let Some((sh_raw, scale_raw)) = post_add {
11047 e.add3_raw(
11050 &workspace.accumulator[0],
11051 &workspace.accumulator[1],
11052 sh_raw,
11053 scale_raw,
11054 &mut output,
11055 experts.input_width,
11056 )?;
11057 } else {
11058 e.add(
11059 &workspace.accumulator[0],
11060 &workspace.accumulator[1],
11061 &mut output,
11062 experts.input_width,
11063 )?;
11064 }
11065 let output = output;
11066 if let Some(started) = started {
11067 use std::sync::atomic::Ordering;
11068 let ns = TIMING_NS
11069 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11070 + started.elapsed().as_nanos() as u64;
11071 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11072 if calls % 430 == 0 {
11073 eprintln!(
11074 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11075 ns as f64 / 1.0e6,
11076 ns as f64 / calls as f64 / 1.0e3,
11077 );
11078 }
11079 }
11080 return Ok(output);
11081 }
11082 {
11083 let root = &self.ranks[0];
11084 let _main = root.gpu.enter_main()?;
11085 for ev in workspace.ev_rank.iter().skip(1) {
11086 root.stream().wait(ev)?;
11087 }
11088 root.stream()
11089 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11090 {
11091 let Nvfp4DeviceRoutesWorkspace {
11092 accumulator,
11093 remote,
11094 combined,
11095 ..
11096 } = &mut *workspace;
11097 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11098 }
11099 workspace
11100 .ev_done
11101 .as_ref()
11102 .expect("device routes done event")
11103 .record(&root.stream())?;
11104 }
11105 let output = {
11106 let _main = e.gpu.enter_main()?;
11107 e.stream().wait(
11108 workspace
11109 .ev_done
11110 .as_ref()
11111 .expect("device routes done event"),
11112 )?;
11113 let mut output = e.uninit(experts.input_width)?;
11116 e.stream().memcpy_dtod(
11117 &workspace.combined.slice(0..experts.input_width),
11118 &mut output.slice_mut(0..experts.input_width),
11119 )?;
11120 output
11121 };
11122 if let Some(started) = started {
11123 use std::sync::atomic::Ordering;
11124 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11125 + started.elapsed().as_nanos() as u64;
11126 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11127 if calls % 430 == 0 {
11128 eprintln!(
11129 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11130 ns as f64 / 1.0e6,
11131 ns as f64 / calls as f64 / 1.0e3,
11132 );
11133 }
11134 }
11135 Ok(output)
11136 }
11137
11138 pub(crate) fn decode_v2_finish_root_fused(
11142 &self,
11143 ws: &mut StepTpDecodeV2Ws,
11144 ) -> Result<(), Box<dyn std::error::Error>> {
11145 let root = &self.ranks[0];
11146 let _main = root.gpu.enter_main()?;
11147 if ws.raw_peer_partial != 0 {
11148 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
11150 } else {
11151 root.stream()
11152 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
11153 }
11154 {
11155 let StepTpDecodeV2Ws {
11156 o_partials,
11157 peer_partial,
11158 reduce_a,
11159 o_out,
11160 ..
11161 } = &mut *ws;
11162 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
11163 }
11164 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
11165 if shadows {
11166 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
11169 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
11170 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
11171 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
11172 }
11173 if shadows && ws.raw_peer_partial != 0 {
11174 raw_copy_bytes(
11175 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
11176 ws.raw_k1,
11177 ws.local_kv_dim * 4,
11178 root,
11179 )?;
11180 raw_copy_bytes(
11181 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
11182 ws.raw_v1,
11183 ws.local_kv_dim * 4,
11184 root,
11185 )?;
11186 } else if shadows {
11187 let start = ws.local_kv_dim;
11188 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
11189 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
11190 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
11191 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
11192 }
11193 if ws.raw_mixed_stage_e != 0 {
11194 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
11197 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
11198 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
11199 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
11200 }
11201 Ok(())
11202 }
11203
11204 pub(crate) fn decode_v2_arm_token_mirrors(
11207 &self,
11208 ws: &mut StepTpDecodeV2Ws,
11209 mixed_stage_e: u64,
11210 shadow_stage_e: (u64, u64),
11211 ) -> Result<(), Box<dyn std::error::Error>> {
11212 use cudarc::driver::DevicePtr;
11213 let root = &self.ranks[0];
11214 let _main = root.gpu.enter_main()?;
11215 let stream = root.stream();
11216 let (a, _g) = ws.reduce_a.device_ptr(&stream);
11217 ws.raw_reduce_a = a as u64;
11218 ws.raw_mixed_stage_e = mixed_stage_e;
11219 ws.raw_shadow_stage_e = shadow_stage_e;
11220 Ok(())
11221 }
11222
11223 fn nvfp4_routes_build_graph(
11229 &self,
11230 experts: &ResidentNvfp4TensorParallel,
11231 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11232 local_out: usize,
11233 n_sel: usize,
11234 activation_limit: Option<f32>,
11235 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
11236 use cudarc::driver::DevicePtr;
11237 use cudarc::driver::sys;
11238 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
11239 if r == sys::CUresult::CUDA_SUCCESS {
11240 Ok(())
11241 } else {
11242 Err(format!("{what}: {r:?}").into())
11243 }
11244 }
11245 let world = self.ranks.len();
11246 if world != 2 {
11247 return Err("routes graph door is built for the TP2 pair".into());
11248 }
11249 let width = experts.input_width;
11250
11251 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
11253 let stream = engine.stream();
11254 let (ptr, _g) = buf.device_ptr(&stream);
11255 ptr as u64
11256 };
11257 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
11258 let stream = engine.stream();
11259 let (ptr, _g) = buf.device_ptr(&stream);
11260 ptr as u64
11261 };
11262 let (sel_e, w_e) = workspace
11263 .dev_route_e
11264 .as_ref()
11265 .expect("device route staging set before graph build");
11266 let root_engine = &self.ranks[0];
11267 let p_in_stage = ptr_f32(
11268 workspace.in_stage_e.as_ref().expect("graph staging"),
11269 root_engine,
11270 );
11271 let p_out_stage = ptr_f32(
11272 workspace.out_stage_e.as_ref().expect("graph staging"),
11273 root_engine,
11274 );
11275 let p_sel_e = ptr_i32(sel_e, root_engine);
11276 let p_w_e = ptr_f32(w_e, root_engine);
11277 let p_input: Vec<u64> = (0..world)
11278 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
11279 .collect();
11280 let p_sel: Vec<u64> = (0..world)
11281 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
11282 .collect();
11283 let p_route_w: Vec<u64> = (0..world)
11284 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
11285 .collect();
11286 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
11287 let p_remote = ptr_f32(&workspace.remote, root_engine);
11288 let p_combined = ptr_f32(&workspace.combined, root_engine);
11289
11290 let raw_copy = |dst: u64,
11291 src: u64,
11292 bytes: usize,
11293 engine: &Engine|
11294 -> Result<(), Box<dyn std::error::Error>> {
11295 unsafe {
11296 cu_try(
11297 sys::cuMemcpyAsync(
11298 dst as sys::CUdeviceptr,
11299 src as sys::CUdeviceptr,
11300 bytes,
11301 engine.stream().cu_stream() as sys::CUstream,
11302 ),
11303 "routes graph cuMemcpyAsync",
11304 )
11305 }
11306 };
11307
11308 let mut children = Vec::with_capacity(3);
11309 for rank in 0..world {
11310 let engine = &self.ranks[rank];
11311 let _main = engine.gpu.enter_main()?;
11312 let (child, _retained) = engine.capture_graph_retained(|_| {
11313 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
11314 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
11315 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
11316 {
11317 let Nvfp4DeviceRoutesWorkspace {
11318 input, in_q, in_d, ..
11319 } = &mut *workspace;
11320 engine.quantize_q8_1_into(
11321 &input[rank],
11322 1,
11323 width,
11324 &mut in_q[rank],
11325 &mut in_d[rank],
11326 )?;
11327 }
11328 self.nvfp4_routes_batched_sweeps_rank(
11329 experts,
11330 workspace,
11331 &[],
11332 &[],
11333 &[],
11334 local_out,
11335 n_sel,
11336 activation_limit,
11337 true,
11338 rank,
11339 )?;
11340 Ok(())
11341 })?;
11342 children.push(child);
11343 }
11344 {
11345 let root = &self.ranks[0];
11346 let _main = root.gpu.enter_main()?;
11347 let (child, _retained) = root.capture_graph_retained(|_| {
11348 raw_copy(p_remote, p_acc1, width * 4, root)?;
11349 {
11350 let Nvfp4DeviceRoutesWorkspace {
11351 accumulator,
11352 remote,
11353 combined,
11354 ..
11355 } = &mut *workspace;
11356 root.add(&accumulator[0], remote, combined, width)?;
11357 }
11358 raw_copy(p_out_stage, p_combined, width * 4, root)?;
11359 Ok(())
11360 })?;
11361 children.push(child);
11362 }
11363
11364 let mut parent: sys::CUgraph = std::ptr::null_mut();
11365 unsafe {
11366 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
11367 }
11368 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
11369 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
11370 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
11371 unsafe {
11372 cu_try(
11373 sys::cuGraphAddChildGraphNode(
11374 &mut n0,
11375 parent,
11376 std::ptr::null(),
11377 0,
11378 children[0].cu_graph(),
11379 ),
11380 "routes child r0",
11381 )?;
11382 cu_try(
11383 sys::cuGraphAddChildGraphNode(
11384 &mut n1,
11385 parent,
11386 std::ptr::null(),
11387 0,
11388 children[1].cu_graph(),
11389 ),
11390 "routes child r1",
11391 )?;
11392 let deps = [n0, n1];
11393 cu_try(
11394 sys::cuGraphAddChildGraphNode(
11395 &mut n2,
11396 parent,
11397 deps.as_ptr(),
11398 2,
11399 children[2].cu_graph(),
11400 ),
11401 "routes child root",
11402 )?;
11403 }
11404 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
11405 unsafe {
11406 cu_try(
11407 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
11408 "routes instantiate",
11409 )?;
11410 }
11411 Ok(RoutesGraph {
11412 exec,
11413 parent,
11414 _children: children,
11415 })
11416 }
11417
11418 #[allow(clippy::too_many_arguments)]
11422 pub(crate) fn routes_rank_section(
11423 &self,
11424 experts: &ResidentNvfp4TensorParallel,
11425 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11426 raw_input_src: u64,
11427 local_out: usize,
11428 n_sel: usize,
11429 activation_limit: Option<f32>,
11430 rank_index: usize,
11431 ) -> Result<(), Box<dyn std::error::Error>> {
11432 let engine = &self.ranks[rank_index];
11433 {
11434 let _main = engine.gpu.enter_main()?;
11435 let (sel_e_ptr, w_e_ptr) = workspace
11437 .raw_dev_route_e
11438 .ok_or("routes rank section requires armed staging pointers")?;
11439 raw_copy_bytes(
11440 workspace.raw_input[rank_index],
11441 raw_input_src,
11442 experts.input_width * 4,
11443 engine,
11444 )?;
11445 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
11446 raw_copy_bytes(
11447 workspace.raw_route_w[rank_index],
11448 w_e_ptr,
11449 n_sel * 4,
11450 engine,
11451 )?;
11452 {
11453 let Nvfp4DeviceRoutesWorkspace {
11454 input, in_q, in_d, ..
11455 } = &mut *workspace;
11456 engine.quantize_q8_1_into(
11457 &input[rank_index],
11458 1,
11459 experts.input_width,
11460 &mut in_q[rank_index],
11461 &mut in_d[rank_index],
11462 )?;
11463 }
11464 }
11465 self.nvfp4_routes_batched_sweeps_rank(
11466 experts,
11467 workspace,
11468 &[],
11469 &[],
11470 &[],
11471 local_out,
11472 n_sel,
11473 activation_limit,
11474 true,
11475 rank_index,
11476 )
11477 }
11478
11479 pub(crate) fn routes_root_section(
11482 &self,
11483 experts: &ResidentNvfp4TensorParallel,
11484 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11485 ) -> Result<(), Box<dyn std::error::Error>> {
11486 let root = &self.ranks[0];
11487 let _main = root.gpu.enter_main()?;
11488 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
11489 .raw_combine
11490 .ok_or("routes root section requires armed combine pointers")?;
11491 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
11492 {
11493 let Nvfp4DeviceRoutesWorkspace {
11494 accumulator,
11495 remote,
11496 combined,
11497 ..
11498 } = &mut *workspace;
11499 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11500 }
11501 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
11502 Ok(())
11503 }
11504
11505 pub(crate) fn routes_arm_raw(
11508 &self,
11509 experts: &ResidentNvfp4TensorParallel,
11510 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11511 ) -> Result<(), Box<dyn std::error::Error>> {
11512 use cudarc::driver::DevicePtr;
11513 if workspace.raw_dev_route_e.is_some() {
11514 return Ok(());
11515 }
11516 let _ = experts;
11517 let (sel_e, w_e) = workspace
11518 .dev_route_e
11519 .as_ref()
11520 .ok_or("routes staging not armed")?;
11521 let root = &self.ranks[0];
11522 {
11523 let _main = root.gpu.enter_main()?;
11524 let stream = root.stream();
11525 let (a, _g) = sel_e.device_ptr(&stream);
11526 let (b, _g) = w_e.device_ptr(&stream);
11527 workspace.raw_dev_route_e = Some((a as u64, b as u64));
11528 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
11529 let (d, _g) = workspace.remote.device_ptr(&stream);
11530 let (f, _g) = workspace.combined.device_ptr(&stream);
11531 let out_stage = workspace
11532 .out_stage_e
11533 .as_ref()
11534 .ok_or("routes out stage not armed")?;
11535 let (g_, _g) = out_stage.device_ptr(&stream);
11536 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
11537 }
11538 for rank in 0..self.ranks.len() {
11539 let engine = &self.ranks[rank];
11540 let _main = engine.gpu.enter_main()?;
11541 let stream = engine.stream();
11542 let (a, _g) = workspace.input[rank].device_ptr(&stream);
11543 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
11544 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
11545 workspace.raw_input.push(a as u64);
11546 workspace.raw_sel.push(b as u64);
11547 workspace.raw_route_w.push(c as u64);
11548 }
11549 Ok(())
11550 }
11551
11552 pub fn run_tensor_parallel_routes_nvfp4(
11556 &self,
11557 experts: &ResidentNvfp4TensorParallel,
11558 input: &[f32],
11559 tokens: usize,
11560 selected: &[usize],
11561 route_weights: &[f32],
11562 experts_per_token: usize,
11563 activation_limit: Option<f32>,
11564 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11565 validate_activations(input, tokens, experts.input_width)?;
11566 let pairs = tokens
11567 .checked_mul(experts_per_token)
11568 .ok_or("NVFP4 TP route count overflow")?;
11569 if selected.len() != pairs || route_weights.len() != pairs {
11570 return Err(format!(
11571 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
11572 {experts_per_token} ({pairs})",
11573 selected.len(),
11574 route_weights.len(),
11575 )
11576 .into());
11577 }
11578 if !route_weights.iter().all(|weight| weight.is_finite()) {
11579 return Err("NVFP4 TP route weights contain a non-finite value".into());
11580 }
11581
11582 let mut output = vec![0.0f32; tokens * experts.input_width];
11583 for token in 0..tokens {
11584 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11585 for slot in 0..experts_per_token {
11586 let pair = token * experts_per_token + slot;
11587 let expert = selected[pair];
11588 if expert >= experts.expert_count {
11589 return Err(format!(
11590 "NVFP4 TP selected expert {expert} outside 0..{}",
11591 experts.expert_count
11592 )
11593 .into());
11594 }
11595 let gate = if experts.ep2 {
11601 self.run_full_bank_expert_nvfp4(
11602 &experts.gate,
11603 &experts.macros_gate,
11604 expert,
11605 input_row,
11606 )?
11607 } else {
11608 self.run_column_bank_expert_nvfp4(
11609 &experts.gate,
11610 &experts.macros_gate,
11611 expert,
11612 input_row,
11613 )?
11614 };
11615 let up = if experts.ep2 {
11616 self.run_full_bank_expert_nvfp4(
11617 &experts.up,
11618 &experts.macros_up,
11619 expert,
11620 input_row,
11621 )?
11622 } else {
11623 self.run_column_bank_expert_nvfp4(
11624 &experts.up,
11625 &experts.macros_up,
11626 expert,
11627 input_row,
11628 )?
11629 };
11630 let activated: Vec<f32> = gate
11631 .iter()
11632 .zip(&up)
11633 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11634 .collect();
11635 debug_assert_eq!(activated.len(), experts.expert_width);
11636 let down = if experts.ep2 {
11637 self.run_full_down_expert_nvfp4(
11638 &experts.down,
11639 &experts.macros_down,
11640 expert,
11641 &activated,
11642 )?
11643 } else {
11644 self.run_row_bank_expert_nvfp4(
11645 &experts.down,
11646 &experts.macros_down,
11647 expert,
11648 &activated,
11649 )?
11650 };
11651 let weight = route_weights[pair];
11652 for (sum, value) in output
11653 [token * experts.input_width..(token + 1) * experts.input_width]
11654 .iter_mut()
11655 .zip(down)
11656 {
11657 *sum += weight * value;
11658 }
11659 }
11660 }
11661 Ok(output)
11662 }
11663}
11664
11665#[cfg(test)]
11666mod tests {
11667 use super::*;
11668
11669 #[test]
11670 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
11671 let limit = Some(7.0);
11672 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
11673 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
11674 assert!(
11675 step_expert_activation_host(-20.0, 9.0, limit).abs()
11676 < step_expert_activation_host(-20.0, 9.0, None).abs()
11677 );
11678 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
11679 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
11680 assert!(validate_step_expert_activation_limit(limit).is_ok());
11681 }
11682
11683 #[test]
11684 fn moe_residual_host_preserves_official_add_order() {
11685 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
11686 assert_eq!(output, [0.0]);
11687 assert_eq!(
11688 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
11689 "MoE residual lengths residual=1 routed=2 shared=1"
11690 );
11691 }
11692
11693 #[test]
11694 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
11695 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
11696 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
11697 assert_eq!(owners.len(), 4);
11698 for (rank, owner) in owners.iter().enumerate() {
11699 assert_eq!(owner.rank, rank);
11700 assert_eq!(owner.selected, vec![0, 36]);
11701 assert_eq!(owner.token_rows, vec![0, 0]);
11702 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
11703 }
11704 }
11705
11706 #[test]
11707 fn expert_owner_routes_validate_geometry_and_selected_experts() {
11708 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
11709 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
11710 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
11711 assert!(error.contains("outside 0..288"));
11712 }
11713
11714 #[test]
11715 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
11716 let selected = [
11717 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
11718 ];
11719 assert_eq!(
11720 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
11721 16
11722 );
11723 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
11724 assert_eq!(
11725 owners
11726 .iter()
11727 .map(|owner| owner.selected.len())
11728 .collect::<Vec<_>>(),
11729 vec![2, 4, 6, 4]
11730 );
11731 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
11732 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
11733 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
11734 }
11735
11736 #[test]
11737 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
11738 let owner0 = [0usize, 3];
11739 let owner1 = [1usize, 2];
11740 let owners = [owner0.as_slice(), owner1.as_slice()];
11741 assert_eq!(
11742 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
11743 .unwrap(),
11744 WeightedRouteCombineShape {
11745 pairs: 4,
11746 max_pairs: 12,
11747 }
11748 );
11749 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
11750 assert!(
11751 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
11752 .is_err()
11753 );
11754 assert!(
11755 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
11756 .is_err()
11757 );
11758 assert!(
11759 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
11760 .is_err()
11761 );
11762 }
11763
11764 #[test]
11765 fn native_p2p_door_is_strict_and_default_off() {
11766 assert!(!parse_step_tp_native_p2p(None).unwrap());
11767 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
11768 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
11769 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
11770 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
11771 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
11772 }
11773
11774 #[test]
11775 fn bulk_p2p_door_is_strict_and_default_off() {
11776 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
11777 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
11778 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
11779 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
11780 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
11781 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
11782 }
11783
11784 #[test]
11785 fn ep_device_arithmetic_door_is_strict_and_default_off() {
11786 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
11787 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
11788 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
11789 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
11790 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
11791 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
11792 }
11793
11794 #[test]
11795 fn f32_mirror_door_is_strict_and_default_off() {
11796 assert!(!parse_step_tp_f32_mirror(None).unwrap());
11797 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
11798 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
11799 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
11800 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
11801 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
11802 }
11803
11804 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
11805 let codes = (0..out_features * in_features)
11806 .map(|index| (index % 251) as u8)
11807 .collect();
11808 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
11809 .map(|index| index as f32 + 1.0)
11810 .collect();
11811 (codes, scales)
11812 }
11813
11814 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
11815 (0..out_features * in_features)
11816 .flat_map(|value| (value as u16).to_le_bytes())
11817 .collect()
11818 }
11819
11820 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
11821 bytes
11822 .chunks_exact(2)
11823 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
11824 .collect()
11825 }
11826
11827 #[test]
11828 fn bf16_matrix_rejects_wrong_byte_count() {
11829 let bytes = vec![0u8; 4 * 4 * 2 - 1];
11830 let matrix = Bf16Matrix {
11831 bytes: &bytes,
11832 out_features: 4,
11833 in_features: 4,
11834 };
11835 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
11836 }
11837
11838 #[test]
11839 fn replicated_device_rows_require_exact_rank_local_shapes() {
11840 assert_eq!(
11841 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
11842 12_288
11843 );
11844 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
11845 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
11846 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
11847 assert!(
11848 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
11849 );
11850 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
11851 }
11852
11853 #[test]
11854 fn replicated_device_row_refresh_requires_exact_root_source() {
11855 assert_eq!(
11856 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
11857 12_288
11858 );
11859 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
11860 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
11861 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
11862 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
11863 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
11864 }
11865
11866 #[test]
11867 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
11868 for tp in [1, 2, 4, 8] {
11869 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
11870 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
11871 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
11872 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
11873 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
11874 }
11875 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
11876 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
11877 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
11878 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
11879 }
11880
11881 #[test]
11882 fn cache_rows_split_by_token_then_rank() {
11883 let rows = (0u8..24).collect::<Vec<_>>();
11884 assert_eq!(
11885 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
11886 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
11887 );
11888 assert_eq!(
11889 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
11890 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
11891 );
11892 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
11893 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
11894 }
11895
11896 #[test]
11897 fn bf16_column_shard_preserves_contiguous_output_rows() {
11898 let bytes = bf16_matrix_bytes(4, 4);
11899 let matrix = Bf16Matrix {
11900 bytes: &bytes,
11901 out_features: 4,
11902 in_features: 4,
11903 };
11904 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
11905 assert_eq!(shard.out_features, 2);
11906 assert_eq!(shard.in_features, 4);
11907 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
11908 }
11909
11910 #[test]
11911 fn bf16_row_shard_preserves_each_input_column_window() {
11912 let bytes = bf16_matrix_bytes(3, 4);
11913 let matrix = Bf16Matrix {
11914 bytes: &bytes,
11915 out_features: 3,
11916 in_features: 4,
11917 };
11918 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
11919 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
11920 }
11921
11922 #[test]
11923 fn bf16_row_block_preserves_global_column_order() {
11924 let bytes = bf16_matrix_bytes(3, 8);
11925 let matrix = Bf16Matrix {
11926 bytes: &bytes,
11927 out_features: 3,
11928 in_features: 8,
11929 };
11930 let block = bf16_row_block(matrix, 2, 3).unwrap();
11931 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
11932 }
11933
11934 #[test]
11935 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
11936 let (codes, scales) = matrix(1280, 4096);
11937 let matrix = E4m3BlockMatrix {
11938 codes: &codes,
11939 scales: &scales,
11940 out_features: 1280,
11941 in_features: 4096,
11942 };
11943 let shard = column_shard(matrix, 2, 1).unwrap();
11944 assert_eq!(shard.out_features, 640);
11945 assert_eq!(shard.codes, &codes[640 * 4096..]);
11946 assert_eq!(shard.scales, &scales[5 * 32..]);
11947 }
11948
11949 #[test]
11950 fn row_shard_preserves_each_weight_and_scale_column_window() {
11951 let (codes, scales) = matrix(4096, 1280);
11952 let matrix = E4m3BlockMatrix {
11953 codes: &codes,
11954 scales: &scales,
11955 out_features: 4096,
11956 in_features: 1280,
11957 };
11958 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
11959 assert_eq!(shard_codes.len(), 4096 * 640);
11960 assert_eq!(&shard_codes[..640], &codes[640..1280]);
11961 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
11962 assert_eq!(shard_scales.len(), 32 * 5);
11963 assert_eq!(&shard_scales[..5], &scales[5..10]);
11964 assert_eq!(&shard_scales[5..10], &scales[15..20]);
11965 }
11966
11967 #[test]
11968 fn activation_shards_keep_token_rows_separate() {
11969 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
11970 assert_eq!(
11971 activation_shard(&activations, 2, 8, 2, 1),
11972 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
11973 );
11974 }
11975
11976 #[test]
11977 fn expert_bank_selects_expert_major_code_and_scale_planes() {
11978 let expert_count = 2;
11979 let out_features = 128;
11980 let in_features = 128;
11981 let code_stride = out_features * in_features;
11982 let codes: Vec<u8> = (0..expert_count * code_stride)
11983 .map(|index| (index % 251) as u8)
11984 .collect();
11985 let scales = vec![1.0f32, 2.0];
11986 let bank = E4m3ExpertBank {
11987 codes: &codes,
11988 scales: &scales,
11989 expert_count,
11990 out_features,
11991 in_features,
11992 };
11993 bank.validate().unwrap();
11994 let expert = bank.expert(1).unwrap();
11995 assert_eq!(expert.codes, &codes[code_stride..]);
11996 assert_eq!(expert.scales, &[2.0]);
11997 }
11998
11999 #[test]
12000 fn expert_bank_rejects_non_positive_scale() {
12001 let codes = vec![0u8; 128 * 128];
12002 let scales = vec![0.0f32];
12003 let bank = E4m3ExpertBank {
12004 codes: &codes,
12005 scales: &scales,
12006 expert_count: 1,
12007 out_features: 128,
12008 in_features: 128,
12009 };
12010 assert!(bank.validate().unwrap_err().contains("non-positive"));
12011 }
12012
12013 #[test]
12014 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
12015 let expert_count = 2;
12016 let out_features = 256;
12017 let in_features = 128;
12018 let code_stride = out_features * in_features;
12019 let scale_stride = 2;
12020 let codes = (0..expert_count * code_stride)
12021 .map(|index| (index % 251) as u8)
12022 .collect::<Vec<_>>();
12023 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12024 let bank = E4m3ExpertBank {
12025 codes: &codes,
12026 scales: &scales,
12027 expert_count,
12028 out_features,
12029 in_features,
12030 };
12031
12032 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
12033 assert_eq!(rank.out_features, 128);
12034 assert_eq!(rank.in_features, 128);
12035 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12036 assert_eq!(rank.scales, vec![11.0, 21.0]);
12037 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
12038 assert_eq!(
12039 &rank.codes[128 * 128..],
12040 &codes[code_stride + 128 * 128..2 * code_stride]
12041 );
12042 assert_eq!(scale_stride, scales.len() / expert_count);
12043 }
12044
12045 #[test]
12046 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
12047 let expert_count = 2;
12048 let out_features = 128;
12049 let in_features = 256;
12050 let code_stride = out_features * in_features;
12051 let codes = (0..expert_count * code_stride)
12052 .map(|index| (index % 251) as u8)
12053 .collect::<Vec<_>>();
12054 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12055 let bank = E4m3ExpertBank {
12056 codes: &codes,
12057 scales: &scales,
12058 expert_count,
12059 out_features,
12060 in_features,
12061 };
12062
12063 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12064 assert_eq!(rank.out_features, 128);
12065 assert_eq!(rank.in_features, 128);
12066 assert_eq!(rank.k_blocks, Some(1));
12067 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12068 assert_eq!(rank.scales, vec![11.0, 21.0]);
12069 assert_eq!(&rank.codes[..128], &codes[128..256]);
12070 assert_eq!(
12071 &rank.codes[128 * 128..128 * 128 + 128],
12072 &codes[code_stride + 128..code_stride + 256]
12073 );
12074 }
12075
12076 #[test]
12077 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
12078 let expert_count = 2;
12079 let out_features = 256;
12080 let in_features = 512;
12081 let code_stride = out_features * in_features;
12082 let mut codes = vec![0u8; expert_count * code_stride];
12083 for expert in 0..expert_count {
12084 for row in 0..out_features {
12085 for block in 0..4 {
12086 let value = (expert * 80 + block * 16 + row % 16) as u8;
12087 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
12088 codes[start..start + FP8_BLOCK].fill(value);
12089 }
12090 }
12091 }
12092 let scales = vec![
12093 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,
12094 112.0, 113.0, 114.0,
12095 ];
12096 let bank = E4m3ExpertBank {
12097 codes: &codes,
12098 scales: &scales,
12099 expert_count,
12100 out_features,
12101 in_features,
12102 };
12103
12104 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12105 assert_eq!(rank.out_features, out_features);
12106 assert_eq!(rank.in_features, 256);
12107 assert_eq!(rank.k_blocks, Some(2));
12108 assert_eq!(rank.code_stride, out_features * 256);
12109 assert_eq!(rank.scale_stride, 4);
12110 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
12111 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
12112
12113 let block_stride = out_features * FP8_BLOCK;
12114 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
12115 assert!(
12116 rank.codes[block_stride..block_stride + FP8_BLOCK]
12117 .iter()
12118 .all(|&code| code == 48)
12119 );
12120 assert!(
12121 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
12122 .iter()
12123 .all(|&code| code == 112)
12124 );
12125 assert!(
12126 rank.codes
12127 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
12128 .iter()
12129 .all(|&code| code == 128)
12130 );
12131 }
12132
12133 #[test]
12134 fn step_ep_layer_specs_are_literal_and_fail_closed() {
12135 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
12136 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
12137 assert_eq!(
12138 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
12139 vec![StepEpLayerSpec {
12140 layer: 24,
12141 devices: vec![1, 2],
12142 }]
12143 );
12144 assert_eq!(
12145 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
12146 vec![
12147 StepEpLayerSpec {
12148 layer: 24,
12149 devices: vec![1, 2],
12150 },
12151 StepEpLayerSpec {
12152 layer: 25,
12153 devices: vec![1, 2],
12154 },
12155 StepEpLayerSpec {
12156 layer: 31,
12157 devices: vec![0, 2],
12158 },
12159 ]
12160 );
12161 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
12162 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
12163 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
12164 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
12165 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
12166 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12167 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
12168 }
12169
12170 #[test]
12171 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
12172 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
12173 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
12174 assert_eq!(
12175 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
12176 vec![
12177 StepTpLayerSpec {
12178 layer: 24,
12179 devices: vec![1, 2],
12180 },
12181 StepTpLayerSpec {
12182 layer: 25,
12183 devices: vec![1, 2],
12184 },
12185 ]
12186 );
12187 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
12188 assert!(error.contains("MEMRA_STEP_TP"));
12189 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
12190 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12191
12192 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
12193 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
12194 assert_eq!(all.first().unwrap().layer, 0);
12195 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
12196 let devices = (0..8).collect::<Vec<_>>();
12197 assert!(all.iter().all(|spec| spec.devices == devices));
12198 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
12199 }
12200}
12201
12202struct TokenGraphChild {
12214 graph: cudarc::driver::CudaGraph,
12215 node: cudarc::driver::sys::CUgraphNode,
12216 ctx: cudarc::driver::sys::CUcontext,
12217}
12218
12219struct TokenGraphFaSite {
12223 ctx: cudarc::driver::sys::CUcontext,
12224 memset_o: cudarc::driver::sys::CUgraphNode,
12225 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
12226 fa: cudarc::driver::sys::CUgraphNode,
12227 combine: cudarc::driver::sys::CUgraphNode,
12228 window: usize,
12229 n_head: usize,
12230 n_head_kv: usize,
12231 head_dim: usize,
12232}
12233
12234pub struct TokenGraphBuilder {
12235 parent: cudarc::driver::sys::CUgraph,
12236 children: Vec<TokenGraphChild>,
12237 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
12240 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
12243 group: Option<(
12246 u32,
12247 Vec<cudarc::driver::sys::CUgraphNode>,
12248 Vec<cudarc::driver::sys::CUgraphNode>,
12249 )>,
12250}
12251
12252unsafe impl Send for TokenGraphBuilder {}
12254
12255impl TokenGraphBuilder {
12256 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
12257 use cudarc::driver::sys;
12258 let mut parent: sys::CUgraph = std::ptr::null_mut();
12259 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
12260 if r != sys::CUresult::CUDA_SUCCESS {
12261 return Err(format!("token graph create: {r:?}").into());
12262 }
12263 Ok(Self {
12264 parent,
12265 children: Vec::new(),
12266 frontier: Vec::new(),
12267 pending_detached: Vec::new(),
12268 group: None,
12269 })
12270 }
12271
12272 fn push_child(
12273 &mut self,
12274 graph: cudarc::driver::CudaGraph,
12275 parallel_group: Option<u32>,
12276 detached: bool,
12277 absorb: bool,
12278 ctx: cudarc::driver::sys::CUcontext,
12279 ) -> Result<(), Box<dyn std::error::Error>> {
12280 use cudarc::driver::sys;
12281 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
12285 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
12286 (state, Some(group)) => {
12287 if let Some((_, _, members)) = state.take() {
12289 self.frontier = members;
12290 }
12291 let base = self.frontier.clone();
12292 *state = Some((group, base.clone(), Vec::new()));
12293 base
12294 }
12295 (state, None) if detached => match state.as_ref() {
12296 Some((_, base, _)) => base.clone(),
12297 None => self.frontier.clone(),
12298 },
12299 (state, None) => {
12300 if let Some((_, _, members)) = state.take() {
12301 self.frontier = members;
12302 }
12303 let mut deps = self.frontier.clone();
12304 if absorb {
12305 deps.append(&mut self.pending_detached);
12306 }
12307 deps
12308 }
12309 };
12310 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12311 let r = unsafe {
12312 sys::cuGraphAddChildGraphNode(
12313 &mut node,
12314 self.parent,
12315 if deps.is_empty() {
12316 std::ptr::null()
12317 } else {
12318 deps.as_ptr()
12319 },
12320 deps.len(),
12321 graph.cu_graph(),
12322 )
12323 };
12324 if r != sys::CUresult::CUDA_SUCCESS {
12325 return Err(format!("token graph child: {r:?}").into());
12326 }
12327 match (&mut self.group, parallel_group, detached) {
12328 (_, None, true) => self.pending_detached.push(node),
12329 (Some((_, _, members)), Some(_), _) => members.push(node),
12330 _ => self.frontier = vec![node],
12331 }
12332 self.children.push(TokenGraphChild { graph, node, ctx });
12333 Ok(())
12334 }
12335
12336 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
12337 use cudarc::driver::sys;
12338 if let Some((_, _, members)) = self.group.take() {
12339 self.frontier = members;
12340 }
12341 let mut fa_sites = Vec::new();
12344 for child in &self.children {
12345 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
12346 fa_sites.push(site);
12347 }
12348 }
12349 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12350 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
12351 if r != sys::CUresult::CUDA_SUCCESS {
12352 return Err(format!("token graph instantiate: {r:?}").into());
12353 }
12354 Ok(TokenGraph {
12355 exec,
12356 parent: self.parent,
12357 _children: self.children,
12358 fa_sites,
12359 })
12360 }
12361}
12362
12363fn discover_fa_site(
12366 child_node: cudarc::driver::sys::CUgraphNode,
12367 ctx: cudarc::driver::sys::CUcontext,
12368) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
12369 use cudarc::driver::sys;
12370 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12371 if r == sys::CUresult::CUDA_SUCCESS {
12372 Ok(())
12373 } else {
12374 Err(format!("{what}: {r:?}").into())
12375 }
12376 }
12377 let mut graph: sys::CUgraph = std::ptr::null_mut();
12378 unsafe {
12379 cu_try(
12380 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
12381 "fa-site child GetGraph",
12382 )?;
12383 }
12384 let mut count: usize = 0;
12385 unsafe {
12386 cu_try(
12387 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
12388 "fa-site GetNodes(count)",
12389 )?;
12390 }
12391 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
12392 unsafe {
12393 cu_try(
12394 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
12395 "fa-site GetNodes",
12396 )?;
12397 }
12398 nodes.truncate(count);
12399 let node_type =
12400 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
12401 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
12402 unsafe {
12403 cu_try(
12404 sys::cuGraphNodeGetType(node, &mut ty),
12405 "fa-site NodeGetType",
12406 )?;
12407 }
12408 Ok(ty)
12409 };
12410 let memsets: Vec<sys::CUgraphNode> = {
12411 let mut v = Vec::new();
12412 for &node in &nodes {
12413 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
12414 v.push(node);
12415 }
12416 }
12417 v
12418 };
12419 if memsets.len() != 3 {
12420 return Ok(None);
12421 }
12422 let dependents =
12424 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
12425 let mut n: usize = 0;
12426 unsafe {
12427 cu_try(
12428 sys::cuGraphNodeGetDependentNodes_v2(
12429 node,
12430 std::ptr::null_mut(),
12431 std::ptr::null_mut(),
12432 &mut n,
12433 ),
12434 "fa-site GetDependentNodes(count)",
12435 )?;
12436 }
12437 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
12438 unsafe {
12439 cu_try(
12440 sys::cuGraphNodeGetDependentNodes_v2(
12441 node,
12442 v.as_mut_ptr(),
12443 std::ptr::null_mut(),
12444 &mut n,
12445 ),
12446 "fa-site GetDependentNodes",
12447 )?;
12448 }
12449 v.truncate(n);
12450 Ok(v)
12451 };
12452 let mut fa: Option<sys::CUgraphNode> = None;
12455 let mut last_memset: Option<sys::CUgraphNode> = None;
12456 for &ms in &memsets {
12457 for dep in dependents(ms)? {
12458 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12459 fa = Some(dep);
12460 last_memset = Some(ms);
12461 }
12462 }
12463 }
12464 let (Some(fa), Some(_last)) = (fa, last_memset) else {
12465 return Ok(None);
12466 };
12467 let mut combine: Option<sys::CUgraphNode> = None;
12468 for dep in dependents(fa)? {
12469 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12470 combine = Some(dep);
12471 }
12472 }
12473 let Some(combine) = combine else {
12474 return Ok(None);
12475 };
12476 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12479 unsafe {
12480 cu_try(
12481 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
12482 "fa-site KernelNodeGetParams",
12483 )?;
12484 }
12485 let arg_i32 =
12486 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
12487 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
12488 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
12490 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12491 unsafe {
12492 cu_try(
12493 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12494 "fa-site MemsetNodeGetParams",
12495 )?;
12496 }
12497 Ok(mp.width)
12498 };
12499 let mut widest = memsets[0];
12500 for &ms in &memsets[1..] {
12501 if width_of(ms)? > width_of(widest)? {
12502 widest = ms;
12503 }
12504 }
12505 let memset_m: Vec<sys::CUgraphNode> =
12506 memsets.iter().copied().filter(|&m| m != widest).collect();
12507 Ok(Some(TokenGraphFaSite {
12508 ctx,
12509 memset_o: widest,
12510 memset_m: [memset_m[0], memset_m[1]],
12511 fa,
12512 combine,
12513 window: win as usize,
12514 n_head: nh as usize,
12515 n_head_kv: nhkv as usize,
12516 head_dim: hd as usize,
12517 }))
12518}
12519
12520pub struct TokenGraph {
12521 exec: cudarc::driver::sys::CUgraphExec,
12522 parent: cudarc::driver::sys::CUgraph,
12523 _children: Vec<TokenGraphChild>,
12524 fa_sites: Vec<TokenGraphFaSite>,
12525}
12526
12527unsafe impl Send for TokenGraph {}
12528
12529impl TokenGraph {
12530 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
12535 use cudarc::driver::sys;
12536 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12537 if r == sys::CUresult::CUDA_SUCCESS {
12538 Ok(())
12539 } else {
12540 Err(format!("{what}: {r:?}").into())
12541 }
12542 }
12543 for site in &self.fa_sites {
12544 let layer_bucket = if site.window > 0 {
12545 bucket.min(site.window)
12546 } else {
12547 bucket
12548 };
12549 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
12550 let nsp = layer_bucket.div_ceil(sp).max(1);
12551 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12553 unsafe {
12554 cu_try(
12555 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
12556 "retarget fa GetParams",
12557 )?;
12558 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
12559 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
12560 params.gridDimY = nsp as u32;
12561 cu_try(
12562 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
12563 "retarget fa SetParams",
12564 )?;
12565 }
12566 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12568 unsafe {
12569 cu_try(
12570 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
12571 "retarget combine GetParams",
12572 )?;
12573 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
12574 cu_try(
12575 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
12576 "retarget combine SetParams",
12577 )?;
12578 }
12579 let set_width =
12581 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
12582 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12583 unsafe {
12584 cu_try(
12585 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12586 "retarget memset GetParams",
12587 )?;
12588 }
12589 mp.width = width;
12590 unsafe {
12591 cu_try(
12592 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
12593 "retarget memset SetParams",
12594 )?;
12595 }
12596 Ok(())
12597 };
12598 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
12599 set_width(site.memset_m[0], site.n_head * nsp)?;
12600 set_width(site.memset_m[1], site.n_head * nsp)?;
12601 }
12602 Ok(())
12603 }
12604
12605 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
12606 use cudarc::driver::sys;
12607 let _main = e.gpu.enter_main()?;
12608 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
12609 if r != sys::CUresult::CUDA_SUCCESS {
12610 return Err(format!("token graph launch: {r:?}").into());
12611 }
12612 Ok(())
12613 }
12614}
12615
12616impl Drop for TokenGraph {
12617 fn drop(&mut self) {
12618 unsafe {
12619 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
12620 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
12621 }
12622 }
12623}
12624
12625std::thread_local! {
12626 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
12627 const { std::cell::RefCell::new(None) };
12628}
12629
12630pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
12632 let builder = TokenGraphBuilder::new()?;
12633 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
12634 Ok(())
12635}
12636
12637pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
12639 let builder = TOKEN_GRAPH_BUILDER
12640 .with(|cell| cell.borrow_mut().take())
12641 .ok_or("token graph build was not begun")?;
12642 builder.finish()
12643}
12644
12645pub fn token_graph_building() -> bool {
12647 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
12648}
12649
12650pub fn graph_section<F>(
12655 engine: &Engine,
12656 parallel_group: Option<u32>,
12657 f: F,
12658) -> Result<(), Box<dyn std::error::Error>>
12659where
12660 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12661{
12662 graph_section_opts(engine, parallel_group, false, false, f)
12663}
12664
12665pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
12667where
12668 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12669{
12670 graph_section_opts(engine, None, false, true, f)
12671}
12672
12673pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
12676where
12677 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12678{
12679 graph_section_opts(engine, None, true, false, f)
12680}
12681
12682pub fn graph_section_opts<F>(
12683 engine: &Engine,
12684 parallel_group: Option<u32>,
12685 detached: bool,
12686 absorb: bool,
12687 f: F,
12688) -> Result<(), Box<dyn std::error::Error>>
12689where
12690 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12691{
12692 let building = token_graph_building();
12693 if !building {
12694 let mut f = f;
12695 return f();
12696 }
12697 let (child, ctx) = {
12698 let _main = engine.gpu.enter_main()?;
12699 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
12700 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
12701 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
12702 return Err(format!("graph section ctx query: {r:?}").into());
12703 }
12704 let mut f = f;
12705 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
12708 (child, ctx)
12709 };
12710 TOKEN_GRAPH_BUILDER.with(|cell| {
12711 cell.borrow_mut()
12712 .as_mut()
12713 .expect("builder checked above")
12714 .push_child(child, parallel_group, detached, absorb, ctx)
12715 })
12716}