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 root_shares_ctx(&self, e: &Engine) -> bool {
2379 self.ranks
2380 .first()
2381 .is_some_and(|root| root.ctx().cu_ctx() == e.ctx().cu_ctx())
2382 }
2383
2384 pub fn bf16_column_parallel_resident_native_device(
2397 &self,
2398 matrix: &ResidentBf16ColumnParallel,
2399 root_activation: &CudaSlice<f32>,
2400 tokens: usize,
2401 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2402 let rank_outputs = self.bf16_column_parallel_resident_device_shards_from_root(
2403 matrix,
2404 root_activation,
2405 tokens,
2406 )?;
2407 let local_out = matrix.out_features / self.ranks.len();
2408 let gathered = self.gather_native_column_shards_device(&rank_outputs, tokens, local_out)?;
2409 let root = &self.ranks[0];
2410 let _main = root.gpu.enter_main()?;
2411 root.stream().synchronize()?;
2412 Ok(gathered)
2413 }
2414
2415 pub fn bf16_column_parallel_resident_device_shards_from_root(
2420 &self,
2421 matrix: &ResidentBf16ColumnParallel,
2422 root_activation: &CudaSlice<f32>,
2423 tokens: usize,
2424 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2425 if self.ranks.len() > 1 && !self.native_p2p {
2426 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2427 }
2428 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2429 let values = tokens
2430 .checked_mul(matrix.in_features)
2431 .ok_or("device BF16 column activation size overflow")?;
2432 let root = &self.ranks[0];
2433 if tokens == 0
2434 || root_activation.len() < values
2435 || root_activation.ordinal() != root.ctx().ordinal()
2436 {
2437 return Err("device BF16 column root activation geometry mismatch".into());
2438 }
2439
2440 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2441 let root_input = {
2442 let _main = root.gpu.enter_main()?;
2443 let mut root_input = root.uninit(values)?;
2444 root.stream()
2445 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
2446 root_input
2447 };
2448 {
2452 let _main = root.gpu.enter_main()?;
2453 root.stream().synchronize()?;
2454 }
2455 rank_inputs.push(root_input);
2456 for engine in &self.ranks[1..] {
2457 let peer_input = {
2458 let _main = engine.gpu.enter_main()?;
2459 let mut peer_input = engine.uninit(values)?;
2460 engine
2461 .stream()
2462 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2463 peer_input
2464 };
2465 rank_inputs.push(peer_input);
2466 }
2467
2468 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2469 for rank in 0..self.ranks.len() {
2470 rank_outputs.push(run_resident_bf16_rank_device(
2471 &self.ranks[rank],
2472 &matrix.ranks[rank],
2473 &rank_inputs[rank],
2474 tokens,
2475 matrix.canonical_chunk_rows,
2476 self.bulk_p2p,
2477 )?);
2478 }
2479 Ok(rank_outputs)
2480 }
2481
2482 pub fn bf16_column_parallel_resident_device_shards(
2489 &self,
2490 matrix: &ResidentBf16ColumnParallel,
2491 activations: &[f32],
2492 tokens: usize,
2493 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2494 if self.ranks.len() > 1 && !self.native_p2p {
2495 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2496 }
2497 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2498 validate_activations(activations, tokens, matrix.in_features)?;
2499
2500 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2501 let root_input = {
2502 let root = &self.ranks[0];
2503 let _main = root.gpu.enter_main()?;
2504 root.htod(activations)?
2505 };
2506 {
2512 let root = &self.ranks[0];
2513 let _main = root.gpu.enter_main()?;
2514 root.stream().synchronize()?;
2515 }
2516 rank_inputs.push(root_input);
2517 for engine in &self.ranks[1..] {
2518 let peer_input = {
2519 let _main = engine.gpu.enter_main()?;
2520 let mut peer_input = engine.uninit(activations.len())?;
2521 engine
2522 .stream()
2523 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2524 peer_input
2525 };
2526 rank_inputs.push(peer_input);
2527 }
2528
2529 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2530 for rank in 0..self.ranks.len() {
2531 rank_outputs.push(run_resident_bf16_rank_device(
2532 &self.ranks[rank],
2533 &matrix.ranks[rank],
2534 &rank_inputs[rank],
2535 tokens,
2536 matrix.canonical_chunk_rows,
2537 self.bulk_p2p,
2538 )?);
2539 }
2540 Ok(rank_outputs)
2541 }
2542
2543 pub fn allocate_replicated_device_rows(
2547 &self,
2548 tokens: usize,
2549 width: usize,
2550 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2551 if self.ranks.len() > 1 && !self.native_p2p {
2552 return Err("replicated device rows require native P2P ranks".into());
2553 }
2554 let values = tokens
2555 .checked_mul(width)
2556 .ok_or("replicated device row size overflow")?;
2557 let rank_lengths = vec![values; self.ranks.len()];
2558 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2559 let mut ranks = Vec::with_capacity(self.ranks.len());
2560 for engine in &self.ranks {
2561 let _main = engine.gpu.enter_main()?;
2562 ranks.push(engine.uninit(values)?);
2563 }
2564 Ok(ResidentReplicatedDeviceRows {
2565 ranks,
2566 tokens,
2567 width,
2568 })
2569 }
2570
2571 pub fn refresh_replicated_device_rows_from_root(
2573 &self,
2574 rows: &mut ResidentReplicatedDeviceRows,
2575 source: &CudaSlice<f32>,
2576 ) -> Result<(), Box<dyn std::error::Error>> {
2577 if self.ranks.len() > 1 && !self.native_p2p {
2578 return Err("replicated device rows require native P2P ranks".into());
2579 }
2580 validate_replicated_device_rows(&self.ranks, rows)?;
2581 let root = self
2582 .ranks
2583 .first()
2584 .ok_or("replicated rows have no root rank")?;
2585 let values = replicated_device_row_source_values(
2586 rows.tokens,
2587 rows.width,
2588 source.len(),
2589 source.ordinal(),
2590 root.ctx().ordinal(),
2591 )?;
2592 let (root_rows, peer_rows) = rows
2593 .ranks
2594 .split_first_mut()
2595 .ok_or("replicated rows have no root allocation")?;
2596 {
2597 let _main = root.gpu.enter_main()?;
2598 let mut destination = root_rows.slice_mut(0..values);
2599 root.stream()
2600 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2601 root.stream().synchronize()?;
2602 }
2603 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2604 let _main = engine.gpu.enter_main()?;
2605 let mut destination = peer_rows.slice_mut(0..values);
2606 engine
2607 .stream()
2608 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2609 }
2610 Ok(())
2611 }
2612
2613 pub fn upload_replicated_device_rows(
2615 &self,
2616 rows: &[f32],
2617 tokens: usize,
2618 width: usize,
2619 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2620 if self.ranks.len() > 1 && !self.native_p2p {
2621 return Err("replicated device rows require native P2P ranks".into());
2622 }
2623 validate_activations(rows, tokens, width)?;
2624 let root = self
2625 .ranks
2626 .first()
2627 .ok_or("replicated rows have no root rank")?;
2628 let root_rows = {
2629 let _main = root.gpu.enter_main()?;
2630 root.htod(rows)?
2631 };
2632 {
2633 let _main = root.gpu.enter_main()?;
2634 root.stream().synchronize()?;
2635 }
2636 let mut ranks = Vec::with_capacity(self.ranks.len());
2637 ranks.push(root_rows);
2638 for engine in self.ranks.iter().skip(1) {
2639 let _main = engine.gpu.enter_main()?;
2640 let mut peer_rows = engine.uninit(rows.len())?;
2641 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2642 ranks.push(peer_rows);
2643 }
2644 Ok(ResidentReplicatedDeviceRows {
2645 ranks,
2646 tokens,
2647 width,
2648 })
2649 }
2650
2651 pub fn bf16_column_parallel_resident_replicated_device_shards(
2653 &self,
2654 matrix: &ResidentBf16ColumnParallel,
2655 activations: &ResidentReplicatedDeviceRows,
2656 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2657 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2658 validate_replicated_device_rows(&self.ranks, activations)?;
2659 if activations.width != matrix.in_features {
2660 return Err(format!(
2661 "replicated BF16 column input width {} != matrix width {}",
2662 activations.width, matrix.in_features
2663 )
2664 .into());
2665 }
2666 let mut outputs = Vec::with_capacity(self.ranks.len());
2667 for rank in 0..self.ranks.len() {
2668 outputs.push(run_resident_bf16_rank_device(
2669 &self.ranks[rank],
2670 &matrix.ranks[rank],
2671 &activations.ranks[rank],
2672 activations.tokens,
2673 matrix.canonical_chunk_rows,
2674 self.bulk_p2p,
2675 )?);
2676 }
2677 Ok(outputs)
2678 }
2679
2680 #[allow(clippy::too_many_arguments)]
2682 pub fn upload_sigmoid_topk_router(
2683 &self,
2684 weight: Bf16Matrix<'_>,
2685 correction_bias: &[f32],
2686 active: Option<&[bool]>,
2687 experts_per_token: usize,
2688 scaling_factor: f32,
2689 route_norm: bool,
2690 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2691 weight.validate()?;
2692 if correction_bias.len() != weight.out_features
2693 || experts_per_token == 0
2694 || experts_per_token > weight.out_features
2695 || !correction_bias.iter().all(|value| value.is_finite())
2696 || !scaling_factor.is_finite()
2697 || scaling_factor <= 0.0
2698 {
2699 return Err(format!(
2700 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2701 weight.out_features,
2702 weight.in_features,
2703 correction_bias.len(),
2704 experts_per_token,
2705 )
2706 .into());
2707 }
2708 let active_row = active
2709 .map(|mask| {
2710 if mask.len() != weight.out_features {
2711 return Err(format!(
2712 "sigmoid router active mask {} != experts {}",
2713 mask.len(),
2714 weight.out_features
2715 ));
2716 }
2717 Ok(mask
2718 .iter()
2719 .map(|&enabled| u8::from(enabled))
2720 .collect::<Vec<_>>())
2721 })
2722 .transpose()?
2723 .unwrap_or_else(|| vec![1; weight.out_features]);
2724 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2725 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2726
2727 let root = self
2728 .ranks
2729 .first()
2730 .ok_or("sigmoid router runtime has no root rank")?;
2731 let _main = root.gpu.enter_main()?;
2732 let bf16 = root.htod_bytes(weight.bytes)?;
2733 let weight_f32 = root.bf16_to_f32(
2734 &bf16.slice(0..bf16.len()),
2735 weight.out_features * weight.in_features,
2736 )?;
2737 Ok(ResidentSigmoidTopKRouter {
2738 weight: weight_f32,
2739 correction_bias: root.htod(correction_bias)?,
2740 active: root.htod_bytes(&active_row)?,
2741 root_device: root.ctx().ordinal(),
2742 input_width: weight.in_features,
2743 expert_count: weight.out_features,
2744 experts_per_token,
2745 active_count,
2746 scaling_factor,
2747 route_norm,
2748 })
2749 }
2750
2751 pub fn sigmoid_topk_replicated_device_rows_host(
2756 &self,
2757 router: &ResidentSigmoidTopKRouter,
2758 input: &ResidentReplicatedDeviceRows,
2759 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2760 validate_replicated_device_rows(&self.ranks, input)?;
2761 if input.width != router.input_width {
2762 return Err(format!(
2763 "sigmoid router input width {} != resident width {}",
2764 input.width, router.input_width
2765 )
2766 .into());
2767 }
2768 let root = self
2769 .ranks
2770 .first()
2771 .ok_or("sigmoid router runtime has no root rank")?;
2772 let _main = root.gpu.enter_main()?;
2773 if root.ctx().ordinal() != router.root_device
2774 || router.weight.ordinal() != router.root_device
2775 || router.correction_bias.ordinal() != router.root_device
2776 || router.active.ordinal() != router.root_device
2777 {
2778 return Err("sigmoid router root residency changed".into());
2779 }
2780 let logits = root.router_gemv(
2781 &router.weight,
2782 &input.ranks[0],
2783 router.input_width,
2784 router.expert_count,
2785 input.tokens,
2786 )?;
2787 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2788 &logits,
2789 input.tokens,
2790 router.expert_count,
2791 router.experts_per_token,
2792 router.active_count,
2793 &router.correction_bias,
2794 &router.active,
2795 router.scaling_factor,
2796 router.route_norm,
2797 )?;
2798 Ok(SigmoidTopKHostOutput {
2799 logits: root.dtoh(&logits)?,
2800 selected,
2801 weights,
2802 })
2803 }
2804
2805 pub fn upload_replicated_bf16_swiglu(
2807 &self,
2808 gate: Bf16Matrix<'_>,
2809 up: Bf16Matrix<'_>,
2810 down: Bf16Matrix<'_>,
2811 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2812 gate.validate()?;
2813 up.validate()?;
2814 down.validate()?;
2815 if gate.in_features != up.in_features
2816 || gate.out_features != up.out_features
2817 || down.in_features != gate.out_features
2818 || down.out_features != gate.in_features
2819 {
2820 return Err(format!(
2821 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2822 gate.out_features,
2823 gate.in_features,
2824 up.out_features,
2825 up.in_features,
2826 down.out_features,
2827 down.in_features,
2828 )
2829 .into());
2830 }
2831 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2832 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2833 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2834 for engine in &self.ranks {
2835 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2836 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2837 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2838 }
2839 Ok(ResidentReplicatedBf16SwiGlu {
2840 gate: gate_ranks,
2841 up: up_ranks,
2842 down: down_ranks,
2843 input_width: gate.in_features,
2844 intermediate_width: gate.out_features,
2845 })
2846 }
2847
2848 pub fn replicated_bf16_swiglu_resident_device(
2850 &self,
2851 mlp: &ResidentReplicatedBf16SwiGlu,
2852 input: &ResidentReplicatedDeviceRows,
2853 activation_limit: Option<f32>,
2854 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2855 validate_step_expert_activation_limit(activation_limit)?;
2856 validate_replicated_device_rows(&self.ranks, input)?;
2857 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2858 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2859 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2860 if input.width != mlp.input_width
2861 || mlp.gate.len() != self.ranks.len()
2862 || mlp.up.len() != self.ranks.len()
2863 || mlp.down.len() != self.ranks.len()
2864 {
2865 return Err("replicated BF16 SwiGLU residency or input width changed".into());
2866 }
2867
2868 let mut outputs = Vec::with_capacity(self.ranks.len());
2869 for rank in 0..self.ranks.len() {
2870 let engine = &self.ranks[rank];
2871 let gate = run_resident_bf16_rank_device(
2872 engine,
2873 &mlp.gate[rank],
2874 &input.ranks[rank],
2875 input.tokens,
2876 None,
2877 self.bulk_p2p,
2878 )?;
2879 let up = run_resident_bf16_rank_device(
2880 engine,
2881 &mlp.up[rank],
2882 &input.ranks[rank],
2883 input.tokens,
2884 None,
2885 self.bulk_p2p,
2886 )?;
2887 let _main = engine.gpu.enter_main()?;
2888 let values = input
2889 .tokens
2890 .checked_mul(mlp.intermediate_width)
2891 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
2892 let mut activation = engine.uninit(values)?;
2893 if let Some(limit) = activation_limit {
2894 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
2895 } else {
2896 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
2897 }
2898 outputs.push(run_resident_bf16_rank_device(
2899 engine,
2900 &mlp.down[rank],
2901 &activation,
2902 input.tokens,
2903 None,
2904 self.bulk_p2p,
2905 )?);
2906 }
2907 Ok(ResidentReplicatedDeviceRows {
2908 ranks: outputs,
2909 tokens: input.tokens,
2910 width: mlp.input_width,
2911 })
2912 }
2913
2914 pub fn rms_norm_replicated_device_rows(
2916 &self,
2917 input: &ResidentReplicatedDeviceRows,
2918 weight: &[f32],
2919 eps: f32,
2920 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2921 validate_replicated_device_rows(&self.ranks, input)?;
2922 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
2923 return Err(format!(
2924 "replicated RMS norm weight/eps {}/{} != width {}",
2925 weight.len(),
2926 eps,
2927 input.width
2928 )
2929 .into());
2930 }
2931 let mut ranks = Vec::with_capacity(self.ranks.len());
2932 for (rank, engine) in self.ranks.iter().enumerate() {
2933 let _main = engine.gpu.enter_main()?;
2934 let weight = engine.htod(weight)?;
2935 let mut output = engine.uninit(input.tokens * input.width)?;
2936 engine.rms_norm(
2937 &input.ranks[rank],
2938 &weight,
2939 &mut output,
2940 input.width,
2941 input.tokens,
2942 eps,
2943 )?;
2944 ranks.push(output);
2945 }
2946 Ok(ResidentReplicatedDeviceRows {
2947 ranks,
2948 tokens: input.tokens,
2949 width: input.width,
2950 })
2951 }
2952
2953 pub fn add_rms_norm_replicated_device_rows(
2955 &self,
2956 input: &ResidentReplicatedDeviceRows,
2957 update: &ResidentReplicatedDeviceRows,
2958 weight: &[f32],
2959 eps: f32,
2960 ) -> Result<
2961 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
2962 Box<dyn std::error::Error>,
2963 > {
2964 validate_replicated_device_rows(&self.ranks, input)?;
2965 validate_replicated_device_rows(&self.ranks, update)?;
2966 if input.tokens != update.tokens
2967 || input.width != update.width
2968 || weight.len() != input.width
2969 || !eps.is_finite()
2970 || eps <= 0.0
2971 {
2972 return Err(format!(
2973 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
2974 input.tokens,
2975 input.width,
2976 update.tokens,
2977 update.width,
2978 weight.len(),
2979 )
2980 .into());
2981 }
2982 let values = input.tokens * input.width;
2983 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
2984 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
2985 for (rank, engine) in self.ranks.iter().enumerate() {
2986 let _main = engine.gpu.enter_main()?;
2987 let weight = engine.htod(weight)?;
2988 let mut residual = engine.uninit(values)?;
2989 let mut normalized = engine.uninit(values)?;
2990 engine.add_rms_norm(
2991 &input.ranks[rank],
2992 &update.ranks[rank],
2993 &weight,
2994 &mut residual,
2995 &mut normalized,
2996 input.width,
2997 input.tokens,
2998 eps,
2999 )?;
3000 residual_ranks.push(residual);
3001 normalized_ranks.push(normalized);
3002 }
3003 Ok((
3004 ResidentReplicatedDeviceRows {
3005 ranks: residual_ranks,
3006 tokens: input.tokens,
3007 width: input.width,
3008 },
3009 ResidentReplicatedDeviceRows {
3010 ranks: normalized_ranks,
3011 tokens: input.tokens,
3012 width: input.width,
3013 },
3014 ))
3015 }
3016
3017 pub fn collect_replicated_device_rows(
3018 &self,
3019 rows: &ResidentReplicatedDeviceRows,
3020 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
3021 validate_replicated_device_rows(&self.ranks, rows)?;
3022 let mut outputs = Vec::with_capacity(self.ranks.len());
3023 for (rank, engine) in self.ranks.iter().enumerate() {
3024 let _main = engine.gpu.enter_main()?;
3025 outputs.push(engine.dtoh(&rows.ranks[rank])?);
3026 }
3027 Ok(outputs)
3028 }
3029
3030 pub fn upload_bf16_row_parallel(
3031 &self,
3032 matrix: Bf16Matrix<'_>,
3033 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
3034 matrix.validate()?;
3035 let tp = self.ranks.len();
3036 if matrix.in_features % tp != 0 {
3037 return Err(format!(
3038 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
3039 matrix.in_features
3040 )
3041 .into());
3042 }
3043 let mut ranks = Vec::with_capacity(tp);
3044 for (rank, engine) in self.ranks.iter().enumerate() {
3045 let shard = bf16_row_shard(matrix, tp, rank)?;
3046 ranks.push(upload_bf16_rank(
3047 engine,
3048 Bf16Matrix {
3049 bytes: &shard,
3050 out_features: matrix.out_features,
3051 in_features: matrix.in_features / tp,
3052 },
3053 false,
3054 )?);
3055 }
3056 Ok(ResidentBf16RowParallel {
3057 ranks,
3058 out_features: matrix.out_features,
3059 in_features: matrix.in_features,
3060 })
3061 }
3062
3063 pub fn bf16_row_parallel_resident(
3064 &self,
3065 matrix: &ResidentBf16RowParallel,
3066 activations: &[f32],
3067 tokens: usize,
3068 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
3069 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
3070 validate_activations(activations, tokens, matrix.in_features)?;
3071 let tp = self.ranks.len();
3072 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
3073 let mut rank_partials = Vec::with_capacity(tp);
3074 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
3075 let local_activations =
3076 activation_shard(activations, tokens, matrix.in_features, tp, rank);
3077 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
3078 for (sum, value) in reduced.iter_mut().zip(&partial) {
3079 *sum += value;
3080 }
3081 rank_partials.push(partial);
3082 }
3083 Ok(RowParallelResult {
3084 reduced,
3085 rank_partials,
3086 })
3087 }
3088
3089 pub fn upload_step_bf16_row_parallel(
3091 &self,
3092 matrix: Bf16Matrix<'_>,
3093 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3094 self.upload_step_bf16_row_parallel_inner(matrix, false)
3095 }
3096
3097 pub fn upload_step_bf16_row_parallel_f32_mirror(
3098 &self,
3099 matrix: Bf16Matrix<'_>,
3100 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3101 self.upload_step_bf16_row_parallel_inner(matrix, true)
3102 }
3103
3104 fn upload_step_bf16_row_parallel_inner(
3105 &self,
3106 matrix: Bf16Matrix<'_>,
3107 f32_mirror: bool,
3108 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
3109 matrix.validate()?;
3110 let tp = self.ranks.len();
3111 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
3112 let local_in = matrix.in_features / tp;
3113 let blocks_per_rank = local_in / canonical_chunk_cols;
3114 let mut ranks = Vec::with_capacity(tp);
3115 for (rank, engine) in self.ranks.iter().enumerate() {
3116 let mut blocks = Vec::with_capacity(blocks_per_rank);
3117 for block in 0..blocks_per_rank {
3118 let global_block = rank * blocks_per_rank + block;
3119 let col_start = global_block * canonical_chunk_cols;
3120 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3121 blocks.push(upload_bf16_rank(
3122 engine,
3123 Bf16Matrix {
3124 bytes: &bytes,
3125 out_features: matrix.out_features,
3126 in_features: canonical_chunk_cols,
3127 },
3128 f32_mirror,
3129 )?);
3130 }
3131 ranks.push(blocks);
3132 }
3133 Ok(ResidentStepBf16RowParallel {
3134 ranks,
3135 out_features: matrix.out_features,
3136 in_features: matrix.in_features,
3137 canonical_chunk_cols,
3138 })
3139 }
3140
3141 pub fn step_bf16_row_parallel_resident(
3146 &self,
3147 matrix: &ResidentStepBf16RowParallel,
3148 activations: &[f32],
3149 tokens: usize,
3150 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3151 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3152 validate_activations(activations, tokens, matrix.in_features)?;
3153 let root = &self.ranks[0];
3154 let output_len = tokens
3155 .checked_mul(matrix.out_features)
3156 .ok_or("Step BF16 row output size overflow")?;
3157 let mut reduced = {
3158 let _main = root.gpu.enter_main()?;
3159 root.htod(&vec![0.0f32; output_len])?
3160 };
3161 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3162 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3163 for (block, resident) in blocks.iter().enumerate() {
3164 let global_block = rank * blocks_per_rank + block;
3165 let input = activation_shard(
3166 activations,
3167 tokens,
3168 matrix.in_features,
3169 PRODUCT_MAX_CARDS,
3170 global_block,
3171 );
3172 let partial =
3173 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3174 let next = {
3175 let _main = root.gpu.enter_main()?;
3176 let partial = root.htod(&partial)?;
3177 let mut next = root.uninit(output_len)?;
3178 root.add(&reduced, &partial, &mut next, output_len)?;
3179 next
3180 };
3181 reduced = next;
3182 }
3183 }
3184 let _main = root.gpu.enter_main()?;
3185 root.dtoh(&reduced)
3186 }
3187
3188 pub fn step_bf16_row_parallel_resident_native(
3194 &self,
3195 matrix: &ResidentStepBf16RowParallel,
3196 activations: &[f32],
3197 tokens: usize,
3198 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3199 if self.ranks.len() > 1 && !self.native_p2p {
3200 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3201 }
3202 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3203 validate_activations(activations, tokens, matrix.in_features)?;
3204 let root = &self.ranks[0];
3205 let root_input = {
3206 let _main = root.gpu.enter_main()?;
3207 root.htod(activations)?
3208 };
3209 {
3212 let _main = root.gpu.enter_main()?;
3213 root.stream().synchronize()?;
3214 }
3215 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3216 let _main = root.gpu.enter_main()?;
3217 root.dtoh(&reduced)
3218 }
3219
3220 pub fn step_bf16_row_parallel_resident_native_device(
3228 &self,
3229 matrix: &ResidentStepBf16RowParallel,
3230 root_activation: &CudaSlice<f32>,
3231 tokens: usize,
3232 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3233 if self.ranks.len() > 1 && !self.native_p2p {
3234 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3235 }
3236 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3237 let values = tokens
3238 .checked_mul(matrix.in_features)
3239 .ok_or("device Step BF16 row activation size overflow")?;
3240 let root = &self.ranks[0];
3241 if tokens == 0
3242 || root_activation.len() < values
3243 || root_activation.ordinal() != root.ctx().ordinal()
3244 {
3245 return Err("device Step BF16 row root activation geometry mismatch".into());
3246 }
3247 let root_input = {
3248 let _main = root.gpu.enter_main()?;
3249 let mut root_input = root.uninit(values)?;
3250 root.stream()
3251 .memcpy_dtod(&root_activation.slice(0..values), &mut root_input)?;
3252 root.stream().synchronize()?; root_input
3254 };
3255 let reduced = self.step_bf16_row_native_reduce_from_root(matrix, &root_input, tokens)?;
3256 let _main = root.gpu.enter_main()?;
3257 root.stream().synchronize()?;
3258 Ok(reduced)
3259 }
3260
3261 fn step_bf16_row_native_reduce_from_root(
3266 &self,
3267 matrix: &ResidentStepBf16RowParallel,
3268 root_input: &CudaSlice<f32>,
3269 tokens: usize,
3270 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3271 let root = &self.ranks[0];
3272 let output_len = tokens
3273 .checked_mul(matrix.out_features)
3274 .ok_or("native Step BF16 row output size overflow")?;
3275 let mut reduced = {
3276 let _main = root.gpu.enter_main()?;
3277 root.htod(&vec![0.0f32; output_len])?
3278 };
3279 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3280 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3281 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3282 let mut remote_partial_keepalive = Vec::new();
3283 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3284 for (block, resident) in blocks.iter().enumerate() {
3285 let global_block = rank * blocks_per_rank + block;
3286 let col_start = global_block * matrix.canonical_chunk_cols;
3287 let block_len = tokens
3288 .checked_mul(matrix.canonical_chunk_cols)
3289 .ok_or("native Step BF16 row block size overflow")?;
3290 let block_input = if self.bulk_p2p {
3291 let root_packed = {
3292 let _main = root.gpu.enter_main()?;
3293 let mut root_packed = root.uninit(block_len)?;
3294 root.copy_rows_strided(
3295 &root_input,
3296 &mut root_packed,
3297 matrix.canonical_chunk_cols,
3298 tokens,
3299 matrix.in_features,
3300 col_start,
3301 )?;
3302 root_packed
3303 };
3304 if rank == 0 {
3305 root_packed
3306 } else {
3307 {
3310 let _main = root.gpu.enter_main()?;
3311 root.stream().synchronize()?;
3312 }
3313 let engine = &self.ranks[rank];
3314 let _main = engine.gpu.enter_main()?;
3315 let mut block_input = engine.uninit(block_len)?;
3316 engine
3317 .stream()
3318 .memcpy_dtod(&root_packed, &mut block_input)?;
3319 root_packed_keepalive.push(root_packed);
3320 block_input
3321 }
3322 } else {
3323 let engine = &self.ranks[rank];
3324 let _main = engine.gpu.enter_main()?;
3325 let mut block_input = engine.uninit(block_len)?;
3326 for token in 0..tokens {
3327 let source_start = token * matrix.in_features + col_start;
3328 let source = root_input
3329 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3330 let destination_start = token * matrix.canonical_chunk_cols;
3331 let mut destination = block_input.slice_mut(
3332 destination_start..destination_start + matrix.canonical_chunk_cols,
3333 );
3334 engine.stream().memcpy_dtod(&source, &mut destination)?;
3335 }
3336 block_input
3337 };
3338 let partial = run_resident_bf16_rank_device(
3339 &self.ranks[rank],
3340 resident,
3341 &block_input,
3342 tokens,
3343 None,
3344 self.bulk_p2p,
3345 )?;
3346 block_input_keepalive.push(block_input);
3347 let root_partial = if rank == 0 {
3348 partial
3349 } else {
3350 {
3353 let engine = &self.ranks[rank];
3354 let _main = engine.gpu.enter_main()?;
3355 engine.stream().synchronize()?;
3356 }
3357 let _main = root.gpu.enter_main()?;
3358 let mut peer_partial = root.uninit(output_len)?;
3359 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3360 remote_partial_keepalive.push(partial);
3361 peer_partial
3362 };
3363 let next = {
3364 let _main = root.gpu.enter_main()?;
3365 let mut next = root.uninit(output_len)?;
3366 root.add(&reduced, &root_partial, &mut next, output_len)?;
3367 next
3368 };
3369 reduced = next;
3370 }
3371 }
3372 {
3373 let _main = root.gpu.enter_main()?;
3374 root.stream().synchronize()?;
3375 }
3376 drop(remote_partial_keepalive);
3377 drop(root_packed_keepalive);
3378 drop(block_input_keepalive);
3379 Ok(reduced)
3380 }
3381
3382 pub fn step_bf16_row_parallel_resident_root_device(
3385 &self,
3386 matrix: &ResidentStepBf16RowParallel,
3387 rank_activations: &[CudaSlice<f32>],
3388 tokens: usize,
3389 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3390 if self.ranks.len() > 1 && !self.native_p2p {
3391 return Err(
3392 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3393 );
3394 }
3395 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3396 let local_width = matrix.in_features / self.ranks.len();
3397 let shard_len = tokens
3398 .checked_mul(local_width)
3399 .ok_or("device Step BF16 row shard size overflow")?;
3400 if tokens == 0
3401 || rank_activations.len() != self.ranks.len()
3402 || rank_activations
3403 .iter()
3404 .zip(&self.ranks)
3405 .any(|(rows, engine)| {
3406 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3407 })
3408 {
3409 return Err("device Step BF16 row activation shard geometry changed".into());
3410 }
3411
3412 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3413 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3414 let mut partials = Vec::with_capacity(self.ranks.len());
3415 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3416 if blocks.len() != blocks_per_rank {
3417 return Err(format!(
3418 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3419 blocks.len()
3420 )
3421 .into());
3422 }
3423 let engine = &self.ranks[rank];
3424 let _main = engine.gpu.enter_main()?;
3425 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3426 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3427 for (block, resident) in blocks.iter().enumerate() {
3428 let block_len = tokens
3429 .checked_mul(matrix.canonical_chunk_cols)
3430 .ok_or("device Step BF16 row block size overflow")?;
3431 let mut block_input = engine.uninit(block_len)?;
3432 let local_col_start = block * matrix.canonical_chunk_cols;
3433 if self.bulk_p2p {
3434 engine.copy_rows_strided(
3435 &rank_activations[rank],
3436 &mut block_input,
3437 matrix.canonical_chunk_cols,
3438 tokens,
3439 local_width,
3440 local_col_start,
3441 )?;
3442 } else {
3443 for token in 0..tokens {
3444 let source_start = token * local_width + local_col_start;
3445 let source = rank_activations[rank]
3446 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3447 let destination_start = token * matrix.canonical_chunk_cols;
3448 let mut destination = block_input.slice_mut(
3449 destination_start..destination_start + matrix.canonical_chunk_cols,
3450 );
3451 engine.stream().memcpy_dtod(&source, &mut destination)?;
3452 }
3453 }
3454 let partial = run_resident_bf16_rank_device(
3455 engine,
3456 resident,
3457 &block_input,
3458 tokens,
3459 None,
3460 self.bulk_p2p,
3461 )?;
3462 rank_inputs.push(block_input);
3463 rank_partials.push(partial);
3464 }
3465 block_inputs.push(rank_inputs);
3466 partials.push(rank_partials);
3467 }
3468 for engine in self.ranks.iter().skip(1) {
3469 let _main = engine.gpu.enter_main()?;
3470 engine.stream().synchronize()?;
3471 }
3472
3473 let output_len = tokens
3474 .checked_mul(matrix.out_features)
3475 .ok_or("device Step BF16 row output size overflow")?;
3476 let root = &self.ranks[0];
3477 let _main = root.gpu.enter_main()?;
3478 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3479 let mut remote_partials = Vec::new();
3480 for (rank, rank_partials) in partials.into_iter().enumerate() {
3481 for partial in rank_partials {
3482 let root_partial = if rank == 0 {
3483 partial
3484 } else {
3485 let mut peer_partial = root.uninit(output_len)?;
3486 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3487 remote_partials.push(partial);
3488 peer_partial
3489 };
3490 let mut next = root.uninit(output_len)?;
3491 root.add(&reduced, &root_partial, &mut next, output_len)?;
3492 reduced = next;
3493 }
3494 }
3495 root.stream().synchronize()?;
3496 drop(remote_partials);
3497 drop(block_inputs);
3498 Ok(reduced)
3499 }
3500
3501 pub fn step_bf16_row_parallel_resident_replicated_device(
3503 &self,
3504 matrix: &ResidentStepBf16RowParallel,
3505 rank_activations: &[CudaSlice<f32>],
3506 tokens: usize,
3507 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3508 let reduced =
3509 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3510 let output_len = tokens
3511 .checked_mul(matrix.out_features)
3512 .ok_or("device Step BF16 row output size overflow")?;
3513 let mut ranks = Vec::with_capacity(self.ranks.len());
3514 ranks.push(reduced);
3515 for engine in self.ranks.iter().skip(1) {
3516 let _main = engine.gpu.enter_main()?;
3517 let mut peer_output = engine.uninit(output_len)?;
3518 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3519 ranks.push(peer_output);
3520 }
3521 Ok(ResidentReplicatedDeviceRows {
3522 ranks,
3523 tokens,
3524 width: matrix.out_features,
3525 })
3526 }
3527
3528 pub fn upload_expert(
3529 &self,
3530 gate: E4m3BlockMatrix<'_>,
3531 up: E4m3BlockMatrix<'_>,
3532 down: E4m3BlockMatrix<'_>,
3533 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3534 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3535 return Err("TP expert gate/up dimensions differ".into());
3536 }
3537 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3538 return Err(format!(
3539 "TP expert down {}x{} does not invert gate/up {}x{}",
3540 down.out_features, down.in_features, gate.out_features, gate.in_features
3541 )
3542 .into());
3543 }
3544 Ok(ResidentTpExpert {
3545 gate: self.upload_column_parallel(gate)?,
3546 up: self.upload_column_parallel(up)?,
3547 down: self.upload_row_parallel(down)?,
3548 input_width: gate.in_features,
3549 expert_width: gate.out_features,
3550 })
3551 }
3552
3553 pub fn run_expert(
3554 &self,
3555 expert: &ResidentTpExpert,
3556 input: &[f32],
3557 tokens: usize,
3558 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3559 validate_activations(input, tokens, expert.input_width)?;
3560 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3561 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3562 let activated: Vec<f32> = gate
3563 .gathered
3564 .iter()
3565 .zip(&up.gathered)
3566 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3567 .collect();
3568 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3569 Ok(self
3570 .row_parallel_resident(&expert.down, &activated, tokens)?
3571 .reduced)
3572 }
3573
3574 pub fn upload_expert_parallel(
3575 &self,
3576 gate: E4m3ExpertBank<'_>,
3577 up: E4m3ExpertBank<'_>,
3578 down: E4m3ExpertBank<'_>,
3579 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3580 gate.validate()?;
3581 up.validate()?;
3582 down.validate()?;
3583 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3584 return Err("EP gate/up/down expert counts differ".into());
3585 }
3586 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3587 return Err("EP gate/up dimensions differ".into());
3588 }
3589 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3590 return Err(format!(
3591 "EP down {}x{} does not invert gate/up {}x{}",
3592 down.out_features, down.in_features, gate.out_features, gate.in_features
3593 )
3594 .into());
3595 }
3596 if gate.expert_count % self.ranks.len() != 0 {
3597 return Err(format!(
3598 "EP expert count {} is not divisible by {} ranks",
3599 gate.expert_count,
3600 self.ranks.len()
3601 )
3602 .into());
3603 }
3604
3605 let per_rank = gate.expert_count / self.ranks.len();
3606 let mut ranks = Vec::with_capacity(self.ranks.len());
3607 for (rank, engine) in self.ranks.iter().enumerate() {
3608 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3609 ranks.push(ResidentEpRank {
3610 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3611 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3612 down: upload_expert_bank_rank(engine, down, expert_range)?,
3613 });
3614 }
3615 Ok(ResidentExpertParallel {
3616 ranks,
3617 expert_count: gate.expert_count,
3618 input_width: gate.in_features,
3619 expert_width: gate.out_features,
3620 })
3621 }
3622
3623 #[allow(clippy::too_many_arguments)]
3629 pub fn prepare_step_grouped_fp8_gate(
3630 &self,
3631 gate: E4m3ExpertBank<'_>,
3632 up: E4m3ExpertBank<'_>,
3633 down: E4m3ExpertBank<'_>,
3634 input: &[f32],
3635 tokens: usize,
3636 selected: &[usize],
3637 activation_limit: Option<f32>,
3638 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3639 gate.validate()?;
3640 up.validate()?;
3641 down.validate()?;
3642 validate_step_expert_activation_limit(activation_limit)?;
3643 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3644 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3645 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3646 {
3647 return Err(format!(
3648 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3649 got gate/up/down={}/{}/{}",
3650 gate.expert_count, up.expert_count, down.expert_count,
3651 )
3652 .into());
3653 }
3654 if gate.in_features != up.in_features
3655 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3656 || up.out_features != STEP_GROUPED_FP8_WIDTH
3657 || down.in_features != STEP_GROUPED_FP8_WIDTH
3658 || down.out_features != gate.in_features
3659 {
3660 return Err(format!(
3661 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3662 gate.out_features,
3663 gate.in_features,
3664 up.out_features,
3665 up.in_features,
3666 down.out_features,
3667 down.in_features,
3668 )
3669 .into());
3670 }
3671 validate_activations(input, tokens, gate.in_features)?;
3672 let pairs = tokens
3673 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3674 .ok_or("official Step grouped FP8 route count overflow")?;
3675 if selected.len() != pairs {
3676 return Err(format!(
3677 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3678 ({pairs})",
3679 selected.len()
3680 )
3681 .into());
3682 }
3683 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3684 let mut unique = routes.to_vec();
3685 unique.sort_unstable();
3686 unique.dedup();
3687 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3688 return Err(format!(
3689 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3690 {routes:?}"
3691 )
3692 .into());
3693 }
3694 }
3695
3696 let engine = self
3697 .ranks
3698 .first()
3699 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3700 let _main = engine.gpu.enter_main()?;
3701 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3702 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3703 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3704 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3705 let input = engine.htod(input)?;
3706 let route_csr = ExpertCsr::from_token_routes(
3707 STEP_GROUPED_FP8_EXPERTS,
3708 tokens,
3709 STEP_GROUPED_FP8_TOP_K,
3710 selected,
3711 )?
3712 .upload(engine)?;
3713 let pair_rows = (0..pairs).collect::<Vec<_>>();
3714 let down_csr =
3715 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3716 .upload(engine)?;
3717 let gate_workspace =
3718 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3719 let up_workspace =
3720 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3721 let down_workspace =
3722 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3723 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3724 Ok(PreparedStepGroupedFp8Gate {
3725 device: engine.ctx().ordinal(),
3726 gate,
3727 up,
3728 down,
3729 input,
3730 route_csr,
3731 down_csr,
3732 gate_workspace,
3733 up_workspace,
3734 down_workspace,
3735 activation,
3736 activation_limit,
3737 tokens,
3738 pairs,
3739 })
3740 }
3741
3742 pub fn run_step_grouped_fp8_gate(
3744 &self,
3745 plan: &mut PreparedStepGroupedFp8Gate,
3746 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3747 let engine = self
3748 .ranks
3749 .first()
3750 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3751 if engine.ctx().ordinal() != plan.device {
3752 return Err(format!(
3753 "official Step grouped FP8 plan device {} != rank-zero device {}",
3754 plan.device,
3755 engine.ctx().ordinal()
3756 )
3757 .into());
3758 }
3759 let _main = engine.gpu.enter_main()?;
3760
3761 plan.gate_workspace.quantize(engine, &plan.input)?;
3762 plan.gate_workspace.project(
3763 engine,
3764 &plan.gate.codes,
3765 &plan.gate.scales,
3766 &plan.route_csr,
3767 plan.gate.code_stride,
3768 plan.gate.scale_stride,
3769 1.0,
3770 )?;
3771 plan.up_workspace.quantize(engine, &plan.input)?;
3772 plan.up_workspace.project(
3773 engine,
3774 &plan.up.codes,
3775 &plan.up.scales,
3776 &plan.route_csr,
3777 plan.up.code_stride,
3778 plan.up.scale_stride,
3779 1.0,
3780 )?;
3781 if let Some(limit) = plan.activation_limit {
3782 engine.silu_clamped_mul_host_expf(
3783 plan.gate_workspace.output(),
3784 plan.up_workspace.output(),
3785 limit,
3786 &mut plan.activation,
3787 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3788 )?;
3789 } else {
3790 engine.silu_mul_host_expf(
3791 plan.gate_workspace.output(),
3792 plan.up_workspace.output(),
3793 &mut plan.activation,
3794 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3795 )?;
3796 }
3797 plan.down_workspace.quantize(engine, &plan.activation)?;
3798 plan.down_workspace.project(
3799 engine,
3800 &plan.down.codes,
3801 &plan.down.scales,
3802 &plan.down_csr,
3803 plan.down.code_stride,
3804 plan.down.scale_stride,
3805 1.0,
3806 )?;
3807
3808 Ok(StepGroupedFp8ProjectionOutput {
3809 gate: engine.dtoh(plan.gate_workspace.output())?,
3810 up: engine.dtoh(plan.up_workspace.output())?,
3811 down: engine.dtoh(plan.down_workspace.output())?,
3812 })
3813 }
3814
3815 pub fn prepare_step_grouped_expert_parallel_gate(
3816 &self,
3817 experts: &ResidentExpertParallel,
3818 input: &[f32],
3819 tokens: usize,
3820 selected: &[usize],
3821 activation_limit: Option<f32>,
3822 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3823 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3824 experts,
3825 input,
3826 tokens,
3827 selected,
3828 activation_limit,
3829 tokens,
3830 )
3831 }
3832
3833 #[allow(clippy::too_many_arguments)]
3834 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3835 &self,
3836 experts: &ResidentExpertParallel,
3837 input: &[f32],
3838 tokens: usize,
3839 selected: &[usize],
3840 activation_limit: Option<f32>,
3841 max_tokens: usize,
3842 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3843 if !self.native_p2p || !self.ep_device_arithmetic {
3844 return Err(
3845 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3846 );
3847 }
3848 validate_step_expert_activation_limit(activation_limit)?;
3849 validate_ep_residency(&self.ranks, experts)?;
3850 validate_activations(input, tokens, experts.input_width)?;
3851 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3852 return Err(format!(
3853 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3854 )
3855 .into());
3856 }
3857 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3858 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3859 {
3860 return Err(format!(
3861 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3862 STEP_GROUPED_FP8_EXPERTS,
3863 STEP_GROUPED_FP8_WIDTH,
3864 experts.expert_count,
3865 experts.expert_width,
3866 )
3867 .into());
3868 }
3869 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3870 let max_pairs = max_tokens
3871 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3872 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
3873 let input_capacity = max_tokens
3874 .checked_mul(experts.input_width)
3875 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
3876
3877 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3878 for engine in &self.ranks {
3879 let _main = engine.gpu.enter_main()?;
3880 rank_inputs.push(engine.uninit(input_capacity)?);
3881 }
3882
3883 let mut owners = Vec::with_capacity(self.ranks.len());
3884 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
3885 if rank.gate.expert_range != rank.up.expert_range
3886 || rank.gate.expert_range != rank.down.expert_range
3887 {
3888 return Err(format!(
3889 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
3890 owner_rank
3891 )
3892 .into());
3893 }
3894 let local_experts = rank.gate.expert_range.len();
3895 let engine = &self.ranks[owner_rank];
3896 let _main = engine.gpu.enter_main()?;
3897 let route_csr =
3898 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
3899 let down_csr =
3900 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
3901 let gate_workspace = Fp8GroupedWorkspace::new(
3902 engine,
3903 experts.input_width,
3904 experts.expert_width,
3905 max_tokens,
3906 max_pairs,
3907 )?;
3908 let up_workspace = Fp8GroupedWorkspace::new(
3909 engine,
3910 experts.input_width,
3911 experts.expert_width,
3912 max_tokens,
3913 max_pairs,
3914 )?;
3915 let down_workspace = Fp8GroupedWorkspace::new(
3916 engine,
3917 experts.expert_width,
3918 experts.input_width,
3919 max_pairs,
3920 max_pairs,
3921 )?;
3922 let activation = engine.uninit(
3923 max_pairs
3924 .checked_mul(experts.expert_width)
3925 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
3926 )?;
3927 owners.push(PreparedStepGroupedExpertOwner {
3928 rank: owner_rank,
3929 global_pairs: Vec::new(),
3930 route_csr,
3931 down_csr,
3932 gate_workspace,
3933 up_workspace,
3934 down_workspace,
3935 activation,
3936 });
3937 }
3938
3939 let mut plan = PreparedStepGroupedExpertParallelGate {
3940 rank_inputs,
3941 owners,
3942 activation_limit,
3943 tokens: 0,
3944 pairs: 0,
3945 max_tokens,
3946 max_pairs,
3947 input_width: experts.input_width,
3948 expert_width: experts.expert_width,
3949 generation: 0,
3950 executed_generation: None,
3951 ready: false,
3952 };
3953 self.refresh_step_grouped_expert_parallel_gate(
3954 experts, &mut plan, input, tokens, selected,
3955 )?;
3956 Ok(plan)
3957 }
3958
3959 fn prepare_step_grouped_expert_parallel_refresh(
3960 &self,
3961 experts: &ResidentExpertParallel,
3962 plan: &PreparedStepGroupedExpertParallelGate,
3963 tokens: usize,
3964 selected: &[usize],
3965 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
3966 {
3967 validate_ep_residency(&self.ranks, experts)?;
3968 if plan.rank_inputs.len() != self.ranks.len()
3969 || plan.owners.len() != self.ranks.len()
3970 || plan.input_width != experts.input_width
3971 || plan.expert_width != experts.expert_width
3972 || tokens > plan.max_tokens
3973 {
3974 return Err(format!(
3975 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
3976 input={}/{} expert={}/{} tokens={}/{}",
3977 plan.rank_inputs.len(),
3978 self.ranks.len(),
3979 plan.owners.len(),
3980 self.ranks.len(),
3981 plan.input_width,
3982 experts.input_width,
3983 plan.expert_width,
3984 experts.expert_width,
3985 tokens,
3986 plan.max_tokens,
3987 )
3988 .into());
3989 }
3990 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3991 if pairs > plan.max_pairs {
3992 return Err(format!(
3993 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
3994 plan.max_pairs
3995 )
3996 .into());
3997 }
3998 let next_generation = plan
3999 .generation
4000 .checked_add(1)
4001 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4002 let owner_routes = partition_expert_owner_routes(
4003 experts.expert_count,
4004 self.ranks.len(),
4005 tokens,
4006 STEP_GROUPED_FP8_TOP_K,
4007 selected,
4008 )?;
4009 let mut schedules = Vec::with_capacity(self.ranks.len());
4010 for routes in owner_routes {
4011 if routes.selected.is_empty() {
4012 schedules.push(None);
4013 continue;
4014 }
4015 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
4016 let local_pairs = routes.selected.len();
4017 let route_csr = ExpertCsr::from_pair_rows(
4018 local_experts,
4019 tokens,
4020 &routes.selected,
4021 &routes.token_rows,
4022 )?;
4023 let down_rows = (0..local_pairs).collect::<Vec<_>>();
4024 let down_csr = ExpertCsr::from_pair_rows(
4025 local_experts,
4026 local_pairs,
4027 &routes.selected,
4028 &down_rows,
4029 )?;
4030 schedules.push(Some(StepGroupedExpertOwnerSchedule {
4031 global_pairs: routes.global_pairs,
4032 route_csr,
4033 down_csr,
4034 }));
4035 }
4036 Ok((pairs, next_generation, schedules))
4037 }
4038
4039 fn commit_step_grouped_expert_parallel_refresh(
4040 &self,
4041 plan: &mut PreparedStepGroupedExpertParallelGate,
4042 tokens: usize,
4043 pairs: usize,
4044 next_generation: u64,
4045 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
4046 ) -> Result<(), Box<dyn std::error::Error>> {
4047 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
4048 let engine = &self.ranks[owner.rank];
4049 let _main = engine.gpu.enter_main()?;
4050 if let Some(schedule) = schedule {
4051 owner.route_csr.refresh(engine, &schedule.route_csr)?;
4052 owner.down_csr.refresh(engine, &schedule.down_csr)?;
4053 owner.global_pairs = schedule.global_pairs;
4054 } else {
4055 owner.route_csr.clear();
4056 owner.down_csr.clear();
4057 owner.global_pairs.clear();
4058 }
4059 }
4060 plan.tokens = tokens;
4061 plan.pairs = pairs;
4062 plan.generation = next_generation;
4063 plan.ready = true;
4064 Ok(())
4065 }
4066
4067 pub fn refresh_step_grouped_expert_parallel_gate(
4068 &self,
4069 experts: &ResidentExpertParallel,
4070 plan: &mut PreparedStepGroupedExpertParallelGate,
4071 input: &[f32],
4072 tokens: usize,
4073 selected: &[usize],
4074 ) -> Result<(), Box<dyn std::error::Error>> {
4075 validate_activations(input, tokens, experts.input_width)?;
4076 let (pairs, next_generation, schedules) =
4077 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4078
4079 plan.ready = false;
4080 plan.executed_generation = None;
4081 {
4082 let root = &self.ranks[0];
4083 let _main = root.gpu.enter_main()?;
4084 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
4085 root.stream().memcpy_htod(input, &mut destination)?;
4086 root.stream().synchronize()?;
4087 }
4088 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4089 let root_input = &root_inputs[0];
4090 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4091 let engine = &self.ranks[rank + 1];
4092 let _main = engine.gpu.enter_main()?;
4093 let mut destination = peer_input.slice_mut(0..input.len());
4094 engine
4095 .stream()
4096 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
4097 }
4098 self.commit_step_grouped_expert_parallel_refresh(
4099 plan,
4100 tokens,
4101 pairs,
4102 next_generation,
4103 schedules,
4104 )
4105 }
4106
4107 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
4112 &self,
4113 experts: &ResidentExpertParallel,
4114 plan: &mut PreparedStepGroupedExpertParallelGate,
4115 input: &CudaSlice<f32>,
4116 tokens: usize,
4117 selected: &[usize],
4118 ) -> Result<(), Box<dyn std::error::Error>> {
4119 let input_values = tokens
4120 .checked_mul(experts.input_width)
4121 .ok_or("Step owner-grouped FP8 input size overflow")?;
4122 let root = self
4123 .ranks
4124 .first()
4125 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
4126 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
4127 return Err(format!(
4128 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
4129 device {}",
4130 input.len(),
4131 input.ordinal(),
4132 input_values,
4133 root.ctx().ordinal(),
4134 )
4135 .into());
4136 }
4137 let (pairs, next_generation, schedules) =
4138 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
4139
4140 plan.ready = false;
4141 plan.executed_generation = None;
4142 {
4143 let _main = root.gpu.enter_main()?;
4144 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
4145 root.stream()
4146 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
4147 root.stream().synchronize()?;
4148 }
4149 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
4150 let root_input = &root_inputs[0];
4151 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
4152 let engine = &self.ranks[rank + 1];
4153 let _main = engine.gpu.enter_main()?;
4154 let mut destination = peer_input.slice_mut(0..input_values);
4155 engine
4156 .stream()
4157 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
4158 }
4159 self.commit_step_grouped_expert_parallel_refresh(
4160 plan,
4161 tokens,
4162 pairs,
4163 next_generation,
4164 schedules,
4165 )
4166 }
4167
4168 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
4173 &self,
4174 experts: &ResidentExpertParallel,
4175 plan: &mut PreparedStepGroupedExpertParallelGate,
4176 input: &ResidentReplicatedDeviceRows,
4177 ) -> Result<(), Box<dyn std::error::Error>> {
4178 validate_ep_residency(&self.ranks, experts)?;
4179 validate_replicated_device_rows(&self.ranks, input)?;
4180 if !plan.ready
4181 || input.tokens != plan.tokens
4182 || input.width != plan.input_width
4183 || input.tokens > plan.max_tokens
4184 || plan.rank_inputs.len() != self.ranks.len()
4185 || plan.owners.len() != self.ranks.len()
4186 || plan.input_width != experts.input_width
4187 || plan.expert_width != experts.expert_width
4188 {
4189 return Err("Step owner-grouped replicated input geometry changed".into());
4190 }
4191 let values = input
4192 .tokens
4193 .checked_mul(input.width)
4194 .ok_or("Step owner-grouped replicated input size overflow")?;
4195 let next_generation = plan
4196 .generation
4197 .checked_add(1)
4198 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4199 plan.ready = false;
4200 plan.executed_generation = None;
4201 for (rank, engine) in self.ranks.iter().enumerate() {
4202 let _main = engine.gpu.enter_main()?;
4203 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4204 engine
4205 .stream()
4206 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4207 }
4208 plan.generation = next_generation;
4209 plan.ready = true;
4210 Ok(())
4211 }
4212
4213 pub fn execute_step_grouped_expert_parallel_gate(
4214 &self,
4215 experts: &ResidentExpertParallel,
4216 plan: &mut PreparedStepGroupedExpertParallelGate,
4217 ) -> Result<(), Box<dyn std::error::Error>> {
4218 validate_ep_residency(&self.ranks, experts)?;
4219 if !plan.ready
4220 || plan.rank_inputs.len() != self.ranks.len()
4221 || plan.owners.len() != self.ranks.len()
4222 || plan.input_width != experts.input_width
4223 || plan.expert_width != experts.expert_width
4224 {
4225 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4226 }
4227 plan.executed_generation = None;
4228
4229 for owner in &mut plan.owners {
4230 if owner.global_pairs.is_empty() {
4231 continue;
4232 }
4233 let engine = &self.ranks[owner.rank];
4234 let bank = &experts.ranks[owner.rank];
4235 let _main = engine.gpu.enter_main()?;
4236 let local_pairs = owner.global_pairs.len();
4237 owner.gate_workspace.quantize_for_shape(
4238 engine,
4239 &plan.rank_inputs[owner.rank],
4240 plan.tokens,
4241 local_pairs,
4242 )?;
4243 owner.gate_workspace.project(
4244 engine,
4245 &bank.gate.codes,
4246 &bank.gate.scales,
4247 &owner.route_csr,
4248 bank.gate.code_stride,
4249 bank.gate.scale_stride,
4250 1.0,
4251 )?;
4252 owner.up_workspace.quantize_for_shape(
4253 engine,
4254 &plan.rank_inputs[owner.rank],
4255 plan.tokens,
4256 local_pairs,
4257 )?;
4258 owner.up_workspace.project(
4259 engine,
4260 &bank.up.codes,
4261 &bank.up.scales,
4262 &owner.route_csr,
4263 bank.up.code_stride,
4264 bank.up.scale_stride,
4265 1.0,
4266 )?;
4267 }
4268 for owner in &mut plan.owners {
4269 if owner.global_pairs.is_empty() {
4270 continue;
4271 }
4272 let engine = &self.ranks[owner.rank];
4273 let _main = engine.gpu.enter_main()?;
4274 let values = owner.global_pairs.len() * plan.expert_width;
4275 if let Some(limit) = plan.activation_limit {
4276 engine.silu_clamped_mul_host_expf(
4277 owner.gate_workspace.output(),
4278 owner.up_workspace.output(),
4279 limit,
4280 &mut owner.activation,
4281 values,
4282 )?;
4283 } else {
4284 engine.silu_mul_host_expf(
4285 owner.gate_workspace.output(),
4286 owner.up_workspace.output(),
4287 &mut owner.activation,
4288 values,
4289 )?;
4290 }
4291 }
4292 for owner in &mut plan.owners {
4293 if owner.global_pairs.is_empty() {
4294 continue;
4295 }
4296 let engine = &self.ranks[owner.rank];
4297 let bank = &experts.ranks[owner.rank];
4298 let _main = engine.gpu.enter_main()?;
4299 let local_pairs = owner.global_pairs.len();
4300 owner.down_workspace.quantize_for_shape(
4301 engine,
4302 &owner.activation,
4303 local_pairs,
4304 local_pairs,
4305 )?;
4306 owner.down_workspace.project(
4307 engine,
4308 &bank.down.codes,
4309 &bank.down.scales,
4310 &owner.down_csr,
4311 bank.down.code_stride,
4312 bank.down.scale_stride,
4313 1.0,
4314 )?;
4315 }
4316 plan.executed_generation = Some(plan.generation);
4317 Ok(())
4318 }
4319
4320 pub fn collect_step_grouped_expert_parallel_gate(
4321 &self,
4322 plan: &PreparedStepGroupedExpertParallelGate,
4323 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4324 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4325 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4326 }
4327 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4328 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4329 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4330 for owner in &plan.owners {
4331 if owner.global_pairs.is_empty() {
4332 continue;
4333 }
4334 let engine = &self.ranks[owner.rank];
4335 let _main = engine.gpu.enter_main()?;
4336 let owner_gate = engine.dtoh_view(
4337 &owner
4338 .gate_workspace
4339 .output()
4340 .slice(0..owner.gate_workspace.output_len()),
4341 )?;
4342 let owner_up = engine.dtoh_view(
4343 &owner
4344 .up_workspace
4345 .output()
4346 .slice(0..owner.up_workspace.output_len()),
4347 )?;
4348 let owner_down = engine.dtoh_view(
4349 &owner
4350 .down_workspace
4351 .output()
4352 .slice(0..owner.down_workspace.output_len()),
4353 )?;
4354 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4355 let local_expert = local_pair * plan.expert_width;
4356 let global_expert = global_pair * plan.expert_width;
4357 gate[global_expert..global_expert + plan.expert_width]
4358 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4359 up[global_expert..global_expert + plan.expert_width]
4360 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4361
4362 let local_hidden = local_pair * plan.input_width;
4363 let global_hidden = global_pair * plan.input_width;
4364 down[global_hidden..global_hidden + plan.input_width]
4365 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4366 }
4367 }
4368 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4369 }
4370
4371 pub fn run_step_grouped_expert_parallel_gate(
4372 &self,
4373 experts: &ResidentExpertParallel,
4374 plan: &mut PreparedStepGroupedExpertParallelGate,
4375 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4376 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4377 self.collect_step_grouped_expert_parallel_gate(plan)
4378 }
4379
4380 pub fn prepare_step_grouped_expert_parallel_combine(
4381 &self,
4382 plan: &PreparedStepGroupedExpertParallelGate,
4383 route_weights: &[f32],
4384 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4385 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4386 return Err(
4387 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4388 );
4389 }
4390 let owner_pairs = plan
4391 .owners
4392 .iter()
4393 .map(|owner| owner.global_pairs.as_slice())
4394 .collect::<Vec<_>>();
4395 let shape = validate_weighted_route_combine(
4396 plan.input_width,
4397 STEP_GROUPED_FP8_TOP_K,
4398 plan.max_tokens,
4399 plan.tokens,
4400 &owner_pairs,
4401 route_weights,
4402 )?;
4403 if shape.max_pairs != plan.max_pairs {
4404 return Err(format!(
4405 "Step owner-grouped combine capacity {} != projection capacity {}",
4406 shape.max_pairs, plan.max_pairs
4407 )
4408 .into());
4409 }
4410 let root = self
4411 .ranks
4412 .first()
4413 .ok_or("Step owner-grouped combine has no root rank")?;
4414 let slot_values = shape
4415 .max_pairs
4416 .checked_mul(plan.input_width)
4417 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4418 let output_values = plan
4419 .max_tokens
4420 .checked_mul(plan.input_width)
4421 .ok_or("Step owner-grouped combine output capacity overflow")?;
4422 let (root_device, owners, peer_staging, slots, weights, output) = {
4423 let _main = root.gpu.enter_main()?;
4424 let mut owners = Vec::with_capacity(plan.owners.len());
4425 for _ in &plan.owners {
4426 owners.push(PreparedPeerWeightedRouteOwner {
4427 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4428 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4429 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4430 active_pairs: 0,
4431 });
4432 }
4433 (
4434 root.ctx().ordinal(),
4435 owners,
4436 root.uninit(slot_values)?,
4437 root.uninit(slot_values)?,
4438 root.uninit(shape.max_pairs)?,
4439 root.uninit(output_values)?,
4440 )
4441 };
4442 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4443 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4444 for engine in self.ranks.iter().skip(1) {
4445 let _main = engine.gpu.enter_main()?;
4446 peer_devices.push(engine.ctx().ordinal());
4447 peer_outputs.push(engine.uninit(output_values)?);
4448 }
4449 let mut combine = PreparedPeerWeightedRouteCombine {
4450 root_device,
4451 owners,
4452 peer_staging,
4453 slots,
4454 weights,
4455 output,
4456 peer_devices,
4457 peer_outputs,
4458 width: plan.input_width,
4459 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4460 max_tokens: plan.max_tokens,
4461 max_pairs: shape.max_pairs,
4462 tokens: 0,
4463 pairs: 0,
4464 projection_generation: 0,
4465 output_generation: None,
4466 broadcast_generation: None,
4467 ready: false,
4468 };
4469 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4470 Ok(combine)
4471 }
4472
4473 pub fn refresh_step_grouped_expert_parallel_combine(
4474 &self,
4475 plan: &PreparedStepGroupedExpertParallelGate,
4476 combine: &mut PreparedPeerWeightedRouteCombine,
4477 route_weights: &[f32],
4478 ) -> Result<(), Box<dyn std::error::Error>> {
4479 let output_capacity = combine
4480 .max_tokens
4481 .checked_mul(combine.width)
4482 .ok_or("Step owner-grouped combine output capacity overflow")?;
4483 if !plan.ready
4484 || combine.owners.len() != plan.owners.len()
4485 || combine.peer_devices.len() + 1 != self.ranks.len()
4486 || combine.peer_outputs.len() + 1 != self.ranks.len()
4487 || combine.width != plan.input_width
4488 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4489 || combine.max_tokens != plan.max_tokens
4490 || combine.max_pairs != plan.max_pairs
4491 || combine.output.len() < output_capacity
4492 || combine
4493 .peer_outputs
4494 .iter()
4495 .any(|output| output.len() < output_capacity)
4496 {
4497 return Err("Step owner-grouped combine/projection geometry changed".into());
4498 }
4499 if self
4500 .ranks
4501 .iter()
4502 .skip(1)
4503 .zip(&combine.peer_devices)
4504 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4505 {
4506 return Err("Step owner-grouped combine peer devices changed".into());
4507 }
4508 let owner_pairs = plan
4509 .owners
4510 .iter()
4511 .map(|owner| owner.global_pairs.as_slice())
4512 .collect::<Vec<_>>();
4513 let shape = validate_weighted_route_combine(
4514 combine.width,
4515 combine.experts_per_token,
4516 combine.max_tokens,
4517 plan.tokens,
4518 &owner_pairs,
4519 route_weights,
4520 )?;
4521 if shape.max_pairs != combine.max_pairs {
4522 return Err("Step owner-grouped combine capacity changed during refresh".into());
4523 }
4524 let metadata = owner_pairs
4525 .iter()
4526 .map(|pairs| {
4527 let token_rows = pairs
4528 .iter()
4529 .map(|&pair| (pair / combine.experts_per_token) as i32)
4530 .collect::<Vec<_>>();
4531 let slots = pairs
4532 .iter()
4533 .map(|&pair| (pair % combine.experts_per_token) as i32)
4534 .collect::<Vec<_>>();
4535 let weights = pairs
4536 .iter()
4537 .map(|&pair| route_weights[pair])
4538 .collect::<Vec<_>>();
4539 (token_rows, slots, weights)
4540 })
4541 .collect::<Vec<_>>();
4542
4543 combine.ready = false;
4544 combine.output_generation = None;
4545 combine.broadcast_generation = None;
4546 let root = self
4547 .ranks
4548 .first()
4549 .ok_or("Step owner-grouped combine has no root rank")?;
4550 let _main = root.gpu.enter_main()?;
4551 if root.ctx().ordinal() != combine.root_device {
4552 return Err(format!(
4553 "Step owner-grouped combine root device changed {} != {}",
4554 root.ctx().ordinal(),
4555 combine.root_device
4556 )
4557 .into());
4558 }
4559 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4560 if token_rows.is_empty() {
4561 owner.active_pairs = 0;
4562 continue;
4563 }
4564 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4565 root.htod_i32_into(&mut owner.slots, &slots)?;
4566 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4567 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4568 owner.active_pairs = token_rows.len();
4569 }
4570 combine.tokens = plan.tokens;
4571 combine.pairs = shape.pairs;
4572 combine.projection_generation = plan.generation;
4573 combine.ready = true;
4574 Ok(())
4575 }
4576
4577 pub fn execute_step_grouped_expert_parallel_combine(
4578 &self,
4579 plan: &PreparedStepGroupedExpertParallelGate,
4580 combine: &mut PreparedPeerWeightedRouteCombine,
4581 ) -> Result<(), Box<dyn std::error::Error>> {
4582 if !plan.ready
4583 || plan.executed_generation != Some(plan.generation)
4584 || !combine.ready
4585 || combine.tokens != plan.tokens
4586 || combine.pairs != plan.pairs
4587 || combine.width != plan.input_width
4588 || combine.owners.len() != plan.owners.len()
4589 || combine.projection_generation != plan.generation
4590 {
4591 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4592 }
4593 combine.output_generation = None;
4594 combine.broadcast_generation = None;
4595 for owner in &plan.owners {
4596 if owner.rank == 0 || owner.global_pairs.is_empty() {
4597 continue;
4598 }
4599 let engine = &self.ranks[owner.rank];
4600 let _main = engine.gpu.enter_main()?;
4601 engine.stream().synchronize()?;
4602 }
4603 let root = self
4604 .ranks
4605 .first()
4606 .ok_or("Step owner-grouped combine has no root rank")?;
4607 let _main = root.gpu.enter_main()?;
4608 if root.ctx().ordinal() != combine.root_device {
4609 return Err("Step owner-grouped combine is not resident on the root device".into());
4610 }
4611 for (index, owner) in plan.owners.iter().enumerate() {
4612 let metadata = &combine.owners[index];
4613 if owner.global_pairs.len() != metadata.active_pairs {
4614 return Err(format!(
4615 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4616 owner.global_pairs.len(),
4617 metadata.active_pairs
4618 )
4619 .into());
4620 }
4621 if metadata.active_pairs == 0 {
4622 continue;
4623 }
4624 let values = metadata
4625 .active_pairs
4626 .checked_mul(combine.width)
4627 .ok_or("Step owner-grouped combine peer value count overflow")?;
4628 if owner.rank == 0 {
4629 root.scatter_slot(
4630 owner.down_workspace.output(),
4631 &metadata.token_rows,
4632 &metadata.slots,
4633 &metadata.weights,
4634 &mut combine.slots,
4635 &mut combine.weights,
4636 combine.width,
4637 combine.experts_per_token,
4638 metadata.active_pairs,
4639 )?;
4640 } else {
4641 let source = owner.down_workspace.output().slice(0..values);
4642 let mut destination = combine.peer_staging.slice_mut(0..values);
4643 root.stream().memcpy_dtod(&source, &mut destination)?;
4644 root.scatter_slot(
4645 &combine.peer_staging,
4646 &metadata.token_rows,
4647 &metadata.slots,
4648 &metadata.weights,
4649 &mut combine.slots,
4650 &mut combine.weights,
4651 combine.width,
4652 combine.experts_per_token,
4653 metadata.active_pairs,
4654 )?;
4655 }
4656 }
4657 root.reduce_slots_host(
4658 &combine.slots,
4659 &combine.weights,
4660 &mut combine.output,
4661 combine.width,
4662 combine.experts_per_token,
4663 combine.tokens,
4664 )?;
4665 combine.output_generation = Some(plan.generation);
4666 Ok(())
4667 }
4668
4669 pub fn collect_step_grouped_expert_parallel_combine(
4670 &self,
4671 plan: &PreparedStepGroupedExpertParallelGate,
4672 combine: &PreparedPeerWeightedRouteCombine,
4673 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4674 if !plan.ready
4675 || combine.output_generation != Some(plan.generation)
4676 || combine.projection_generation != plan.generation
4677 {
4678 return Err("Step owner-grouped combine output is stale or has not executed".into());
4679 }
4680 let root = self
4681 .ranks
4682 .first()
4683 .ok_or("Step owner-grouped combine has no root rank")?;
4684 let _main = root.gpu.enter_main()?;
4685 if root.ctx().ordinal() != combine.root_device {
4686 return Err("Step owner-grouped combine is not resident on the root device".into());
4687 }
4688 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4689 }
4690
4691 pub fn copy_step_grouped_expert_parallel_combine_root(
4696 &self,
4697 plan: &PreparedStepGroupedExpertParallelGate,
4698 combine: &PreparedPeerWeightedRouteCombine,
4699 destination: &Engine,
4700 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4701 if !plan.ready
4702 || combine.output_generation != Some(plan.generation)
4703 || combine.projection_generation != plan.generation
4704 {
4705 return Err("Step owner-grouped combine output is stale or has not executed".into());
4706 }
4707 let root = self
4708 .ranks
4709 .first()
4710 .ok_or("Step owner-grouped combine has no root rank")?;
4711 if root.ctx().ordinal() != combine.root_device
4712 || destination.ctx().ordinal() != combine.root_device
4713 {
4714 return Err(format!(
4715 "Step owner-grouped combine root/destination devices {}/{} != {}",
4716 root.ctx().ordinal(),
4717 destination.ctx().ordinal(),
4718 combine.root_device,
4719 )
4720 .into());
4721 }
4722 let values = combine
4723 .tokens
4724 .checked_mul(combine.width)
4725 .ok_or("Step owner-grouped combine copy size overflow")?;
4726 {
4727 let _main = root.gpu.enter_main()?;
4728 root.stream().synchronize()?;
4729 }
4730 let _main = destination.gpu.enter_main()?;
4731 let mut output = destination.uninit(values)?;
4732 destination
4733 .stream()
4734 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4735 Ok(output)
4736 }
4737
4738 pub fn broadcast_step_grouped_expert_parallel_combine(
4739 &self,
4740 plan: &PreparedStepGroupedExpertParallelGate,
4741 combine: &mut PreparedPeerWeightedRouteCombine,
4742 ) -> Result<(), Box<dyn std::error::Error>> {
4743 if !plan.ready
4744 || combine.output_generation != Some(plan.generation)
4745 || combine.projection_generation != plan.generation
4746 || combine.peer_devices.len() + 1 != self.ranks.len()
4747 || combine.peer_outputs.len() + 1 != self.ranks.len()
4748 {
4749 return Err("Step owner-grouped combine output cannot be broadcast".into());
4750 }
4751 combine.broadcast_generation = None;
4752 let values = combine
4753 .tokens
4754 .checked_mul(combine.width)
4755 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4756 {
4757 let root = self
4758 .ranks
4759 .first()
4760 .ok_or("Step owner-grouped combine has no root rank")?;
4761 let _main = root.gpu.enter_main()?;
4762 if root.ctx().ordinal() != combine.root_device {
4763 return Err("Step owner-grouped combine root device changed".into());
4764 }
4765 root.stream().synchronize()?;
4766 }
4767 let source = &combine.output;
4768 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4769 let engine = &self.ranks[index + 1];
4770 let _main = engine.gpu.enter_main()?;
4771 if engine.ctx().ordinal() != combine.peer_devices[index] {
4772 return Err(format!(
4773 "Step owner-grouped combine peer {} device changed",
4774 index + 1
4775 )
4776 .into());
4777 }
4778 let mut destination = destination_buffer.slice_mut(0..values);
4779 engine
4780 .stream()
4781 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4782 }
4783 combine.broadcast_generation = Some(plan.generation);
4784 Ok(())
4785 }
4786
4787 pub fn collect_step_grouped_expert_parallel_broadcast(
4788 &self,
4789 plan: &PreparedStepGroupedExpertParallelGate,
4790 combine: &PreparedPeerWeightedRouteCombine,
4791 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4792 if !plan.ready
4793 || combine.output_generation != Some(plan.generation)
4794 || combine.broadcast_generation != Some(plan.generation)
4795 || combine.peer_outputs.len() + 1 != self.ranks.len()
4796 {
4797 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4798 }
4799 let values = combine
4800 .tokens
4801 .checked_mul(combine.width)
4802 .ok_or("Step owner-grouped combine collection size overflow")?;
4803 let mut outputs = Vec::with_capacity(self.ranks.len());
4804 {
4805 let root = &self.ranks[0];
4806 let _main = root.gpu.enter_main()?;
4807 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4808 }
4809 for (index, output) in combine.peer_outputs.iter().enumerate() {
4810 let engine = &self.ranks[index + 1];
4811 let _main = engine.gpu.enter_main()?;
4812 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4813 }
4814 Ok(outputs)
4815 }
4816
4817 pub fn finish_step_grouped_expert_parallel_layer(
4819 &self,
4820 plan: &PreparedStepGroupedExpertParallelGate,
4821 combine: &PreparedPeerWeightedRouteCombine,
4822 shared: &ResidentReplicatedDeviceRows,
4823 residual: &ResidentReplicatedDeviceRows,
4824 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4825 validate_replicated_device_rows(&self.ranks, shared)?;
4826 validate_replicated_device_rows(&self.ranks, residual)?;
4827 if !plan.ready
4828 || plan.executed_generation != Some(plan.generation)
4829 || combine.output_generation != Some(plan.generation)
4830 || combine.broadcast_generation != Some(plan.generation)
4831 || combine.projection_generation != plan.generation
4832 || combine.peer_outputs.len() + 1 != self.ranks.len()
4833 || shared.tokens != combine.tokens
4834 || residual.tokens != combine.tokens
4835 || shared.width != combine.width
4836 || residual.width != combine.width
4837 {
4838 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4839 }
4840 let values = combine
4841 .tokens
4842 .checked_mul(combine.width)
4843 .ok_or("Step full-layer output size overflow")?;
4844 let mut ranks = Vec::with_capacity(self.ranks.len());
4845 for rank in 0..self.ranks.len() {
4846 let engine = &self.ranks[rank];
4847 let _main = engine.gpu.enter_main()?;
4848 let routed = if rank == 0 {
4849 &combine.output
4850 } else {
4851 &combine.peer_outputs[rank - 1]
4852 };
4853 let mut ffn = engine.uninit(values)?;
4854 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4855 let mut output = engine.uninit(values)?;
4856 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4857 ranks.push(output);
4858 }
4859 Ok(ResidentReplicatedDeviceRows {
4860 ranks,
4861 tokens: combine.tokens,
4862 width: combine.width,
4863 })
4864 }
4865
4866 pub fn run_step_grouped_expert_parallel_combine(
4867 &self,
4868 plan: &PreparedStepGroupedExpertParallelGate,
4869 combine: &mut PreparedPeerWeightedRouteCombine,
4870 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4871 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
4872 self.collect_step_grouped_expert_parallel_combine(plan, combine)
4873 }
4874
4875 pub fn upload_tensor_parallel(
4876 &self,
4877 gate: E4m3ExpertBank<'_>,
4878 up: E4m3ExpertBank<'_>,
4879 down: E4m3ExpertBank<'_>,
4880 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
4881 gate.validate()?;
4882 up.validate()?;
4883 down.validate()?;
4884 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4885 return Err("TP gate/up/down expert counts differ".into());
4886 }
4887 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4888 return Err("TP gate/up dimensions differ".into());
4889 }
4890 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4891 return Err(format!(
4892 "TP down {}x{} does not invert gate/up {}x{}",
4893 down.out_features, down.in_features, gate.out_features, gate.in_features
4894 )
4895 .into());
4896 }
4897 let tp = self.ranks.len();
4898 validate_column_bank_shape(gate, tp)?;
4899 validate_column_bank_shape(up, tp)?;
4900 validate_row_bank_shape(down, tp)?;
4901
4902 let mut gate_ranks = Vec::with_capacity(tp);
4903 let mut up_ranks = Vec::with_capacity(tp);
4904 let mut down_ranks = Vec::with_capacity(tp);
4905 for (rank, engine) in self.ranks.iter().enumerate() {
4906 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
4907 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
4908 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
4909 }
4910 Ok(ResidentTensorParallel {
4911 bank: ResidentTpExpertBank {
4912 gate: gate_ranks,
4913 up: up_ranks,
4914 down: down_ranks,
4915 expert_count: gate.expert_count,
4916 input_width: gate.in_features,
4917 expert_width: gate.out_features,
4918 },
4919 })
4920 }
4921
4922 pub fn run_tensor_parallel_routes(
4923 &self,
4924 experts: &ResidentTensorParallel,
4925 input: &[f32],
4926 tokens: usize,
4927 selected: &[usize],
4928 route_weights: &[f32],
4929 experts_per_token: usize,
4930 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4931 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
4932 validate_activations(input, tokens, experts.bank.input_width)?;
4933 let pairs = tokens
4934 .checked_mul(experts_per_token)
4935 .ok_or("TP route count overflow")?;
4936 if selected.len() != pairs || route_weights.len() != pairs {
4937 return Err(format!(
4938 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
4939 {experts_per_token} ({pairs})",
4940 selected.len(),
4941 route_weights.len(),
4942 )
4943 .into());
4944 }
4945 if !route_weights.iter().all(|weight| weight.is_finite()) {
4946 return Err("TP route weights contain a non-finite value".into());
4947 }
4948
4949 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
4950 for token in 0..tokens {
4951 let input_row =
4952 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
4953 for slot in 0..experts_per_token {
4954 let pair = token * experts_per_token + slot;
4955 let expert = selected[pair];
4956 if expert >= experts.bank.expert_count {
4957 return Err(format!(
4958 "TP selected expert {expert} outside 0..{}",
4959 experts.bank.expert_count
4960 )
4961 .into());
4962 }
4963 let down = if self.native_p2p {
4964 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
4965 } else {
4966 let gate =
4967 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
4968 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
4969 let activated: Vec<f32> = gate
4970 .iter()
4971 .zip(&up)
4972 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4973 .collect();
4974 debug_assert_eq!(activated.len(), experts.bank.expert_width);
4975 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
4976 };
4977 let weight = route_weights[pair];
4978 for (sum, value) in output
4979 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
4980 .iter_mut()
4981 .zip(down)
4982 {
4983 *sum += weight * value;
4984 }
4985 }
4986 }
4987 Ok(output)
4988 }
4989
4990 fn run_column_bank_expert(
4991 &self,
4992 ranks: &[ResidentE4m3ExpertBankRank],
4993 expert: usize,
4994 input: &[f32],
4995 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4996 let local_out = ranks
4997 .first()
4998 .ok_or("TP column bank has no ranks")?
4999 .out_features;
5000 let mut gathered = vec![0.0f32; local_out * ranks.len()];
5001 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5002 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
5003 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
5004 }
5005 Ok(gathered)
5006 }
5007
5008 fn run_row_bank_expert(
5009 &self,
5010 ranks: &[ResidentE4m3ExpertBankRank],
5011 expert: usize,
5012 input: &[f32],
5013 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5014 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
5015 if input.len() != local_in * ranks.len() {
5016 return Err(format!(
5017 "TP row input {} != {} ranks x {local_in}",
5018 input.len(),
5019 ranks.len()
5020 )
5021 .into());
5022 }
5023 let out_features = ranks[0].out_features;
5024 let mut reduced = vec![0.0f32; out_features];
5025 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
5026 let blocks = bank
5027 .k_blocks
5028 .ok_or("TP row bank is not packed in native K-block order")?;
5029 if blocks * FP8_BLOCK != local_in {
5030 return Err(format!(
5031 "TP row bank has {blocks} blocks but local input width is {local_in}"
5032 )
5033 .into());
5034 }
5035 for block in 0..blocks {
5036 let global_start = rank * local_in + block * FP8_BLOCK;
5037 let partial = run_resident_bank_expert_block(
5038 engine,
5039 bank,
5040 expert,
5041 block,
5042 &input[global_start..global_start + FP8_BLOCK],
5043 )?;
5044 for (sum, value) in reduced.iter_mut().zip(partial) {
5045 *sum += value;
5046 }
5047 }
5048 }
5049 Ok(reduced)
5050 }
5051
5052 fn run_tensor_parallel_expert_native(
5053 &self,
5054 bank: &ResidentTpExpertBank,
5055 expert: usize,
5056 input: &[f32],
5057 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5058 if !self.native_p2p || self.ranks.len() < 2 {
5059 return Err("native TP expert execution requires at least two P2P ranks".into());
5060 }
5061 let local_out = bank
5062 .gate
5063 .first()
5064 .ok_or("native TP gate bank has no ranks")?
5065 .out_features;
5066 if local_out * self.ranks.len() != bank.expert_width {
5067 return Err(format!(
5068 "native TP gate shards {}x{local_out} != expert width {}",
5069 self.ranks.len(),
5070 bank.expert_width
5071 )
5072 .into());
5073 }
5074
5075 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
5078 let root_input = {
5079 let root = &self.ranks[0];
5080 let _main = root.gpu.enter_main()?;
5081 root.htod(input)?
5082 };
5083 rank_inputs.push(root_input);
5084 for engine in &self.ranks[1..] {
5085 let peer_input = {
5086 let _main = engine.gpu.enter_main()?;
5087 let mut peer_input = engine.uninit(input.len())?;
5088 engine
5089 .stream()
5090 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
5091 peer_input
5092 };
5093 rank_inputs.push(peer_input);
5094 }
5095
5096 let mut gate_shards = Vec::with_capacity(self.ranks.len());
5097 let mut up_shards = Vec::with_capacity(self.ranks.len());
5098 for rank in 0..self.ranks.len() {
5099 gate_shards.push(run_resident_bank_expert_device(
5100 &self.ranks[rank],
5101 &bank.gate[rank],
5102 expert,
5103 &rank_inputs[rank],
5104 1,
5105 )?);
5106 up_shards.push(run_resident_bank_expert_device(
5107 &self.ranks[rank],
5108 &bank.up[rank],
5109 expert,
5110 &rank_inputs[rank],
5111 1,
5112 )?);
5113 }
5114
5115 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
5119 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
5120 let activated = gate
5121 .iter()
5122 .zip(&up)
5123 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
5124 .collect::<Vec<_>>();
5125 debug_assert_eq!(activated.len(), bank.expert_width);
5126
5127 let root_activated = {
5128 let root = &self.ranks[0];
5129 let _main = root.gpu.enter_main()?;
5130 root.htod(&activated)?
5131 };
5132 let mut rank_activated = Vec::with_capacity(self.ranks.len());
5133 for (rank, engine) in self.ranks.iter().enumerate() {
5134 let start = rank * local_out;
5135 let source = root_activated.slice(start..start + local_out);
5136 let local = {
5137 let _main = engine.gpu.enter_main()?;
5138 let mut local = engine.uninit(local_out)?;
5139 engine.stream().memcpy_dtod(&source, &mut local)?;
5140 local
5141 };
5142 rank_activated.push(local);
5143 }
5144
5145 let out_features = bank
5146 .down
5147 .first()
5148 .ok_or("native TP down bank has no ranks")?
5149 .out_features;
5150 let mut reduced = {
5151 let root = &self.ranks[0];
5152 let _main = root.gpu.enter_main()?;
5153 root.htod(&vec![0.0f32; out_features])?
5154 };
5155 let mut remote_partial_keepalive = Vec::new();
5156 for rank in 0..self.ranks.len() {
5157 let down = &bank.down[rank];
5158 let blocks = down
5159 .k_blocks
5160 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
5161 if blocks * FP8_BLOCK != local_out {
5162 return Err(format!(
5163 "native TP rank {rank} has {blocks} blocks but local activation width is \
5164 {local_out}"
5165 )
5166 .into());
5167 }
5168 for block in 0..blocks {
5169 let start = block * FP8_BLOCK;
5170 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
5171 let partial = run_resident_bank_expert_block_device(
5172 &self.ranks[rank],
5173 down,
5174 expert,
5175 block,
5176 &input_block,
5177 )?;
5178 let root_partial = if rank == 0 {
5179 partial
5180 } else {
5181 let root = &self.ranks[0];
5182 let _main = root.gpu.enter_main()?;
5183 let mut peer_partial = root.uninit(out_features)?;
5184 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5185 remote_partial_keepalive.push(partial);
5186 peer_partial
5187 };
5188 let next = {
5189 let root = &self.ranks[0];
5190 let _main = root.gpu.enter_main()?;
5191 let mut next = root.uninit(out_features)?;
5192 root.add(&reduced, &root_partial, &mut next, out_features)?;
5193 next
5194 };
5195 reduced = next;
5196 }
5197 }
5198 let output = {
5199 let root = &self.ranks[0];
5200 let _main = root.gpu.enter_main()?;
5201 root.dtoh(&reduced)?
5202 };
5203 drop(remote_partial_keepalive);
5204 Ok(output)
5205 }
5206
5207 pub fn gather_native_column_shards_device(
5209 &self,
5210 shards: &[CudaSlice<f32>],
5211 tokens: usize,
5212 local_out: usize,
5213 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5214 let shard_len = tokens
5215 .checked_mul(local_out)
5216 .ok_or("native TP gather shard size overflow")?;
5217 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5218 return Err("native TP gather shard geometry mismatch".into());
5219 }
5220 for engine in &self.ranks[1..] {
5224 let _main = engine.gpu.enter_main()?;
5225 engine.stream().synchronize()?;
5226 }
5227 let root = &self.ranks[0];
5228 let _main = root.gpu.enter_main()?;
5229 let global_out = shards
5230 .len()
5231 .checked_mul(local_out)
5232 .ok_or("native TP gather output width overflow")?;
5233 let gathered_len = tokens
5234 .checked_mul(global_out)
5235 .ok_or("native TP gather output size overflow")?;
5236 let mut gathered = root.uninit(gathered_len)?;
5237 if self.bulk_p2p {
5238 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5239 if shards.len() > 1 {
5240 let mut staging = root.uninit(shard_len)?;
5241 for (rank, shard) in shards.iter().enumerate().skip(1) {
5242 root.stream().memcpy_dtod(shard, &mut staging)?;
5243 root.place_rows_strided(
5244 &staging,
5245 &mut gathered,
5246 local_out,
5247 tokens,
5248 global_out,
5249 rank * local_out,
5250 )?;
5251 }
5252 }
5253 } else {
5254 for token in 0..tokens {
5255 for (rank, shard) in shards.iter().enumerate() {
5256 let source = shard.slice(token * local_out..(token + 1) * local_out);
5257 let start = token * global_out + rank * local_out;
5258 let mut destination = gathered.slice_mut(start..start + local_out);
5259 root.stream().memcpy_dtod(&source, &mut destination)?;
5260 }
5261 }
5262 }
5263 Ok(gathered)
5264 }
5265
5266 pub fn gather_native_column_shards(
5267 &self,
5268 shards: &[CudaSlice<f32>],
5269 tokens: usize,
5270 local_out: usize,
5271 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5272 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5273 let root = &self.ranks[0];
5274 let _main = root.gpu.enter_main()?;
5275 root.dtoh(&gathered)
5276 }
5277
5278 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5279 &self.decode_v2
5280 }
5281
5282 pub(crate) fn decode_v2_ensure(
5291 &self,
5292 e: &Engine,
5293 q_m: &ResidentBf16ColumnParallel,
5294 k_m: &ResidentBf16ColumnParallel,
5295 v_m: &ResidentBf16ColumnParallel,
5296 o_m: &ResidentStepBf16RowParallel,
5297 heads: usize,
5298 ) -> Result<usize, Box<dyn std::error::Error>> {
5299 if self.ranks.len() > 1 && !self.native_p2p {
5300 return Err("step TP decode v2 requires native P2P ranks".into());
5301 }
5302 let ranks = self.ranks.len();
5303 let fused_door = step_tp_qkv_fused_enabled()?;
5307 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5308 ResidentBf16Weight::F32(_) => true,
5309 ResidentBf16Weight::Bf16(_) => fused_door,
5310 };
5311 for matrix in [q_m, k_m, v_m] {
5312 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5313 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5314 return Err("step TP decode v2 QKV geometry mismatch".into());
5315 }
5316 for rank in &matrix.ranks {
5317 if !arm_ok(&rank.weight) {
5318 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5319 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5320 .into());
5321 }
5322 }
5323 }
5324 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5325 for blocks in &o_m.ranks {
5326 for block in blocks {
5327 if !arm_ok(&block.weight) {
5328 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5329 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5330 .into());
5331 }
5332 }
5333 }
5334 if v_m.out_features != k_m.out_features
5335 || o_m.in_features != q_m.out_features
5336 || heads == 0
5337 || heads % ranks != 0
5338 {
5339 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5340 }
5341 let local_q_dim = q_m.out_features / ranks;
5342 let local_kv_dim = k_m.out_features / ranks;
5343 let o_out = o_m.out_features;
5344 let o_block_cols = o_m.canonical_chunk_cols;
5345 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5346 if blocks_per_rank == 0
5347 || o_m
5348 .ranks
5349 .iter()
5350 .any(|blocks| blocks.len() != blocks_per_rank)
5351 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5352 {
5353 return Err("step TP decode v2 O canonical block grid mismatch".into());
5354 }
5355
5356 let mut guard = self
5357 .decode_v2
5358 .lock()
5359 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5360 if let Some(index) = guard.iter().position(|ws| {
5361 ws.local_q_dim == local_q_dim
5362 && ws.local_kv_dim == local_kv_dim
5363 && ws.heads == heads
5364 && ws.o_out == o_out
5365 && ws.o_block_cols == o_block_cols
5366 && ws.blocks_per_rank == blocks_per_rank
5367 && ws.e_device == e.ctx().ordinal()
5368 && ws.q.len() == ranks
5369 }) {
5370 return Ok(index);
5371 }
5372
5373 let mut q_raw = Vec::with_capacity(ranks);
5374 let mut k_raw = Vec::with_capacity(ranks);
5375 let mut v_raw = Vec::with_capacity(ranks);
5376 let mut q = Vec::with_capacity(ranks);
5377 let mut k = Vec::with_capacity(ranks);
5378 let mut pos = Vec::with_capacity(ranks);
5379 let mut gate = Vec::with_capacity(ranks);
5380 let mut attn_out = Vec::with_capacity(ranks);
5381 let mut gated = Vec::with_capacity(ranks);
5382 let mut fuse_ctr = Vec::with_capacity(ranks);
5383 let mut o_partials = Vec::with_capacity(ranks);
5384 let mut ev_rank = Vec::with_capacity(ranks);
5385 let direct_join = oproj_direct_on();
5386 for (rank, engine) in self.ranks.iter().enumerate() {
5387 let _main = engine.gpu.enter_main()?;
5388 q_raw.push(engine.uninit(local_q_dim)?);
5389 k_raw.push(engine.uninit(local_kv_dim)?);
5390 v_raw.push(engine.uninit(local_kv_dim)?);
5391 q.push(engine.uninit(local_q_dim)?);
5392 k.push(engine.uninit(local_kv_dim)?);
5393 pos.push(engine.htod_i32(&[0])?);
5394 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5395 gate.push(engine.uninit(heads / ranks)?);
5396 attn_out.push(engine.uninit(local_q_dim)?);
5397 gated.push(engine.uninit(local_q_dim)?);
5398 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5399 for _ in 0..blocks_per_rank {
5400 if direct_join && rank != 0 {
5403 let root = &self.ranks[0];
5404 let _root_main = root.gpu.enter_main()?;
5405 rank_partials.push(root.uninit(o_out)?);
5406 } else {
5407 rank_partials.push(engine.uninit(o_out)?);
5408 }
5409 }
5410 o_partials.push(rank_partials);
5411 ev_rank.push(engine.ctx().new_event(None)?);
5412 }
5413 let root = &self.ranks[0];
5414 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5415 let _main = root.gpu.enter_main()?;
5416 (
5417 root.uninit(o_out)?,
5418 root.uninit(o_out)?,
5419 root.uninit(o_out)?,
5420 root.htod(&vec![0.0f32; o_out])?,
5421 root.uninit(ranks * local_kv_dim)?,
5422 root.uninit(ranks * local_kv_dim)?,
5423 root.ctx().new_event(None)?,
5424 root.ctx().new_event(None)?,
5425 )
5426 };
5427 let (gate_e, ev_entry) = {
5428 let _main = e.gpu.enter_main()?;
5429 (e.uninit(heads)?, e.ctx().new_event(None)?)
5430 };
5431 let raw_attn_in = Vec::new();
5432 let raw_pos = Vec::new();
5433 guard.push(StepTpDecodeV2Ws {
5434 tcol_q: Vec::new(),
5435 tcol_k: Vec::new(),
5436 tcol_v: Vec::new(),
5437 tcol_g: Vec::new(),
5438 tcol_in: Vec::new(),
5439 tcol_cap: 0,
5440 fa2_q: Vec::new(),
5441 fa2_gate: Vec::new(),
5442 fa2_gated: Vec::new(),
5443 fa2_cap: 0,
5444 tcol_gated: Vec::new(),
5445 tcol_opart: Vec::new(),
5446 tcol_opeer: None,
5447 tcol_omix: None,
5448 tcol_ocap: 0,
5449 q_raw,
5450 k_raw,
5451 v_raw,
5452 q,
5453 k,
5454 pos,
5455 fuse_ctr,
5456 gate,
5457 attn_out,
5458 gated,
5459 o_partials,
5460 ev_rank,
5461 peer_partial,
5462 reduce_a,
5463 reduce_b,
5464 zeros,
5465 k_shadow,
5466 v_shadow,
5467 ev_refresh,
5468 ev_oproj,
5469 gate_e,
5470 attn_in: Vec::new(),
5471 h_stage: None,
5472 pos_stage: None,
5473 raw_h_stage: 0,
5474 raw_pos_stage: 0,
5475 raw_attn_in,
5476 raw_pos,
5477 raw_o_partial1: 0,
5478 raw_peer_partial: 0,
5479 raw_k1: 0,
5480 raw_v1: 0,
5481 raw_k_shadow: 0,
5482 raw_v_shadow: 0,
5483 raw_mixed_stage_e: 0,
5484 raw_reduce_a: 0,
5485 raw_shadow_stage_e: (0, 0),
5486 ev_entry,
5487 e_device: e.ctx().ordinal(),
5488 local_q_dim,
5489 local_kv_dim,
5490 heads,
5491 o_out,
5492 o_block_cols,
5493 blocks_per_rank,
5494 });
5495 eprintln!(
5496 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5497 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5498 residency=persistent ordering=evented performance_claim=false"
5499 );
5500 Ok(guard.len() - 1)
5501 }
5502
5503 #[allow(clippy::too_many_arguments)]
5511 #[allow(clippy::too_many_arguments)]
5516 pub fn decode_v2_input_qkv_tcol(
5517 &self,
5518 ws_index: usize,
5519 e: &Engine,
5520 h_t: &CudaSlice<f32>,
5521 t: usize,
5522 q_m: &ResidentBf16ColumnParallel,
5523 k_m: &ResidentBf16ColumnParallel,
5524 v_m: &ResidentBf16ColumnParallel,
5525 gate_shards: Option<StepTpGateShards<'_>>,
5526 ) -> Result<(), Box<dyn std::error::Error>> {
5527 let ranks = self.ranks.len();
5528 let mut guard = self
5529 .decode_v2
5530 .lock()
5531 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5532 let ws = guard
5533 .get_mut(ws_index)
5534 .ok_or("step TP decode v2 workspace index out of range")?;
5535 let in_f = q_m.in_features;
5536 if h_t.len() < t * in_f || t == 0 || t > 8 {
5537 return Err("decode_v2_input_qkv_tcol geometry".into());
5538 }
5539 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5541 ws.tcol_q.clear();
5542 ws.tcol_k.clear();
5543 ws.tcol_v.clear();
5544 ws.tcol_g.clear();
5545 ws.tcol_in.clear();
5546 for engine in &self.ranks {
5547 let _m = engine.gpu.enter_main()?;
5548 ws.tcol_q.push(engine.uninit(8 * ws.local_q_dim)?);
5549 ws.tcol_k.push(engine.uninit(8 * ws.local_kv_dim)?);
5550 ws.tcol_v.push(engine.uninit(8 * ws.local_kv_dim)?);
5551 ws.tcol_g
5552 .push(engine.uninit(8 * (ws.heads / ranks).max(1))?);
5553 ws.tcol_in.push(engine.uninit(8 * in_f)?);
5554 }
5555 ws.tcol_cap = 8;
5556 }
5557 use cudarc::driver::DevicePtr;
5559 let raw_src = {
5560 let _main = e.gpu.enter_main()?;
5561 let stream = e.stream();
5562 let (p, _g) = h_t.device_ptr(&stream);
5563 ws.ev_entry.record(&stream)?;
5564 p as u64
5565 };
5566 for rank in 0..ranks {
5567 let engine = &self.ranks[rank];
5568 let _main = engine.gpu.enter_main()?;
5569 engine.stream().wait(&ws.ev_entry)?;
5570 let raw_dst = {
5571 let stream = engine.stream();
5572 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5573 p as u64
5574 };
5575 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5576 let out_g = match &gate_shards {
5577 Some(_) => ws.heads / ranks,
5578 None => 0,
5579 };
5580 match (
5581 &q_m.ranks[rank].weight,
5582 &k_m.ranks[rank].weight,
5583 &v_m.ranks[rank].weight,
5584 ) {
5585 (
5586 ResidentBf16Weight::Bf16(wq),
5587 ResidentBf16Weight::Bf16(wk),
5588 ResidentBf16Weight::Bf16(wv),
5589 ) => {
5590 let wg = match &gate_shards {
5591 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5592 Some(StepTpGateShards::F32(_)) => {
5593 return Err(
5594 "tcol verify: gate shard class does not match bf16 QKV".into()
5595 );
5596 }
5597 None => wq,
5598 };
5599 let StepTpDecodeV2Ws {
5600 tcol_q,
5601 tcol_k,
5602 tcol_v,
5603 tcol_g,
5604 tcol_in,
5605 local_q_dim,
5606 local_kv_dim,
5607 ..
5608 } = &mut *ws;
5609 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5612 let refk = *REFK
5613 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5614 if refk {
5615 let lq = *local_q_dim;
5616 let lkv = *local_kv_dim;
5617 let mut hrow = engine.uninit(in_f)?;
5618 let mut qr = engine.uninit(lq)?;
5619 let mut kr = engine.uninit(lkv)?;
5620 let mut vr = engine.uninit(lkv)?;
5621 let mut gr = engine.uninit(out_g.max(1))?;
5622 for c in 0..t {
5623 {
5624 let mut dst = hrow.slice_mut(0..in_f);
5625 engine.stream().memcpy_dtod(
5626 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5627 &mut dst,
5628 )?;
5629 }
5630 engine.matvec_bf16_qkvg_into(
5631 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5632 lq, lkv, out_g,
5633 )?;
5634 let stream = engine.stream();
5635 {
5636 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5637 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5638 }
5639 {
5640 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5641 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5642 }
5643 {
5644 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5645 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5646 }
5647 if out_g > 0 {
5648 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5649 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5650 }
5651 }
5652 } else {
5653 engine.matvec_bf16_qkvg_tcol_into(
5654 wq,
5655 wk,
5656 wv,
5657 wg,
5658 &tcol_in[rank],
5659 &mut tcol_q[rank],
5660 &mut tcol_k[rank],
5661 &mut tcol_v[rank],
5662 &mut tcol_g[rank],
5663 in_f,
5664 *local_q_dim,
5665 *local_kv_dim,
5666 out_g,
5667 t,
5668 )?;
5669 }
5670 }
5671 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5672 }
5673 }
5674 Ok(())
5675 }
5676
5677 pub(crate) fn decode_v2_oproj_tcol_eligible(
5681 &self,
5682 ws: &StepTpDecodeV2Ws,
5683 o_m: &ResidentStepBf16RowParallel,
5684 ) -> bool {
5685 self.ranks.len() == 2
5686 && ws.blocks_per_rank == 4
5687 && step_tp_qkv_fused_enabled().unwrap_or(false)
5688 && no_local_shadow_on()
5689 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5690 && o_m
5691 .ranks
5692 .iter()
5693 .flatten()
5694 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5695 }
5696
5697 pub(crate) fn decode_v2_stash_fa2(
5702 &self,
5703 ws: &mut StepTpDecodeV2Ws,
5704 e: &Engine,
5705 col: usize,
5706 ) -> Result<(), Box<dyn std::error::Error>> {
5707 let ranks = self.ranks.len();
5708 if col >= 2 {
5709 return Err("decode_v2_stash_fa2 column out of range".into());
5710 }
5711 let lq = ws.local_q_dim;
5712 let lg = (ws.heads / ranks).max(1);
5713 if ws.fa2_cap == 0 || ws.fa2_q.len() != ranks {
5714 ws.fa2_q.clear();
5715 ws.fa2_gate.clear();
5716 ws.fa2_gated.clear();
5717 for engine in &self.ranks {
5718 let _m = engine.gpu.enter_main()?;
5719 ws.fa2_q.push(engine.uninit(2 * lq)?);
5720 ws.fa2_gate.push(engine.uninit(2 * lg)?);
5721 ws.fa2_gated.push(engine.uninit(2 * lq)?);
5722 }
5723 ws.fa2_cap = 2;
5724 }
5725 for rank in 0..ranks {
5726 let engine = &self.ranks[rank];
5727 let _main = engine.gpu.enter_main()?;
5728 {
5729 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5730 engine
5731 .stream()
5732 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5733 }
5734 {
5735 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5736 engine
5737 .stream()
5738 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5739 }
5740 ws.ev_rank[rank].record(&engine.stream())?;
5741 }
5742 {
5743 let _main = e.gpu.enter_main()?;
5744 for ev in ws.ev_rank.iter() {
5745 e.stream().wait(ev)?;
5746 }
5747 }
5748 Ok(())
5749 }
5750
5751 #[allow(clippy::too_many_arguments)]
5758 pub(crate) fn decode_v2_spec_fa2_join(
5759 &self,
5760 ws_index: usize,
5761 e: &Engine,
5762 o_m: &ResidentStepBf16RowParallel,
5763 kv: &ResidentTpKvCache,
5764 head_dim: usize,
5765 window: usize,
5766 bucket_max: usize,
5767 scale: f32,
5768 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5769 let ranks = self.ranks.len();
5770 static ONCE: std::sync::Once = std::sync::Once::new();
5772 ONCE.call_once(|| eprintln!("[spec-fa2] joined T=2 attention ENGAGED"));
5773 {
5774 let mut guard = self
5775 .decode_v2
5776 .lock()
5777 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5778 let ws = guard
5779 .get_mut(ws_index)
5780 .ok_or("step TP decode v2 workspace index out of range")?;
5781 if ws.fa2_cap < 2 || ws.fa2_q.len() != ranks {
5782 return Err("spec fa2 join without stashed columns".into());
5783 }
5784 let lq = ws.local_q_dim;
5785 let local_heads = (ws.heads / ranks).max(1);
5786 let local_kv_heads = (ws.local_kv_dim / head_dim).max(1);
5787 let capacity = kv.physical_capacity();
5788 let (k_tok_bytes, v_tok_bytes) = (kv.k_tok_bytes(), kv.v_tok_bytes());
5789 if ws.tcol_ocap < 2 || ws.tcol_gated.len() != ranks {
5791 ws.tcol_gated.clear();
5792 ws.tcol_opart.clear();
5793 for engine in &self.ranks {
5794 let _m = engine.gpu.enter_main()?;
5795 ws.tcol_gated.push(engine.uninit(8 * lq)?);
5796 ws.tcol_opart.push(engine.uninit(8 * ws.o_out)?);
5797 }
5798 let root = &self.ranks[0];
5799 let _m = root.gpu.enter_main()?;
5800 ws.tcol_opeer = Some(root.uninit(8 * ws.o_out)?);
5801 ws.tcol_omix = Some(root.uninit(8 * ws.o_out)?);
5802 ws.tcol_ocap = 8;
5803 }
5804 for rank in 0..ranks {
5805 let engine = &self.ranks[rank];
5806 let _main = engine.gpu.enter_main()?;
5807 let rank_cache = kv
5808 .rank(rank)
5809 .ok_or("spec fa2 join lost its KV cache rank")?;
5810 let k_ring = engine.view_u8_range(rank_cache.k(), 0, capacity * k_tok_bytes);
5811 let v_ring = engine.view_u8_range(rank_cache.v(), 0, capacity * v_tok_bytes);
5812 {
5813 let StepTpDecodeV2Ws {
5814 fa2_q,
5815 fa2_gate,
5816 fa2_gated,
5817 ..
5818 } = &mut *ws;
5819 engine.fa_decode_dcw2(
5820 &fa2_q[rank],
5821 &k_ring,
5822 &v_ring,
5823 &mut fa2_gated[rank],
5824 head_dim,
5825 local_heads,
5826 local_kv_heads,
5827 rank_cache.len_d(),
5828 rank_cache.base_d(),
5829 window,
5830 bucket_max,
5831 scale,
5832 k_tok_bytes,
5833 v_tok_bytes,
5834 &fa2_gate[rank],
5835 )?;
5836 }
5837 let StepTpDecodeV2Ws {
5840 fa2_gated,
5841 tcol_gated,
5842 ..
5843 } = &mut *ws;
5844 let mut dst = tcol_gated[rank].slice_mut(0..2 * lq);
5845 engine
5846 .stream()
5847 .memcpy_dtod(&fa2_gated[rank].slice(0..2 * lq), &mut dst)?;
5848 }
5849 }
5850 self.decode_v2_oproj_tcol(ws_index, e, o_m, 2)
5851 }
5852
5853 pub(crate) fn decode_v2_stash_gated(
5858 &self,
5859 ws: &mut StepTpDecodeV2Ws,
5860 e: &Engine,
5861 col: usize,
5862 ) -> Result<(), Box<dyn std::error::Error>> {
5863 let ranks = self.ranks.len();
5864 if col >= 8 {
5865 return Err("decode_v2_stash_gated column out of range".into());
5866 }
5867 let lq = ws.local_q_dim;
5868 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
5869 ws.tcol_gated.clear();
5870 ws.tcol_opart.clear();
5871 for engine in &self.ranks {
5872 let _m = engine.gpu.enter_main()?;
5873 ws.tcol_gated.push(engine.uninit(8 * lq)?);
5874 ws.tcol_opart.push(engine.uninit(8 * ws.o_out)?);
5875 }
5876 let root = &self.ranks[0];
5877 let _m = root.gpu.enter_main()?;
5878 ws.tcol_opeer = Some(root.uninit(8 * ws.o_out)?);
5879 ws.tcol_omix = Some(root.uninit(8 * ws.o_out)?);
5880 ws.tcol_ocap = 8;
5881 }
5882 for rank in 0..ranks {
5883 let engine = &self.ranks[rank];
5884 let _main = engine.gpu.enter_main()?;
5885 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
5886 engine
5887 .stream()
5888 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
5889 ws.ev_rank[rank].record(&engine.stream())?;
5893 }
5894 {
5895 let _main = e.gpu.enter_main()?;
5896 for ev in ws.ev_rank.iter() {
5897 e.stream().wait(ev)?;
5898 }
5899 }
5900 Ok(())
5901 }
5902
5903 pub(crate) fn decode_v2_oproj_tcol(
5909 &self,
5910 ws_index: usize,
5911 e: &Engine,
5912 o_m: &ResidentStepBf16RowParallel,
5913 t: usize,
5914 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5915 let ranks = self.ranks.len();
5916 let mut guard = self
5917 .decode_v2
5918 .lock()
5919 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5920 let ws = guard
5921 .get_mut(ws_index)
5922 .ok_or("step TP decode v2 workspace index out of range")?;
5923 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 8 || ws.tcol_ocap < t {
5924 return Err("decode_v2_oproj_tcol geometry".into());
5925 }
5926 for rank in 0..ranks {
5927 let engine = &self.ranks[rank];
5928 let _main = engine.gpu.enter_main()?;
5929 let mut weights = Vec::with_capacity(4);
5930 for block in 0..4 {
5931 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
5932 return Err("tcol o_proj requires bf16-resident O blocks".into());
5933 };
5934 weights.push(weight);
5935 }
5936 {
5937 let StepTpDecodeV2Ws {
5938 tcol_gated,
5939 tcol_opart,
5940 local_q_dim,
5941 o_block_cols,
5942 o_out,
5943 ..
5944 } = &mut *ws;
5945 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5948 let refk = *REFK
5949 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
5950 if refk {
5951 let lq = *local_q_dim;
5952 let mut xr = engine.uninit(lq)?;
5953 let mut yr = engine.uninit(*o_out)?;
5954 for c in 0..t {
5955 {
5956 let mut dst = xr.slice_mut(0..lq);
5957 engine.stream().memcpy_dtod(
5958 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
5959 &mut dst,
5960 )?;
5961 }
5962 engine.matvec_bf16_b4_into(
5963 [weights[0], weights[1], weights[2], weights[3]],
5964 &xr,
5965 &mut yr,
5966 *o_block_cols,
5967 *o_out,
5968 )?;
5969 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
5970 engine
5971 .stream()
5972 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
5973 }
5974 } else {
5975 engine.matvec_bf16_b4_tcol_into(
5976 [weights[0], weights[1], weights[2], weights[3]],
5977 &tcol_gated[rank],
5978 &mut tcol_opart[rank],
5979 *o_block_cols,
5980 *o_out,
5981 t,
5982 )?;
5983 }
5984 }
5985 if rank != 0 {
5986 ws.ev_rank[rank].record(&engine.stream())?;
5987 }
5988 }
5989 let root = &self.ranks[0];
5990 {
5991 let _main = root.gpu.enter_main()?;
5992 for ev in ws.ev_rank.iter().skip(1) {
5993 root.stream().wait(ev)?;
5994 }
5995 {
5996 let StepTpDecodeV2Ws {
5997 tcol_opart,
5998 tcol_opeer,
5999 tcol_omix,
6000 o_out,
6001 ..
6002 } = &mut *ws;
6003 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
6004 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
6005 {
6006 let mut dst = opeer.slice_mut(0..t * *o_out);
6007 root.stream()
6008 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
6009 }
6010 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
6013 }
6014 ws.ev_oproj.record(&root.stream())?;
6015 }
6016 let _main = e.gpu.enter_main()?;
6017 e.stream().wait(&ws.ev_oproj)?;
6018 let mut out = e.uninit(t * ws.o_out)?;
6019 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
6020 e.stream().memcpy_dtod(
6021 &omix.slice(0..t * ws.o_out),
6022 &mut out.slice_mut(0..t * ws.o_out),
6023 )?;
6024 Ok(out)
6025 }
6026
6027 pub(crate) fn decode_v2_input_qkv(
6028 &self,
6029 ws: &mut StepTpDecodeV2Ws,
6030 e: &Engine,
6031 h: &CudaSlice<f32>,
6032 pos_d: &CudaSlice<i32>,
6033 gate_raw: Option<&CudaSlice<f32>>,
6034 gate_shards: Option<StepTpGateShards<'_>>,
6035 decode_input: &mut ResidentReplicatedDeviceRows,
6036 q_m: &ResidentBf16ColumnParallel,
6037 k_m: &ResidentBf16ColumnParallel,
6038 v_m: &ResidentBf16ColumnParallel,
6039 q_norm: &[CudaSlice<f32>],
6040 k_norm: &[CudaSlice<f32>],
6041 head_dim: usize,
6042 n_rot: usize,
6043 rope_base: f32,
6044 rope_freqs: &[Option<&CudaSlice<f32>>],
6045 rms_eps: f32,
6046 defer_norm_rope: bool,
6047 tcol_col: Option<usize>,
6048 ) -> Result<(), Box<dyn std::error::Error>> {
6049 let ranks = self.ranks.len();
6050 validate_replicated_device_rows(&self.ranks, decode_input)?;
6051 if decode_input.tokens != 1
6052 || decode_input.width != q_m.in_features
6053 || pos_d.len() != 1
6054 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
6055 || gate_raw.is_none() != gate_shards.is_some()
6056 || gate_shards.as_ref().is_some_and(|shards| match shards {
6057 StepTpGateShards::F32(shards) => shards.len() != ranks,
6058 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
6059 })
6060 || q_norm.len() != ranks
6061 || k_norm.len() != ranks
6062 || rope_freqs.len() != ranks
6063 || e.ctx().ordinal() != ws.e_device
6064 {
6065 return Err("step TP decode v2 input geometry mismatch".into());
6066 }
6067
6068 let qkv_fused = step_tp_qkv_fused_enabled()?;
6069 if gate_shards.is_some() && !qkv_fused {
6070 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
6071 }
6072 let values = decode_input.width;
6073 if h.len() != values {
6074 return Err(format!(
6075 "step TP decode v2 hidden width {} != replicated width {values}",
6076 h.len()
6077 )
6078 .into());
6079 }
6080
6081 if qkv_fused {
6082 if ws.h_stage.is_none() {
6086 use cudarc::driver::DevicePtr;
6087 let _main = e.gpu.enter_main()?;
6088 let h_stage = e.uninit(values)?;
6089 let pos_stage = e.htod_i32(&[0])?;
6090 {
6091 let stream = e.stream();
6092 let (hp, _g0) = h_stage.device_ptr(&stream);
6093 let (pp, _g1) = pos_stage.device_ptr(&stream);
6094 ws.raw_h_stage = hp as u64;
6095 ws.raw_pos_stage = pp as u64;
6096 }
6097 ws.h_stage = Some(h_stage);
6098 ws.pos_stage = Some(pos_stage);
6099 for rank in 0..ranks {
6100 use cudarc::driver::DevicePtr;
6101 let engine = &self.ranks[rank];
6102 let _rmain = engine.gpu.enter_main()?;
6103 let attn_in = engine.uninit(values)?;
6104 let (dp, pp) = {
6105 let stream = engine.stream();
6106 let (dp, _g2) = attn_in.device_ptr(&stream);
6107 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
6108 (dp as u64, pp as u64)
6109 };
6110 ws.raw_attn_in.push(dp);
6111 ws.raw_pos.push(pp);
6112 ws.attn_in.push(attn_in);
6113 }
6114 {
6115 use cudarc::driver::DevicePtr;
6116 let root = &self.ranks[0];
6117 let _rmain = root.gpu.enter_main()?;
6118 let stream = root.stream();
6119 let (a, _g) = ws.peer_partial.device_ptr(&stream);
6120 let (b, _g) = ws.k_shadow.device_ptr(&stream);
6121 let (c, _g) = ws.v_shadow.device_ptr(&stream);
6122 ws.raw_peer_partial = a as u64;
6123 ws.raw_k_shadow = b as u64;
6124 ws.raw_v_shadow = c as u64;
6125 }
6126 {
6127 use cudarc::driver::DevicePtr;
6128 let rank1 = &self.ranks[1];
6129 let _rmain = rank1.gpu.enter_main()?;
6130 let stream = rank1.stream();
6131 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
6132 let (b, _g) = ws.k[1].device_ptr(&stream);
6133 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
6134 ws.raw_o_partial1 = a as u64;
6135 ws.raw_k1 = b as u64;
6136 ws.raw_v1 = c as u64;
6137 }
6138 }
6139 {
6140 let _main = e.gpu.enter_main()?;
6141 {
6142 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
6145 let mut dst = h_stage.slice_mut(0..values);
6146 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
6147 }
6148 {
6149 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
6150 let mut dst = pos_stage.slice_mut(0..1);
6151 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
6152 }
6153 ws.ev_entry.record(&e.stream())?;
6154 }
6155 for rank in 0..ranks {
6156 let engine = &self.ranks[rank];
6157 let _main = engine.gpu.enter_main()?;
6158 engine.stream().wait(&ws.ev_entry)?;
6159 }
6160 } else {
6161 {
6163 let _main = e.gpu.enter_main()?;
6164 if let Some(gate_raw) = gate_raw {
6165 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
6166 e.stream()
6167 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
6168 }
6169 ws.ev_entry.record(&e.stream())?;
6170 }
6171 {
6172 let root = &self.ranks[0];
6173 let _main = root.gpu.enter_main()?;
6174 root.stream().wait(&ws.ev_entry)?;
6175 let mut destination = decode_input.ranks[0].slice_mut(0..values);
6176 root.stream()
6177 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
6178 ws.ev_refresh.record(&root.stream())?;
6179 }
6180 for rank in 1..ranks {
6181 let engine = &self.ranks[rank];
6182 let _main = engine.gpu.enter_main()?;
6183 engine.stream().wait(&ws.ev_refresh)?;
6184 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
6185 let mut destination = peer_rows[0].slice_mut(0..values);
6186 engine
6187 .stream()
6188 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
6189 }
6190 }
6191 for rank in 0..ranks {
6192 self.decode_v2_input_qkv_rank(
6193 ws,
6194 pos_d,
6195 decode_input,
6196 q_m,
6197 k_m,
6198 v_m,
6199 q_norm,
6200 k_norm,
6201 head_dim,
6202 n_rot,
6203 rope_base,
6204 rope_freqs,
6205 rms_eps,
6206 gate_shards.as_ref(),
6207 qkv_fused,
6208 defer_norm_rope,
6209 rank,
6210 tcol_col,
6211 )?;
6212 }
6213 Ok(())
6214 }
6215
6216 #[allow(clippy::too_many_arguments)]
6219 pub(crate) fn decode_v2_input_qkv_rank(
6220 &self,
6221 ws: &mut StepTpDecodeV2Ws,
6222 pos_d: &CudaSlice<i32>,
6223 decode_input: &mut ResidentReplicatedDeviceRows,
6224 q_m: &ResidentBf16ColumnParallel,
6225 k_m: &ResidentBf16ColumnParallel,
6226 v_m: &ResidentBf16ColumnParallel,
6227 q_norm: &[CudaSlice<f32>],
6228 k_norm: &[CudaSlice<f32>],
6229 head_dim: usize,
6230 n_rot: usize,
6231 rope_base: f32,
6232 rope_freqs: &[Option<&CudaSlice<f32>>],
6233 rms_eps: f32,
6234 gate_shards: Option<&StepTpGateShards<'_>>,
6235 qkv_fused: bool,
6236 defer_norm_rope: bool,
6237 rank: usize,
6238 tcol_col: Option<usize>,
6239 ) -> Result<(), Box<dyn std::error::Error>> {
6240 let ranks = self.ranks.len();
6241 let local_heads = ws.local_q_dim / head_dim;
6242 let local_kv_heads = ws.local_kv_dim / head_dim;
6243 let engine = &self.ranks[rank];
6244 let _main = engine.gpu.enter_main()?;
6245 let ws_e_device = ws.e_device;
6246 if qkv_fused && tcol_col.is_some() {
6251 let c = tcol_col.expect("checked");
6252 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
6253 return Err("tcol select without precompute".into());
6254 }
6255 if engine.ctx().ordinal() != ws_e_device {
6259 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6260 }
6261 let StepTpDecodeV2Ws {
6262 tcol_q,
6263 tcol_k,
6264 tcol_v,
6265 tcol_g,
6266 q_raw,
6267 k_raw,
6268 v_raw,
6269 gate,
6270 local_q_dim,
6271 local_kv_dim,
6272 heads,
6273 ..
6274 } = &mut *ws;
6275 let lg = *heads / ranks;
6276 let stream = engine.stream();
6277 {
6278 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6279 stream.memcpy_dtod(
6280 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6281 &mut dst,
6282 )?;
6283 }
6284 {
6285 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6286 stream.memcpy_dtod(
6287 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6288 &mut dst,
6289 )?;
6290 }
6291 {
6292 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6293 stream.memcpy_dtod(
6294 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6295 &mut dst,
6296 )?;
6297 }
6298 if lg > 0 {
6299 let mut dst = gate[rank].slice_mut(0..lg);
6300 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6301 }
6302 if !defer_norm_rope {
6303 } else {
6307 return Ok(());
6308 }
6309 }
6310 if qkv_fused {
6311 let same_dev = engine.ctx().ordinal() == ws.e_device;
6316 if !same_dev {
6317 raw_copy_bytes(
6318 ws.raw_attn_in[rank],
6319 ws.raw_h_stage,
6320 q_m.in_features * 4,
6321 engine,
6322 )?;
6323 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6324 }
6325 let StepTpDecodeV2Ws {
6326 q_raw,
6327 k_raw,
6328 v_raw,
6329 gate,
6330 gate_e,
6331 attn_in,
6332 h_stage,
6333 heads,
6334 local_q_dim,
6335 local_kv_dim,
6336 ..
6337 } = &mut *ws;
6338 let input_ref: &CudaSlice<f32> = if same_dev {
6339 h_stage
6340 .as_ref()
6341 .ok_or("step TP decode v2 stage not armed")?
6342 } else {
6343 &attn_in[rank]
6344 };
6345 match (
6346 &q_m.ranks[rank].weight,
6347 &k_m.ranks[rank].weight,
6348 &v_m.ranks[rank].weight,
6349 ) {
6350 (
6351 ResidentBf16Weight::F32(wq),
6352 ResidentBf16Weight::F32(wk),
6353 ResidentBf16Weight::F32(wv),
6354 ) => {
6355 let (wg, out_g) = match &gate_shards {
6356 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6357 Some(StepTpGateShards::Bf16(_)) => {
6358 return Err("step TP decode v2 gate shard class does not \
6359 match the F32 projections"
6360 .into());
6361 }
6362 None => (&*gate_e, 0),
6364 };
6365 engine.matvec_f32_qkv_into(
6366 wq,
6367 wk,
6368 wv,
6369 wg,
6370 input_ref,
6371 &mut q_raw[rank],
6372 &mut k_raw[rank],
6373 &mut v_raw[rank],
6374 &mut gate[rank],
6375 q_m.in_features,
6376 *local_q_dim,
6377 *local_kv_dim,
6378 out_g,
6379 )?;
6380 }
6381 (
6382 ResidentBf16Weight::Bf16(wq),
6383 ResidentBf16Weight::Bf16(wk),
6384 ResidentBf16Weight::Bf16(wv),
6385 ) => {
6386 let (wg, out_g) = match &gate_shards {
6387 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6388 Some(StepTpGateShards::F32(_)) => {
6389 return Err("step TP decode v2 gate shard class does not \
6390 match the bf16 projections"
6391 .into());
6392 }
6393 None => (wq, 0),
6394 };
6395 engine.matvec_bf16_qkvg_into(
6396 wq,
6397 wk,
6398 wv,
6399 wg,
6400 input_ref,
6401 &mut q_raw[rank],
6402 &mut k_raw[rank],
6403 &mut v_raw[rank],
6404 &mut gate[rank],
6405 q_m.in_features,
6406 *local_q_dim,
6407 *local_kv_dim,
6408 out_g,
6409 )?;
6410 }
6411 _ => {
6412 return Err("step TP decode v2 QKV projections mix residency classes".into());
6413 }
6414 }
6415 } else {
6416 for (matrix, local_out, raw) in [
6417 (q_m, ws.local_q_dim, &mut ws.q_raw),
6418 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6419 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6420 ] {
6421 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6422 return Err("step TP decode v2 lost its F32 projection residency".into());
6423 };
6424 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6425 engine.linear_f32_resident_canonical_rows_t1_into(
6426 &decode_input.ranks[rank],
6427 values_w,
6428 &mut raw[rank],
6429 matrix.in_features,
6430 local_out,
6431 chunk_rows,
6432 )?;
6433 }
6434 }
6435 if qkv_fused && defer_norm_rope {
6436 } else if qkv_fused {
6438 let StepTpDecodeV2Ws {
6441 q_raw,
6442 k_raw,
6443 q,
6444 k,
6445 pos,
6446 pos_stage,
6447 ..
6448 } = &mut *ws;
6449 let same_dev = engine.ctx().ordinal() == ws_e_device;
6450 let pos_ref: &CudaSlice<i32> = if same_dev {
6451 pos_stage
6452 .as_ref()
6453 .ok_or("step TP decode v2 pos stage not armed")?
6454 } else {
6455 &pos[rank]
6456 };
6457 engine.qk_norm_rope_into(
6458 &q_raw[rank],
6459 &k_raw[rank],
6460 &q_norm[rank],
6461 &k_norm[rank],
6462 &mut q[rank],
6463 &mut k[rank],
6464 pos_ref,
6465 head_dim,
6466 n_rot,
6467 local_heads,
6468 local_kv_heads,
6469 rms_eps,
6470 rope_base,
6471 1.0,
6472 rope_freqs[rank],
6473 )?;
6474 } else {
6475 engine.rms_norm(
6476 &ws.q_raw[rank],
6477 &q_norm[rank],
6478 &mut ws.q[rank],
6479 head_dim,
6480 local_heads,
6481 rms_eps,
6482 )?;
6483 engine.rms_norm(
6484 &ws.k_raw[rank],
6485 &k_norm[rank],
6486 &mut ws.k[rank],
6487 head_dim,
6488 local_kv_heads,
6489 rms_eps,
6490 )?;
6491 {
6492 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6493 engine
6494 .stream()
6495 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6496 }
6497 engine.rope_neox2(
6498 &mut ws.q[rank],
6499 &mut ws.k[rank],
6500 &ws.pos[rank],
6501 head_dim,
6502 n_rot,
6503 local_heads,
6504 local_kv_heads,
6505 1,
6506 rope_base,
6507 1.0,
6508 rope_freqs[rank],
6509 )?;
6510 }
6511 if gate_shards.is_none() {
6512 let gate_start = rank * (ws.heads / ranks);
6513 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6514 engine.stream().memcpy_dtod(
6515 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6516 &mut gate_dst,
6517 )?;
6518 }
6519 Ok(())
6520 }
6521
6522 pub(crate) fn decode_v2_finish_rank_partial(
6526 &self,
6527 ws: &mut StepTpDecodeV2Ws,
6528 o_m: &ResidentStepBf16RowParallel,
6529 o_fused: bool,
6530 rank: usize,
6531 ) -> Result<(), Box<dyn std::error::Error>> {
6532 let engine = &self.ranks[rank];
6533 let _main = engine.gpu.enter_main()?;
6534 if o_fused {
6535 let StepTpDecodeV2Ws {
6536 gated,
6537 o_partials,
6538 o_block_cols,
6539 o_out,
6540 ..
6541 } = &mut *ws;
6542 let all_f32 = o_m.ranks[rank]
6543 .iter()
6544 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6545 if all_f32 {
6546 let mut weights = Vec::with_capacity(4);
6547 for block in 0..4 {
6548 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6549 unreachable!("all_f32 checked above");
6550 };
6551 weights.push(weight);
6552 }
6553 engine.matvec_f32_b4_into(
6554 [weights[0], weights[1], weights[2], weights[3]],
6555 &gated[rank],
6556 &mut o_partials[rank][0],
6557 *o_block_cols,
6558 *o_out,
6559 )?;
6560 } else {
6561 let mut weights = Vec::with_capacity(4);
6562 for block in 0..4 {
6563 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6564 return Err("step TP decode v2 O projections mix residency classes".into());
6565 };
6566 weights.push(weight);
6567 }
6568 engine.matvec_bf16_b4_into(
6569 [weights[0], weights[1], weights[2], weights[3]],
6570 &gated[rank],
6571 &mut o_partials[rank][0],
6572 *o_block_cols,
6573 *o_out,
6574 )?;
6575 }
6576 } else {
6577 for block in 0..ws.blocks_per_rank {
6578 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6579 return Err("step TP decode v2 lost its F32 O residency".into());
6580 };
6581 let x =
6582 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
6583 let w = weight.slice(0..weight.len());
6584 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
6585 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
6586 }
6587 }
6588 Ok(())
6589 }
6590
6591 pub(crate) fn decode_v2_finish(
6599 &self,
6600 ws: &mut StepTpDecodeV2Ws,
6601 e: &Engine,
6602 o_m: &ResidentStepBf16RowParallel,
6603 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6604 let ranks = self.ranks.len();
6605 if e.ctx().ordinal() != ws.e_device {
6606 return Err("step TP decode v2 finish engine changed".into());
6607 }
6608 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
6613
6614 for rank in 0..ranks {
6617 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
6618 if rank == 0 {
6619 continue;
6622 }
6623 let engine = &self.ranks[rank];
6624 let _main = engine.gpu.enter_main()?;
6625 ws.ev_rank[rank].record(&engine.stream())?;
6626 }
6627
6628 let root = &self.ranks[0];
6630 #[allow(unused_assignments)]
6631 let mut final_in_a = false;
6632 {
6633 let _main = root.gpu.enter_main()?;
6634 for ev in ws.ev_rank.iter().skip(1) {
6635 root.stream().wait(ev)?;
6636 }
6637 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
6638 ws.ev_oproj.record(&root.stream())?;
6644 let _main = e.gpu.enter_main()?;
6645 e.stream().wait(&ws.ev_oproj)?;
6646 let mut output = e.uninit(ws.o_out)?;
6647 if oproj_tail_on() && oproj_tail_eligible() {
6648 use cudarc::driver::DevicePtr;
6651 let stream = e.stream();
6652 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
6653 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
6654 set_oproj_tail((p0 as u64, p1 as u64));
6655 return Ok(output);
6656 }
6657 e.add(
6658 &ws.o_partials[0][0],
6659 &ws.o_partials[1][0],
6660 &mut output,
6661 ws.o_out,
6662 )?;
6663 return Ok(output);
6664 }
6665 if o_fused {
6666 self.decode_v2_finish_root_fused(ws)?;
6667 ws.ev_oproj.record(&root.stream())?;
6668 let _main = e.gpu.enter_main()?;
6669 e.stream().wait(&ws.ev_oproj)?;
6670 let mut output = e.uninit(ws.o_out)?;
6671 e.stream().memcpy_dtod(
6672 &ws.reduce_a.slice(0..ws.o_out),
6673 &mut output.slice_mut(0..ws.o_out),
6674 )?;
6675 return Ok(output);
6676 }
6677 let mut first = true;
6678 let mut current_is_a = false;
6679 for rank in 0..ranks {
6680 for block in 0..ws.blocks_per_rank {
6681 let use_peer = rank != 0;
6682 if use_peer {
6683 root.stream()
6684 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
6685 }
6686 match (first, current_is_a, use_peer) {
6688 (true, _, true) => {
6689 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6690 }
6691 (true, _, false) => root.add(
6692 &ws.zeros,
6693 &ws.o_partials[0][block],
6694 &mut ws.reduce_a,
6695 ws.o_out,
6696 )?,
6697 (false, true, true) => {
6698 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
6699 }
6700 (false, true, false) => root.add(
6701 &ws.reduce_a,
6702 &ws.o_partials[0][block],
6703 &mut ws.reduce_b,
6704 ws.o_out,
6705 )?,
6706 (false, false, true) => {
6707 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6708 }
6709 (false, false, false) => root.add(
6710 &ws.reduce_b,
6711 &ws.o_partials[0][block],
6712 &mut ws.reduce_a,
6713 ws.o_out,
6714 )?,
6715 }
6716 current_is_a = first || !current_is_a;
6717 first = false;
6718 }
6719 }
6720 final_in_a = current_is_a;
6721
6722 for rank in 0..ranks {
6723 let start = rank * ws.local_kv_dim;
6724 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
6725 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
6726 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
6727 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
6728 }
6729 ws.ev_oproj.record(&root.stream())?;
6730 }
6731
6732 let _main = e.gpu.enter_main()?;
6736 e.stream().wait(&ws.ev_oproj)?;
6737 let mut output = e.uninit(ws.o_out)?;
6738 let source = if final_in_a {
6739 &ws.reduce_a
6740 } else {
6741 &ws.reduce_b
6742 };
6743 e.stream().memcpy_dtod(
6744 &source.slice(0..ws.o_out),
6745 &mut output.slice_mut(0..ws.o_out),
6746 )?;
6747 Ok(output)
6748 }
6749
6750 pub fn run_routed_experts(
6751 &self,
6752 experts: &ResidentExpertParallel,
6753 input: &[f32],
6754 tokens: usize,
6755 selected: &[usize],
6756 route_weights: &[f32],
6757 experts_per_token: usize,
6758 activation_limit: Option<f32>,
6759 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6760 validate_step_expert_activation_limit(activation_limit)?;
6761 validate_ep_residency(&self.ranks, experts)?;
6762 validate_activations(input, tokens, experts.input_width)?;
6763 let pairs = tokens
6764 .checked_mul(experts_per_token)
6765 .ok_or("EP route count overflow")?;
6766 if selected.len() != pairs || route_weights.len() != pairs {
6767 return Err(format!(
6768 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
6769 {experts_per_token} ({pairs})",
6770 selected.len(),
6771 route_weights.len(),
6772 )
6773 .into());
6774 }
6775 if !route_weights.iter().all(|weight| weight.is_finite()) {
6776 return Err("EP route weights contain a non-finite value".into());
6777 }
6778 if self.native_p2p {
6779 return self.run_routed_experts_native(
6780 experts,
6781 input,
6782 tokens,
6783 selected,
6784 route_weights,
6785 experts_per_token,
6786 activation_limit,
6787 );
6788 }
6789
6790 let mut output = vec![0.0f32; tokens * experts.input_width];
6791 let per_rank = experts.expert_count / experts.ranks.len();
6792 for token in 0..tokens {
6793 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6794 for slot in 0..experts_per_token {
6795 let pair = token * experts_per_token + slot;
6796 let expert = selected[pair];
6797 if expert >= experts.expert_count {
6798 return Err(format!(
6799 "EP selected expert {expert} outside 0..{}",
6800 experts.expert_count
6801 )
6802 .into());
6803 }
6804 let owner = expert / per_rank;
6805 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6806 let rank = &experts.ranks[owner];
6807 let engine = &self.ranks[owner];
6808 let gate =
6809 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
6810 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
6811 let activated: Vec<f32> = gate
6812 .iter()
6813 .zip(&up)
6814 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6815 .collect();
6816 debug_assert_eq!(activated.len(), experts.expert_width);
6817 let down =
6818 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
6819 let weight = route_weights[pair];
6820 for (sum, value) in output
6821 [token * experts.input_width..(token + 1) * experts.input_width]
6822 .iter_mut()
6823 .zip(down)
6824 {
6825 *sum += weight * value;
6826 }
6827 }
6828 }
6829 Ok(output)
6830 }
6831
6832 fn run_routed_experts_native(
6833 &self,
6834 experts: &ResidentExpertParallel,
6835 input: &[f32],
6836 tokens: usize,
6837 selected: &[usize],
6838 route_weights: &[f32],
6839 experts_per_token: usize,
6840 activation_limit: Option<f32>,
6841 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6842 if !self.native_p2p || self.ranks.len() < 2 {
6843 return Err("native EP execution requires at least two P2P ranks".into());
6844 }
6845 if self.ep_device_arithmetic {
6846 return self.run_routed_experts_native_device(
6847 experts,
6848 input,
6849 tokens,
6850 selected,
6851 route_weights,
6852 experts_per_token,
6853 activation_limit,
6854 );
6855 }
6856 let mut output = vec![0.0f32; tokens * experts.input_width];
6857 let per_rank = experts.expert_count / experts.ranks.len();
6858 for token in 0..tokens {
6859 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6860 let mut rank_inputs = (0..self.ranks.len())
6861 .map(|_| None)
6862 .collect::<Vec<Option<CudaSlice<f32>>>>();
6863 rank_inputs[0] = Some({
6864 let root = &self.ranks[0];
6865 let _main = root.gpu.enter_main()?;
6866 root.htod(input_row)?
6867 });
6868
6869 for slot in 0..experts_per_token {
6870 let pair = token * experts_per_token + slot;
6871 let expert = selected[pair];
6872 if expert >= experts.expert_count {
6873 return Err(format!(
6874 "EP selected expert {expert} outside 0..{}",
6875 experts.expert_count
6876 )
6877 .into());
6878 }
6879 let owner = expert / per_rank;
6880 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6881 if rank_inputs[owner].is_none() {
6882 let peer_input = {
6883 let root_input = rank_inputs[0]
6884 .as_ref()
6885 .ok_or("native EP lost its root input")?;
6886 let engine = &self.ranks[owner];
6887 let _main = engine.gpu.enter_main()?;
6888 let mut peer_input = engine.uninit(experts.input_width)?;
6889 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6890 peer_input
6891 };
6892 rank_inputs[owner] = Some(peer_input);
6893 }
6894
6895 let rank = &experts.ranks[owner];
6896 let engine = &self.ranks[owner];
6897 let owner_input = rank_inputs[owner]
6898 .as_ref()
6899 .ok_or("native EP owner input is absent after dispatch")?;
6900 let gate = run_resident_bank_expert_device(
6901 engine,
6902 &rank.gate,
6903 local_expert,
6904 owner_input,
6905 1,
6906 )?;
6907 let up = run_resident_bank_expert_device(
6908 engine,
6909 &rank.up,
6910 local_expert,
6911 owner_input,
6912 1,
6913 )?;
6914 let (gate, up) = {
6915 let _main = engine.gpu.enter_main()?;
6916 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
6917 };
6918 let activated = gate
6919 .iter()
6920 .zip(&up)
6921 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6922 .collect::<Vec<_>>();
6923 debug_assert_eq!(activated.len(), experts.expert_width);
6924 let activated = {
6925 let _main = engine.gpu.enter_main()?;
6926 engine.htod(&activated)?
6927 };
6928 let down = run_resident_bank_expert_device(
6929 engine,
6930 &rank.down,
6931 local_expert,
6932 &activated,
6933 1,
6934 )?;
6935 let down = if owner == 0 {
6936 let _main = engine.gpu.enter_main()?;
6937 engine.dtoh(&down)?
6938 } else {
6939 let root = &self.ranks[0];
6940 let _main = root.gpu.enter_main()?;
6941 let mut root_down = root.uninit(experts.input_width)?;
6942 root.stream().memcpy_dtod(&down, &mut root_down)?;
6943 root.dtoh(&root_down)?
6944 };
6945 let weight = route_weights[pair];
6946 for (sum, value) in output
6947 [token * experts.input_width..(token + 1) * experts.input_width]
6948 .iter_mut()
6949 .zip(down)
6950 {
6951 *sum += weight * value;
6952 }
6953 }
6954 }
6955 Ok(output)
6956 }
6957
6958 fn run_routed_experts_native_device(
6959 &self,
6960 experts: &ResidentExpertParallel,
6961 input: &[f32],
6962 tokens: usize,
6963 selected: &[usize],
6964 route_weights: &[f32],
6965 experts_per_token: usize,
6966 activation_limit: Option<f32>,
6967 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6968 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
6969 return Err(
6970 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
6971 );
6972 }
6973 let mut output = Vec::with_capacity(tokens * experts.input_width);
6974 let per_rank = experts.expert_count / experts.ranks.len();
6975 let root = &self.ranks[0];
6976 for token in 0..tokens {
6977 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6978 let mut rank_inputs = (0..self.ranks.len())
6979 .map(|_| None)
6980 .collect::<Vec<Option<CudaSlice<f32>>>>();
6981 rank_inputs[0] = Some({
6982 let _main = root.gpu.enter_main()?;
6983 root.htod(input_row)?
6984 });
6985 let mut root_output = {
6986 let _main = root.gpu.enter_main()?;
6987 root.zeros(experts.input_width)?
6988 };
6989 let mut remote_down_keepalive = Vec::new();
6990
6991 for slot in 0..experts_per_token {
6992 let pair = token * experts_per_token + slot;
6993 let expert = selected[pair];
6994 if expert >= experts.expert_count {
6995 return Err(format!(
6996 "EP selected expert {expert} outside 0..{}",
6997 experts.expert_count
6998 )
6999 .into());
7000 }
7001 let owner = expert / per_rank;
7002 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
7003 if rank_inputs[owner].is_none() {
7004 let peer_input = {
7005 let root_input = rank_inputs[0]
7006 .as_ref()
7007 .ok_or("native EP lost its root input")?;
7008 let engine = &self.ranks[owner];
7009 let _main = engine.gpu.enter_main()?;
7010 let mut peer_input = engine.uninit(experts.input_width)?;
7011 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
7012 peer_input
7013 };
7014 rank_inputs[owner] = Some(peer_input);
7015 }
7016
7017 let rank = &experts.ranks[owner];
7018 let engine = &self.ranks[owner];
7019 let owner_input = rank_inputs[owner]
7020 .as_ref()
7021 .ok_or("native EP owner input is absent after dispatch")?;
7022 let gate = run_resident_bank_expert_device(
7023 engine,
7024 &rank.gate,
7025 local_expert,
7026 owner_input,
7027 1,
7028 )?;
7029 let up = run_resident_bank_expert_device(
7030 engine,
7031 &rank.up,
7032 local_expert,
7033 owner_input,
7034 1,
7035 )?;
7036 let activated = {
7037 let _main = engine.gpu.enter_main()?;
7038 let mut activated = engine.uninit(experts.expert_width)?;
7039 if let Some(limit) = activation_limit {
7040 engine.silu_clamped_mul_host_expf(
7041 &gate,
7042 &up,
7043 limit,
7044 &mut activated,
7045 experts.expert_width,
7046 )?;
7047 } else {
7048 engine.silu_mul_host_expf(
7049 &gate,
7050 &up,
7051 &mut activated,
7052 experts.expert_width,
7053 )?;
7054 }
7055 activated
7056 };
7057 let down = run_resident_bank_expert_device(
7058 engine,
7059 &rank.down,
7060 local_expert,
7061 &activated,
7062 1,
7063 )?;
7064 let root_down = if owner == 0 {
7065 down
7066 } else {
7067 let _main = root.gpu.enter_main()?;
7068 let mut root_down = root.uninit(experts.input_width)?;
7069 root.stream().memcpy_dtod(&down, &mut root_down)?;
7070 remote_down_keepalive.push(down);
7074 root_down
7075 };
7076 let _main = root.gpu.enter_main()?;
7077 let mut destination = root_output.slice_mut(0..experts.input_width);
7078 root.axpy_host_into(
7079 &root_down.slice(0..root_down.len()),
7080 route_weights[pair],
7081 &mut destination,
7082 experts.input_width,
7083 )?;
7084 }
7085
7086 let _main = root.gpu.enter_main()?;
7087 let root_output = root.dtoh(&root_output)?;
7088 drop(remote_down_keepalive);
7089 output.extend(root_output);
7090 }
7091 Ok(output)
7092 }
7093}
7094
7095fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7096 if matrix.out_features % tp != 0 {
7097 return Err(format!(
7098 "column-parallel out_features {} is not divisible by TP={tp}",
7099 matrix.out_features
7100 ));
7101 }
7102 let local_out = matrix.out_features / tp;
7103 if local_out % FP8_BLOCK != 0 {
7104 return Err(format!(
7105 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
7106 E4M3 scale block"
7107 ));
7108 }
7109 Ok(())
7110}
7111
7112fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
7113 if !matches!(tp, 1 | 2 | 4 | 8) {
7114 return Err(format!(
7115 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7116 ));
7117 }
7118 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
7119 return Err(format!(
7120 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
7121 ));
7122 }
7123 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
7124 let local_out = out_features / tp;
7125 if local_out % canonical_rows != 0 {
7126 return Err(format!(
7127 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
7128 {canonical_rows}-row chunks"
7129 ));
7130 }
7131 Ok(canonical_rows)
7132}
7133
7134fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
7135 if !matches!(tp, 1 | 2 | 4 | 8) {
7136 return Err(format!(
7137 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
7138 ));
7139 }
7140 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
7141 return Err(format!(
7142 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
7143 ));
7144 }
7145 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
7146 let local_in = in_features / tp;
7147 if local_in % canonical_cols != 0 {
7148 return Err(format!(
7149 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
7150 {canonical_cols}-column chunks"
7151 ));
7152 }
7153 Ok(canonical_cols)
7154}
7155
7156fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
7157 if matrix.in_features % tp != 0 {
7158 return Err(format!(
7159 "row-parallel in_features {} is not divisible by TP={tp}",
7160 matrix.in_features
7161 ));
7162 }
7163 let local_in = matrix.in_features / tp;
7164 if local_in % FP8_BLOCK != 0 {
7165 return Err(format!(
7166 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
7167 E4M3 scale block"
7168 ));
7169 }
7170 Ok(())
7171}
7172
7173fn upload_rank(
7174 engine: &Engine,
7175 matrix: E4m3BlockMatrix<'_>,
7176) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
7177 let _main = engine.gpu.enter_main()?;
7178 matrix.validate()?;
7179 Ok(ResidentE4m3Rank {
7180 codes: engine.htod_bytes(matrix.codes)?,
7181 scales: engine.htod(matrix.scales)?,
7182 out_features: matrix.out_features,
7183 in_features: matrix.in_features,
7184 })
7185}
7186
7187fn upload_bf16_rank(
7188 engine: &Engine,
7189 matrix: Bf16Matrix<'_>,
7190 f32_mirror: bool,
7191) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
7192 let _main = engine.gpu.enter_main()?;
7193 matrix.validate()?;
7194 let bytes = engine.htod_bytes(matrix.bytes)?;
7195 let weight = if f32_mirror {
7196 let values = matrix
7197 .out_features
7198 .checked_mul(matrix.in_features)
7199 .ok_or("resident BF16 mirror element count overflow")?;
7200 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
7201 } else {
7202 ResidentBf16Weight::Bf16(bytes)
7203 };
7204 Ok(ResidentBf16Rank {
7205 weight,
7206 out_features: matrix.out_features,
7207 in_features: matrix.in_features,
7208 })
7209}
7210
7211fn upload_expert_bank_rank(
7212 engine: &Engine,
7213 bank: E4m3ExpertBank<'_>,
7214 expert_range: Range<usize>,
7215) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7216 let _main = engine.gpu.enter_main()?;
7217 bank.validate()?;
7218 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
7219 return Err(format!(
7220 "invalid EP expert range {expert_range:?} for {} experts",
7221 bank.expert_count
7222 )
7223 .into());
7224 }
7225 let code_stride = bank.out_features * bank.in_features;
7226 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
7227 Ok(ResidentE4m3ExpertBankRank {
7228 codes: engine.htod_bytes(
7229 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
7230 )?,
7231 scales: engine.htod(
7232 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
7233 )?,
7234 expert_range,
7235 out_features: bank.out_features,
7236 in_features: bank.in_features,
7237 code_stride,
7238 scale_stride,
7239 k_blocks: None,
7240 })
7241}
7242
7243fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7244 if bank.out_features % tp != 0 {
7245 return Err(format!(
7246 "TP expert output width {} is not divisible by TP={tp}",
7247 bank.out_features
7248 ));
7249 }
7250 let local_out = bank.out_features / tp;
7251 if local_out % FP8_BLOCK != 0 {
7252 return Err(format!(
7253 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
7254 ));
7255 }
7256 Ok(())
7257}
7258
7259fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
7260 if bank.in_features % tp != 0 {
7261 return Err(format!(
7262 "TP expert input width {} is not divisible by TP={tp}",
7263 bank.in_features
7264 ));
7265 }
7266 let local_in = bank.in_features / tp;
7267 if local_in % FP8_BLOCK != 0 {
7268 return Err(format!(
7269 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
7270 ));
7271 }
7272 Ok(())
7273}
7274
7275fn upload_column_bank_rank(
7276 engine: &Engine,
7277 bank: E4m3ExpertBank<'_>,
7278 tp: usize,
7279 rank: usize,
7280) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7281 let _main = engine.gpu.enter_main()?;
7282 let packed = pack_column_bank_rank(bank, tp, rank)?;
7283 Ok(ResidentE4m3ExpertBankRank {
7284 codes: engine.htod_bytes(&packed.codes)?,
7285 scales: engine.htod(&packed.scales)?,
7286 expert_range: packed.expert_range,
7287 out_features: packed.out_features,
7288 in_features: packed.in_features,
7289 code_stride: packed.code_stride,
7290 scale_stride: packed.scale_stride,
7291 k_blocks: packed.k_blocks,
7292 })
7293}
7294
7295fn pack_column_bank_rank(
7296 bank: E4m3ExpertBank<'_>,
7297 tp: usize,
7298 rank: usize,
7299) -> Result<PackedE4m3ExpertBankRank, String> {
7300 bank.validate()?;
7301 validate_column_bank_shape(bank, tp)?;
7302 if rank >= tp {
7303 return Err(format!("TP rank {rank} outside 0..{tp}"));
7304 }
7305 let local_out = bank.out_features / tp;
7306 let full_code_stride = bank.out_features * bank.in_features;
7307 let local_code_stride = local_out * bank.in_features;
7308 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7309 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7310 let local_scale_rows = local_out / FP8_BLOCK;
7311 let local_scale_stride = local_scale_rows * scale_cols;
7312 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7313 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7314 let row_start = rank * local_out;
7315 let scale_row_start = rank * local_scale_rows;
7316 for expert in 0..bank.expert_count {
7317 let code_start = expert * full_code_stride + row_start * bank.in_features;
7318 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7319 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7320 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7321 }
7322 Ok(PackedE4m3ExpertBankRank {
7323 codes,
7324 scales,
7325 expert_range: 0..bank.expert_count,
7326 out_features: local_out,
7327 in_features: bank.in_features,
7328 code_stride: local_code_stride,
7329 scale_stride: local_scale_stride,
7330 k_blocks: None,
7331 })
7332}
7333
7334fn upload_row_bank_rank(
7335 engine: &Engine,
7336 bank: E4m3ExpertBank<'_>,
7337 tp: usize,
7338 rank: usize,
7339) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7340 let _main = engine.gpu.enter_main()?;
7341 let packed = pack_row_bank_rank(bank, tp, rank)?;
7342 Ok(ResidentE4m3ExpertBankRank {
7343 codes: engine.htod_bytes(&packed.codes)?,
7344 scales: engine.htod(&packed.scales)?,
7345 expert_range: packed.expert_range,
7346 out_features: packed.out_features,
7347 in_features: packed.in_features,
7348 code_stride: packed.code_stride,
7349 scale_stride: packed.scale_stride,
7350 k_blocks: packed.k_blocks,
7351 })
7352}
7353
7354fn pack_row_bank_rank(
7355 bank: E4m3ExpertBank<'_>,
7356 tp: usize,
7357 rank: usize,
7358) -> Result<PackedE4m3ExpertBankRank, String> {
7359 bank.validate()?;
7360 validate_row_bank_shape(bank, tp)?;
7361 if rank >= tp {
7362 return Err(format!("TP rank {rank} outside 0..{tp}"));
7363 }
7364 let local_in = bank.in_features / tp;
7365 let full_code_stride = bank.out_features * bank.in_features;
7366 let local_code_stride = bank.out_features * local_in;
7367 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7368 let local_scale_cols = local_in / FP8_BLOCK;
7369 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7370 let full_scale_stride = scale_rows * full_scale_cols;
7371 let local_scale_stride = scale_rows * local_scale_cols;
7372 let global_block_start = rank * local_scale_cols;
7373 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7374 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7375 for expert in 0..bank.expert_count {
7376 let expert_code_start = expert * full_code_stride;
7377 let expert_scale_start = expert * full_scale_stride;
7378 for local_block in 0..local_scale_cols {
7379 let global_block = global_block_start + local_block;
7380 let column_start = global_block * FP8_BLOCK;
7381 for row in 0..bank.out_features {
7382 let start = expert_code_start + row * bank.in_features + column_start;
7383 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7384 }
7385 for row in 0..scale_rows {
7386 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7387 }
7388 }
7389 }
7390 Ok(PackedE4m3ExpertBankRank {
7391 codes,
7392 scales,
7393 expert_range: 0..bank.expert_count,
7394 out_features: bank.out_features,
7395 in_features: local_in,
7396 code_stride: local_code_stride,
7397 scale_stride: local_scale_stride,
7398 k_blocks: Some(local_scale_cols),
7399 })
7400}
7401
7402fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7403 if engines.len() != ranks.len() {
7404 return Err(format!(
7405 "resident TP rank count {} != runtime rank count {}",
7406 ranks.len(),
7407 engines.len()
7408 ));
7409 }
7410 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7411 let device = engine.ctx().ordinal();
7412 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7413 return Err(format!(
7414 "resident TP rank {rank} is not owned by runtime device {device}"
7415 ));
7416 }
7417 }
7418 Ok(())
7419}
7420
7421fn validate_tp_bank_residency(
7422 engines: &[Engine],
7423 experts: &ResidentTpExpertBank,
7424) -> Result<(), String> {
7425 if engines.len() != experts.gate.len()
7426 || engines.len() != experts.up.len()
7427 || engines.len() != experts.down.len()
7428 {
7429 return Err(format!(
7430 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7431 experts.gate.len(),
7432 experts.up.len(),
7433 experts.down.len(),
7434 engines.len()
7435 ));
7436 }
7437 for (rank, engine) in engines.iter().enumerate() {
7438 let device = engine.ctx().ordinal();
7439 for (projection, bank) in [
7440 ("gate", &experts.gate[rank]),
7441 ("up", &experts.up[rank]),
7442 ("down", &experts.down[rank]),
7443 ] {
7444 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7445 return Err(format!(
7446 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7447 {device}"
7448 ));
7449 }
7450 }
7451 }
7452 Ok(())
7453}
7454
7455fn validate_ep_residency(
7456 engines: &[Engine],
7457 experts: &ResidentExpertParallel,
7458) -> Result<(), String> {
7459 if engines.len() != experts.ranks.len() {
7460 return Err(format!(
7461 "resident EP rank count {} != runtime rank count {}",
7462 experts.ranks.len(),
7463 engines.len()
7464 ));
7465 }
7466 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7467 let device = engine.ctx().ordinal();
7468 for (projection, bank) in [
7469 ("gate", &resident.gate),
7470 ("up", &resident.up),
7471 ("down", &resident.down),
7472 ] {
7473 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7474 return Err(format!(
7475 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7476 {device}"
7477 ));
7478 }
7479 }
7480 }
7481 Ok(())
7482}
7483
7484fn run_rank(
7485 engine: &Engine,
7486 matrix: E4m3BlockMatrix<'_>,
7487 activations: &[f32],
7488 tokens: usize,
7489) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7490 let _main = engine.gpu.enter_main()?;
7491 let codes = engine.htod_bytes(matrix.codes)?;
7492 let scales = engine.htod(matrix.scales)?;
7493 let activations = engine.htod(activations)?;
7494 let output = engine.qmatvec_mmq_fp8_blk(
7495 &codes,
7496 &scales,
7497 &activations,
7498 tokens,
7499 matrix.in_features,
7500 matrix.out_features,
7501 )?;
7502 engine.dtoh(&output)
7503}
7504
7505fn run_resident_rank(
7506 engine: &Engine,
7507 matrix: &ResidentE4m3Rank,
7508 activations: &[f32],
7509 tokens: usize,
7510) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7511 let _main = engine.gpu.enter_main()?;
7512 let activations = engine.htod(activations)?;
7513 let output = engine.qmatvec_mmq_fp8_blk(
7514 &matrix.codes,
7515 &matrix.scales,
7516 &activations,
7517 tokens,
7518 matrix.in_features,
7519 matrix.out_features,
7520 )?;
7521 engine.dtoh(&output)
7522}
7523
7524fn run_resident_bf16_rank(
7525 engine: &Engine,
7526 matrix: &ResidentBf16Rank,
7527 activations: &[f32],
7528 tokens: usize,
7529 canonical_chunk_rows: Option<usize>,
7530) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7531 let _main = engine.gpu.enter_main()?;
7532 let activations = engine.htod(activations)?;
7533 let output = run_resident_bf16_rank_device(
7534 engine,
7535 matrix,
7536 &activations,
7537 tokens,
7538 canonical_chunk_rows,
7539 false,
7540 )?;
7541 engine.dtoh(&output)
7542}
7543
7544fn run_resident_bf16_rank_device(
7545 engine: &Engine,
7546 matrix: &ResidentBf16Rank,
7547 activations: &CudaSlice<f32>,
7548 tokens: usize,
7549 canonical_chunk_rows: Option<usize>,
7550 strided_chunk_output: bool,
7551) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7552 let _main = engine.gpu.enter_main()?;
7553 if activations.ordinal() != engine.ctx().ordinal() {
7554 return Err(format!(
7555 "resident BF16 activation device {} != rank device {}",
7556 activations.ordinal(),
7557 engine.ctx().ordinal()
7558 )
7559 .into());
7560 }
7561 if activations.len() != tokens * matrix.in_features {
7562 return Err(format!(
7563 "resident BF16 activation count {} != {tokens}x{}",
7564 activations.len(),
7565 matrix.in_features
7566 )
7567 .into());
7568 }
7569 match (&matrix.weight, canonical_chunk_rows) {
7570 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
7571 .linear_bf16_resident_canonical_rows(
7572 activations,
7573 bytes,
7574 tokens,
7575 matrix.in_features,
7576 matrix.out_features,
7577 rows,
7578 ),
7579 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
7580 activations,
7581 bytes,
7582 tokens,
7583 matrix.in_features,
7584 matrix.out_features,
7585 ),
7586 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
7587 .linear_f32_resident_canonical_rows_strided(
7588 activations,
7589 values,
7590 tokens,
7591 matrix.in_features,
7592 matrix.out_features,
7593 rows,
7594 ),
7595 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
7596 activations,
7597 values,
7598 tokens,
7599 matrix.in_features,
7600 matrix.out_features,
7601 rows,
7602 ),
7603 (ResidentBf16Weight::F32(values), None) => engine.linear(
7604 activations,
7605 values,
7606 tokens,
7607 matrix.in_features,
7608 matrix.out_features,
7609 ),
7610 }
7611}
7612
7613fn validate_resident_bf16_ranks(
7614 engines: &[Engine],
7615 ranks: &[ResidentBf16Rank],
7616) -> Result<(), String> {
7617 if engines.len() != ranks.len() {
7618 return Err(format!(
7619 "resident BF16 TP rank count {} != runtime rank count {}",
7620 ranks.len(),
7621 engines.len(),
7622 ));
7623 }
7624 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7625 let device = engine.ctx().ordinal();
7626 if matrix.weight.ordinal() != device {
7627 return Err(format!(
7628 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
7629 ));
7630 }
7631 }
7632 Ok(())
7633}
7634
7635fn validate_step_bf16_row_residency(
7636 engines: &[Engine],
7637 matrix: &ResidentStepBf16RowParallel,
7638) -> Result<(), String> {
7639 if engines.len() != matrix.ranks.len() {
7640 return Err(format!(
7641 "resident Step BF16 row rank count {} != runtime rank count {}",
7642 matrix.ranks.len(),
7643 engines.len(),
7644 ));
7645 }
7646 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
7647 if matrix.canonical_chunk_cols != canonical_cols {
7648 return Err(format!(
7649 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
7650 matrix.canonical_chunk_cols
7651 ));
7652 }
7653 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
7654 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
7655 if blocks.len() != blocks_per_rank {
7656 return Err(format!(
7657 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
7658 blocks.len()
7659 ));
7660 }
7661 let device = engine.ctx().ordinal();
7662 for (block, resident) in blocks.iter().enumerate() {
7663 if resident.weight.ordinal() != device
7664 || resident.in_features != canonical_cols
7665 || resident.out_features != matrix.out_features
7666 {
7667 return Err(format!(
7668 "resident Step BF16 row rank {rank} block {block} has inconsistent \
7669 device or geometry"
7670 ));
7671 }
7672 }
7673 }
7674 Ok(())
7675}
7676
7677fn validate_replicated_device_rows(
7678 engines: &[Engine],
7679 rows: &ResidentReplicatedDeviceRows,
7680) -> Result<(), String> {
7681 let rank_lengths = rows
7682 .ranks
7683 .iter()
7684 .map(|rank_rows| rank_rows.len())
7685 .collect::<Vec<_>>();
7686 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
7687 if rows
7688 .ranks
7689 .iter()
7690 .zip(engines)
7691 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
7692 {
7693 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
7694 }
7695 Ok(())
7696}
7697
7698fn replicated_device_row_values(
7699 tokens: usize,
7700 width: usize,
7701 expected_ranks: usize,
7702 rank_lengths: &[usize],
7703) -> Result<usize, String> {
7704 let values = tokens
7705 .checked_mul(width)
7706 .ok_or("replicated device row size overflow")?;
7707 if tokens == 0
7708 || width == 0
7709 || expected_ranks == 0
7710 || rank_lengths.len() != expected_ranks
7711 || rank_lengths.iter().any(|&rank_len| rank_len != values)
7712 {
7713 return Err(format!(
7714 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
7715 tokens,
7716 width,
7717 rank_lengths.len(),
7718 expected_ranks
7719 ));
7720 }
7721 Ok(values)
7722}
7723
7724fn replicated_device_row_source_values(
7725 tokens: usize,
7726 width: usize,
7727 source_len: usize,
7728 source_device: usize,
7729 root_device: usize,
7730) -> Result<usize, String> {
7731 let values = tokens
7732 .checked_mul(width)
7733 .ok_or("replicated device row size overflow")?;
7734 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
7735 return Err(format!(
7736 "replicated device row source has inconsistent geometry/device \
7737 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
7738 ));
7739 }
7740 Ok(values)
7741}
7742
7743fn bf16_column_shard(
7744 matrix: Bf16Matrix<'_>,
7745 tp: usize,
7746 rank: usize,
7747) -> Result<Bf16Matrix<'_>, String> {
7748 matrix.validate()?;
7749 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
7750 return Err(format!(
7751 "invalid BF16 column shard out={} TP={tp} rank={rank}",
7752 matrix.out_features
7753 ));
7754 }
7755 let local_out = matrix.out_features / tp;
7756 let row_bytes = matrix.in_features * 2;
7757 let start = rank * local_out * row_bytes;
7758 Ok(Bf16Matrix {
7759 bytes: &matrix.bytes[start..start + local_out * row_bytes],
7760 out_features: local_out,
7761 in_features: matrix.in_features,
7762 })
7763}
7764
7765fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
7766 matrix.validate()?;
7767 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
7768 return Err(format!(
7769 "invalid BF16 row shard in={} TP={tp} rank={rank}",
7770 matrix.in_features
7771 ));
7772 }
7773 let local_in = matrix.in_features / tp;
7774 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
7775 for row in 0..matrix.out_features {
7776 let start = (row * matrix.in_features + rank * local_in) * 2;
7777 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
7778 }
7779 Ok(bytes)
7780}
7781
7782fn bf16_row_block(
7783 matrix: Bf16Matrix<'_>,
7784 col_start: usize,
7785 block_cols: usize,
7786) -> Result<Vec<u8>, String> {
7787 matrix.validate()?;
7788 let col_end = col_start
7789 .checked_add(block_cols)
7790 .ok_or("BF16 row block column overflow")?;
7791 if block_cols == 0 || col_end > matrix.in_features {
7792 return Err(format!(
7793 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
7794 matrix.in_features
7795 ));
7796 }
7797 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
7798 for row in 0..matrix.out_features {
7799 let start = (row * matrix.in_features + col_start) * 2;
7800 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
7801 }
7802 Ok(bytes)
7803}
7804
7805fn run_resident_bank_expert(
7806 engine: &Engine,
7807 bank: &ResidentE4m3ExpertBankRank,
7808 local_expert: usize,
7809 activations: &[f32],
7810 tokens: usize,
7811) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7812 let _main = engine.gpu.enter_main()?;
7813 if bank.k_blocks.is_some() {
7814 return Err("block-major TP row bank requires canonical block execution".into());
7815 }
7816 let local_count = bank.expert_range.end - bank.expert_range.start;
7817 if local_expert >= local_count {
7818 return Err(format!(
7819 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
7820 bank.expert_range
7821 )
7822 .into());
7823 }
7824 validate_activations(activations, tokens, bank.in_features)?;
7825 let activations = engine.htod(activations)?;
7826 let weight = bank
7827 .codes
7828 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7829 let scales = bank
7830 .scales
7831 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7832 let input = activations.slice(0..activations.len());
7833 let output = engine.qmatvec_mmq_fp8_blk_view(
7834 &weight,
7835 &scales,
7836 &input,
7837 tokens,
7838 bank.in_features,
7839 bank.out_features,
7840 )?;
7841 engine.dtoh(&output)
7842}
7843
7844fn run_resident_bank_expert_block(
7845 engine: &Engine,
7846 bank: &ResidentE4m3ExpertBankRank,
7847 local_expert: usize,
7848 block: usize,
7849 activations: &[f32],
7850) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7851 let _main = engine.gpu.enter_main()?;
7852 let local_count = bank.expert_range.end - bank.expert_range.start;
7853 if local_expert >= local_count {
7854 return Err(format!(
7855 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7856 bank.expert_range
7857 )
7858 .into());
7859 }
7860 let blocks = bank
7861 .k_blocks
7862 .ok_or("TP row bank is not packed in native K-block order")?;
7863 if block >= blocks {
7864 return Err(format!("TP row block {block} outside 0..{blocks}").into());
7865 }
7866 validate_activations(activations, 1, FP8_BLOCK)?;
7867 let block_code_stride = bank.out_features * FP8_BLOCK;
7868 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7869 if bank.in_features != blocks * FP8_BLOCK
7870 || bank.code_stride != blocks * block_code_stride
7871 || bank.scale_stride != blocks * block_scale_stride
7872 {
7873 return Err("TP row bank block-major geometry is inconsistent".into());
7874 }
7875
7876 let expert_code_start = local_expert * bank.code_stride;
7877 let expert_scale_start = local_expert * bank.scale_stride;
7878 let weight = bank.codes.slice(
7879 expert_code_start + block * block_code_stride
7880 ..expert_code_start + (block + 1) * block_code_stride,
7881 );
7882 let scales = bank.scales.slice(
7883 expert_scale_start + block * block_scale_stride
7884 ..expert_scale_start + (block + 1) * block_scale_stride,
7885 );
7886 let activations = engine.htod(activations)?;
7887 let input = activations.slice(0..activations.len());
7888 let output = engine.qmatvec_mmq_fp8_blk_view(
7889 &weight,
7890 &scales,
7891 &input,
7892 1,
7893 FP8_BLOCK,
7894 bank.out_features,
7895 )?;
7896 engine.dtoh(&output)
7897}
7898
7899fn run_resident_bank_expert_device(
7900 engine: &Engine,
7901 bank: &ResidentE4m3ExpertBankRank,
7902 local_expert: usize,
7903 activations: &CudaSlice<f32>,
7904 tokens: usize,
7905) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7906 let _main = engine.gpu.enter_main()?;
7907 if bank.k_blocks.is_some() {
7908 return Err("block-major TP row bank requires canonical block execution".into());
7909 }
7910 let local_count = bank.expert_range.end - bank.expert_range.start;
7911 if local_expert >= local_count {
7912 return Err(format!(
7913 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7914 bank.expert_range
7915 )
7916 .into());
7917 }
7918 let expected = tokens
7919 .checked_mul(bank.in_features)
7920 .ok_or("native TP activation size overflow")?;
7921 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
7922 return Err(format!(
7923 "native TP activation len/device {}/{} != expected {expected}/{}",
7924 activations.len(),
7925 activations.ordinal(),
7926 engine.ctx().ordinal()
7927 )
7928 .into());
7929 }
7930 let weight = bank
7931 .codes
7932 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7933 let scales = bank
7934 .scales
7935 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7936 let input = activations.slice(0..activations.len());
7937 engine.qmatvec_mmq_fp8_blk_view(
7938 &weight,
7939 &scales,
7940 &input,
7941 tokens,
7942 bank.in_features,
7943 bank.out_features,
7944 )
7945}
7946
7947fn run_resident_bank_expert_block_device(
7948 engine: &Engine,
7949 bank: &ResidentE4m3ExpertBankRank,
7950 local_expert: usize,
7951 block: usize,
7952 activations: &cudarc::driver::CudaView<'_, f32>,
7953) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7954 let _main = engine.gpu.enter_main()?;
7955 let local_count = bank.expert_range.end - bank.expert_range.start;
7956 if local_expert >= local_count {
7957 return Err(format!(
7958 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7959 bank.expert_range
7960 )
7961 .into());
7962 }
7963 let blocks = bank
7964 .k_blocks
7965 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
7966 if block >= blocks {
7967 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
7968 }
7969 let activation_device = activations.stream().context().ordinal();
7970 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
7971 return Err(format!(
7972 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
7973 activations.len(),
7974 activation_device,
7975 engine.ctx().ordinal()
7976 )
7977 .into());
7978 }
7979 let block_code_stride = bank.out_features * FP8_BLOCK;
7980 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7981 if bank.in_features != blocks * FP8_BLOCK
7982 || bank.code_stride != blocks * block_code_stride
7983 || bank.scale_stride != blocks * block_scale_stride
7984 {
7985 return Err("native TP row bank block-major geometry is inconsistent".into());
7986 }
7987 let expert_code_start = local_expert * bank.code_stride;
7988 let expert_scale_start = local_expert * bank.scale_stride;
7989 let weight = bank.codes.slice(
7990 expert_code_start + block * block_code_stride
7991 ..expert_code_start + (block + 1) * block_code_stride,
7992 );
7993 let scales = bank.scales.slice(
7994 expert_scale_start + block * block_scale_stride
7995 ..expert_scale_start + (block + 1) * block_scale_stride,
7996 );
7997 engine.qmatvec_mmq_fp8_blk_view(
7998 &weight,
7999 &scales,
8000 activations,
8001 1,
8002 FP8_BLOCK,
8003 bank.out_features,
8004 )
8005}
8006
8007fn configure_native_p2p(
8008 ranks: &[Engine],
8009 devices: &[usize],
8010) -> Result<(), Box<dyn std::error::Error>> {
8011 if ranks.len() != devices.len() || ranks.len() < 2 {
8012 return Err("native TP P2P setup requires matching multi-rank devices".into());
8013 }
8014 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
8015 if engine.ctx().ordinal() != device {
8016 return Err(format!(
8017 "native TP rank {rank} context device {} != requested device {device}",
8018 engine.ctx().ordinal()
8019 )
8020 .into());
8021 }
8022 }
8023
8024 for src in 0..ranks.len() {
8025 for dst in 0..ranks.len() {
8026 if src == dst {
8027 continue;
8028 }
8029 let mut can_access = 0;
8030 unsafe {
8031 cudarc::driver::sys::cuDeviceCanAccessPeer(
8032 &mut can_access,
8033 ranks[src].ctx().cu_device(),
8034 ranks[dst].ctx().cu_device(),
8035 )
8036 .result()?;
8037 }
8038 if can_access == 0 {
8039 return Err(format!(
8040 "native TP requires P2P, but dev{} cannot access dev{}",
8041 devices[src], devices[dst]
8042 )
8043 .into());
8044 }
8045 ranks[src].ctx().bind_to_thread()?;
8046 let rc =
8047 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
8048 use cudarc::driver::sys::cudaError_enum as E;
8049 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
8050 return Err(format!(
8051 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
8052 devices[src], devices[dst]
8053 )
8054 .into());
8055 }
8056 }
8057 }
8058
8059 for &owner in devices {
8060 for &accessor in devices {
8061 if owner == accessor {
8062 continue;
8063 }
8064 let device = cudarc::driver::result::device::get(owner as i32)?;
8065 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
8066 unsafe {
8067 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
8068 }
8069 let desc = cudarc::driver::sys::CUmemAccessDesc {
8070 location: cudarc::driver::sys::CUmemLocation {
8071 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
8072 id: accessor as i32,
8073 },
8074 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
8075 };
8076 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
8077 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
8078 return Err(format!(
8079 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
8080 {rc:?}"
8081 )
8082 .into());
8083 }
8084 }
8085 }
8086
8087 for src in 0..ranks.len() {
8088 for dst in 0..ranks.len() {
8089 if src == dst {
8090 continue;
8091 }
8092 let expected = (0..NATIVE_P2P_PROBE_WORDS)
8093 .map(|index| {
8094 (index as u32)
8095 .wrapping_mul(0x9e37_79b9)
8096 .wrapping_add(((src as u32) << 16) | dst as u32)
8097 })
8098 .collect::<Vec<_>>();
8099 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
8100 let source = ranks[src].htod_u32_v(&expected)?;
8101 let mut destination = ranks[dst].htod_u32_v(&poison)?;
8102 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
8103 let actual = ranks[dst].dtoh_u32(&destination)?;
8104 if actual != expected {
8105 let mismatches = actual
8106 .iter()
8107 .zip(&expected)
8108 .filter(|(actual, expected)| actual != expected)
8109 .count();
8110 return Err(format!(
8111 "native TP peer probe dev{}->dev{} failed: {mismatches}/{} words differ",
8112 devices[src],
8113 devices[dst],
8114 expected.len()
8115 )
8116 .into());
8117 }
8118 }
8119 }
8120 ranks[0].ctx().bind_to_thread()?;
8121 eprintln!(
8122 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
8123 directions={} bytes={} mismatches=0",
8124 ranks.len() * (ranks.len() - 1),
8125 NATIVE_P2P_PROBE_WORDS * std::mem::size_of::<u32>(),
8126 );
8127 Ok(())
8128}
8129
8130fn validate_activations(
8131 activations: &[f32],
8132 tokens: usize,
8133 in_features: usize,
8134) -> Result<(), String> {
8135 let expected = tokens
8136 .checked_mul(in_features)
8137 .ok_or_else(|| "activation size overflow".to_string())?;
8138 if activations.len() != expected {
8139 return Err(format!(
8140 "activation count {} != {tokens}x{in_features} ({expected})",
8141 activations.len()
8142 ));
8143 }
8144 if !activations.iter().all(|value| value.is_finite()) {
8145 return Err("activations contain a non-finite value".to_string());
8146 }
8147 Ok(())
8148}
8149
8150fn column_shard(
8151 matrix: E4m3BlockMatrix<'_>,
8152 tp: usize,
8153 rank: usize,
8154) -> Result<E4m3BlockMatrix<'_>, String> {
8155 let local_out = matrix.out_features / tp;
8156 let row_start = rank * local_out;
8157 let code_start = row_start * matrix.in_features;
8158 let code_end = code_start + local_out * matrix.in_features;
8159 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8160 let local_scale_rows = local_out / FP8_BLOCK;
8161 let scale_start = rank * local_scale_rows * scale_cols;
8162 let scale_end = scale_start + local_scale_rows * scale_cols;
8163 Ok(E4m3BlockMatrix {
8164 codes: &matrix.codes[code_start..code_end],
8165 scales: &matrix.scales[scale_start..scale_end],
8166 out_features: local_out,
8167 in_features: matrix.in_features,
8168 })
8169}
8170
8171fn row_shard(
8172 matrix: E4m3BlockMatrix<'_>,
8173 tp: usize,
8174 rank: usize,
8175) -> Result<(Vec<u8>, Vec<f32>), String> {
8176 let local_in = matrix.in_features / tp;
8177 let col_start = rank * local_in;
8178 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
8179 for row in 0..matrix.out_features {
8180 let start = row * matrix.in_features + col_start;
8181 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
8182 }
8183
8184 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
8185 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
8186 let local_scale_cols = local_in / FP8_BLOCK;
8187 let scale_col_start = rank * local_scale_cols;
8188 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
8189 for row in 0..scale_rows {
8190 let start = row * scale_cols + scale_col_start;
8191 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
8192 }
8193 Ok((codes, scales))
8194}
8195
8196fn activation_shard(
8197 activations: &[f32],
8198 tokens: usize,
8199 in_features: usize,
8200 tp: usize,
8201 rank: usize,
8202) -> Vec<f32> {
8203 let local_in = in_features / tp;
8204 let col_start = rank * local_in;
8205 let mut shard = Vec::with_capacity(tokens * local_in);
8206 for token in 0..tokens {
8207 let start = token * in_features + col_start;
8208 shard.extend_from_slice(&activations[start..start + local_in]);
8209 }
8210 shard
8211}
8212
8213#[derive(Clone, Copy)]
8233pub struct Nvfp4BlockMatrix<'a> {
8234 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
8238 pub in_features: usize,
8239}
8240
8241impl Nvfp4BlockMatrix<'_> {
8242 pub fn validate(&self) -> Result<(), String> {
8243 if self.in_features == 0 || self.out_features == 0 {
8244 return Err("NVFP4 matrix has a zero dimension".to_string());
8245 }
8246 if self.in_features % 64 != 0 {
8247 return Err(format!(
8248 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
8249 self.in_features
8250 ));
8251 }
8252 if self.codes.len() != self.out_features * self.in_features / 2 {
8253 return Err(format!(
8254 "NVFP4 code bytes {} != {}x{}/2",
8255 self.codes.len(),
8256 self.out_features,
8257 self.in_features
8258 ));
8259 }
8260 if self.scales.len() != self.out_features * self.in_features / 16 {
8261 return Err(format!(
8262 "NVFP4 scale bytes {} != {}x{}/16",
8263 self.scales.len(),
8264 self.out_features,
8265 self.in_features
8266 ));
8267 }
8268 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
8269 return Err(format!(
8270 "NVFP4 macro scale {} is not finite-positive",
8271 self.macro_scale
8272 ));
8273 }
8274 Ok(())
8275 }
8276}
8277
8278#[derive(Clone, Copy)]
8280pub struct Nvfp4ExpertBank<'a> {
8281 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8285 pub out_features: usize,
8286 pub in_features: usize,
8287}
8288
8289impl Nvfp4ExpertBank<'_> {
8290 pub fn validate(&self) -> Result<(), String> {
8291 if self.expert_count == 0 {
8292 return Err("NVFP4 expert bank is empty".to_string());
8293 }
8294 if self.macros.len() != self.expert_count {
8295 return Err(format!(
8296 "NVFP4 bank macros {} != expert count {}",
8297 self.macros.len(),
8298 self.expert_count
8299 ));
8300 }
8301 self.expert(0).map(|_| ())
8302 }
8303
8304 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8305 if expert >= self.expert_count {
8306 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8307 }
8308 let code_stride = self.out_features * self.in_features / 2;
8309 let scale_stride = self.out_features * self.in_features / 16;
8310 if self.codes.len() != self.expert_count * code_stride
8311 || self.scales.len() != self.expert_count * scale_stride
8312 {
8313 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8314 }
8315 let matrix = Nvfp4BlockMatrix {
8316 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8317 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8318 macro_scale: self.macros[expert],
8319 out_features: self.out_features,
8320 in_features: self.in_features,
8321 };
8322 matrix.validate()?;
8323 Ok(matrix)
8324 }
8325}
8326
8327pub struct ResidentNvfp4Rank {
8329 blocks: crate::CudaSlice<u8>,
8330 macro_scale: f32,
8331 out_features: usize,
8332 in_features: usize,
8333 row_bytes: usize,
8334}
8335
8336pub struct ResidentNvfp4ColumnParallel {
8337 ranks: Vec<ResidentNvfp4Rank>,
8338 pub out_features: usize,
8339 pub in_features: usize,
8340}
8341
8342pub struct ResidentNvfp4RowParallel {
8343 ranks: Vec<ResidentNvfp4Rank>,
8344 pub out_features: usize,
8345 pub in_features: usize,
8346}
8347
8348pub struct ResidentTpNvfp4Expert {
8349 gate: ResidentNvfp4ColumnParallel,
8350 up: ResidentNvfp4ColumnParallel,
8351 down: ResidentNvfp4RowParallel,
8352 pub input_width: usize,
8353 pub expert_width: usize,
8354}
8355
8356pub struct ResidentNvfp4ColumnBankRank {
8360 bank: crate::CudaSlice<u8>,
8364 expert_bytes: usize,
8365 local_out: usize,
8366 in_features: usize,
8367 row_bytes: usize,
8368}
8369
8370impl ResidentNvfp4ColumnBankRank {
8371 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8372 self.bank
8373 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8374 }
8375}
8376
8377pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8383
8384pub struct ResidentNvfp4RowBankRank {
8385 bank: crate::CudaSlice<u8>,
8387 expert_bytes: usize,
8388 device_rank: usize, out_features: usize,
8390 local_in: usize,
8391 row_bytes: usize,
8392}
8393
8394impl ResidentNvfp4RowBankRank {
8395 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8396 self.bank
8397 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8398 }
8399}
8400
8401impl ResidentNvfp4TensorParallel {
8402 pub(crate) fn device_workspace_handle(
8403 &self,
8404 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8405 &self.device_workspace
8406 }
8407}
8408
8409pub struct ResidentNvfp4TensorParallel {
8410 gate: Vec<ResidentNvfp4ColumnBankRank>,
8411 up: Vec<ResidentNvfp4ColumnBankRank>,
8412 down: Vec<ResidentNvfp4RowBankRank>,
8413 macros_gate: Vec<f32>,
8414 macros_up: Vec<f32>,
8415 macros_down: Vec<f32>,
8416 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8420 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8421 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8422 pub expert_count: usize,
8423 pub input_width: usize,
8424 pub expert_width: usize,
8425 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8428 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8432 pub(crate) ep2: bool,
8436}
8437
8438pub struct Nvfp4T2Workspace {
8442 input2: Vec<crate::CudaSlice<f32>>,
8443 in_q2: Vec<crate::CudaSlice<i8>>,
8444 in_d2: Vec<crate::CudaSlice<f32>>,
8445 sel2: Vec<crate::CudaSlice<i32>>,
8446 route_w2: Vec<crate::CudaSlice<f32>>,
8447 gate_out2: Vec<crate::CudaSlice<f32>>,
8448 up_out2: Vec<crate::CudaSlice<f32>>,
8449 act_q2: Vec<crate::CudaSlice<i8>>,
8450 act_d2: Vec<crate::CudaSlice<f32>>,
8451 partial2: Vec<crate::CudaSlice<f32>>,
8452 acc_a: Vec<crate::CudaSlice<f32>>,
8454 acc_b: Vec<crate::CudaSlice<f32>>,
8455 acc2: Vec<crate::CudaSlice<f32>>,
8458 peer2: crate::CudaSlice<f32>,
8459 omix2: crate::CudaSlice<f32>,
8460 peer_a: crate::CudaSlice<f32>,
8462 peer_b: crate::CudaSlice<f32>,
8463 omix_a: crate::CudaSlice<f32>,
8464 omix_b: crate::CudaSlice<f32>,
8465 ev_entry: CudaEvent,
8466 ev_rank: Vec<CudaEvent>,
8467 ev_root: CudaEvent,
8468 n_sel: usize,
8469 e_device: usize,
8470}
8471
8472struct RoutesGraph {
8479 exec: cudarc::driver::sys::CUgraphExec,
8480 parent: cudarc::driver::sys::CUgraph,
8481 _children: Vec<cudarc::driver::CudaGraph>,
8482}
8483unsafe impl Send for RoutesGraph {}
8486
8487impl Drop for RoutesGraph {
8488 fn drop(&mut self) {
8489 unsafe {
8490 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8491 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8492 }
8493 }
8494}
8495
8496impl Nvfp4DeviceRoutesWorkspace {
8497 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8498 self.in_stage_e.as_ref()
8499 }
8500 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8501 self.in_stage_e.as_mut()
8502 }
8503 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8504 self.out_stage_e.as_mut()
8505 }
8506 pub(crate) fn arm_stages(
8508 &mut self,
8509 e: &Engine,
8510 width: usize,
8511 n_sel: usize,
8512 ) -> Result<(), Box<dyn std::error::Error>> {
8513 let _main = e.gpu.enter_main()?;
8514 if self.in_stage_e.is_none() {
8515 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8516 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8517 }
8518 if self.dev_route_e.is_none() {
8519 self.dev_route_e = Some((
8520 e.htod_i32(&vec![0i32; n_sel])?,
8521 e.htod(&vec![0.0f32; n_sel])?,
8522 ));
8523 }
8524 Ok(())
8525 }
8526
8527 pub(crate) fn in_and_out_stages_mut(
8529 &mut self,
8530 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
8531 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
8532 (Some(input), Some(output)) => Some((input, output)),
8533 _ => None,
8534 }
8535 }
8536 pub(crate) fn dev_route_e_mut(
8537 &mut self,
8538 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
8539 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
8540 }
8541}
8542
8543pub struct Nvfp4DeviceRoutesWorkspace {
8544 gate_out: Vec<crate::CudaSlice<f32>>,
8547 up_out: Vec<crate::CudaSlice<f32>>,
8548 act_q: Vec<crate::CudaSlice<i8>>,
8549 act_d: Vec<crate::CudaSlice<f32>>,
8550 sel: Vec<crate::CudaSlice<i32>>,
8551 partial: Vec<crate::CudaSlice<f32>>,
8552 accumulator: Vec<crate::CudaSlice<f32>>,
8553 combine_w: Vec<crate::CudaSlice<f32>>,
8555 route_w: Vec<crate::CudaSlice<f32>>,
8558 in_q: Vec<crate::CudaSlice<i8>>,
8561 in_d: Vec<crate::CudaSlice<f32>>,
8562 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
8566 prestaged: bool,
8569 rank1_routed: bool,
8572 fence_flags_raw: u64,
8576 fence_ticket: u32,
8577 ev_input: Option<(CudaEvent, usize)>,
8579 in_stage_e: Option<crate::CudaSlice<f32>>,
8582 out_stage_e: Option<crate::CudaSlice<f32>>,
8583 routes_graph: Option<RoutesGraph>,
8584 raw_dev_route_e: Option<(u64, u64)>,
8586 raw_combine: Option<(u64, u64, u64, u64)>,
8587 raw_input: Vec<u64>,
8588 raw_sel: Vec<u64>,
8589 raw_route_w: Vec<u64>,
8590 remote: crate::CudaSlice<f32>,
8591 combined: crate::CudaSlice<f32>,
8592 n_sel: usize,
8593 input: Vec<crate::CudaSlice<f32>>,
8597 ev_rank: Vec<CudaEvent>,
8598 ev_done: Option<CudaEvent>,
8599 ev_entry: Option<(CudaEvent, usize)>,
8600}
8601
8602struct ResidentNvfp4EpRank {
8604 gate: Vec<crate::CudaSlice<u8>>,
8605 up: Vec<crate::CudaSlice<u8>>,
8606 down: Vec<crate::CudaSlice<u8>>,
8607 #[allow(dead_code)]
8608 expert_range: Range<usize>,
8609}
8610
8611pub struct ResidentNvfp4ExpertParallel {
8612 ranks: Vec<ResidentNvfp4EpRank>,
8613 macros_gate: Vec<f32>,
8614 macros_up: Vec<f32>,
8615 macros_down: Vec<f32>,
8616 pub expert_count: usize,
8617 pub input_width: usize,
8618 pub expert_width: usize,
8619 gate_row_bytes: usize,
8620 down_row_bytes: usize,
8621}
8622
8623fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8624 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
8625 matrix.codes,
8626 matrix.scales,
8627 matrix.out_features,
8628 matrix.in_features,
8629 )
8630}
8631
8632fn nvfp4_row_bytes(in_features: usize) -> usize {
8633 in_features / 64 * 36 }
8635
8636pub(crate) fn fuse_rope_append_on() -> bool {
8644 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8645 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
8646}
8647
8648pub(crate) fn no_local_shadow_on() -> bool {
8649 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8650 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
8651}
8652
8653pub(crate) fn nvfp4_bank_v2_on() -> bool {
8654 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8655 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
8656}
8657
8658fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
8662 let row_bytes = nvfp4_row_bytes(in_features);
8663 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
8664 let n_slots = in_features / 32;
8665 let mut out = Vec::with_capacity(v1.len());
8666 for row in 0..out_features {
8667 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
8668 for g in 0..n_slots {
8669 let (sblk, h) = (g / 2, g % 2);
8670 let b = &r[sblk * 36..sblk * 36 + 36];
8671 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
8672 }
8673 for g in 0..n_slots {
8674 let (sblk, h) = (g / 2, g % 2);
8675 let b = &r[sblk * 36..sblk * 36 + 36];
8676 out.push(b[2 * h]);
8677 out.push(b[2 * h + 1]);
8678 }
8679 }
8680 out
8681}
8682
8683fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8685 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
8686 let v1 = nvfp4_repack_matrix(matrix);
8687 if nvfp4_bank_v2_on() {
8688 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
8689 } else {
8690 v1
8691 }
8692}
8693
8694fn nvfp4_column_shard<'a>(
8697 matrix: Nvfp4BlockMatrix<'a>,
8698 tp: usize,
8699 rank: usize,
8700) -> Result<Nvfp4BlockMatrix<'a>, String> {
8701 if matrix.out_features % tp != 0 {
8702 return Err(format!(
8703 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
8704 matrix.out_features
8705 ));
8706 }
8707 let local_out = matrix.out_features / tp;
8708 let code_row = matrix.in_features / 2;
8709 let scale_row = matrix.in_features / 16;
8710 Ok(Nvfp4BlockMatrix {
8711 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
8712 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
8713 macro_scale: matrix.macro_scale,
8714 out_features: local_out,
8715 in_features: matrix.in_features,
8716 })
8717}
8718
8719fn nvfp4_row_shard(
8722 matrix: Nvfp4BlockMatrix<'_>,
8723 tp: usize,
8724 rank: usize,
8725) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
8726 if matrix.in_features % tp != 0 {
8727 return Err(format!(
8728 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
8729 matrix.in_features
8730 ));
8731 }
8732 let local_in = matrix.in_features / tp;
8733 if local_in % 64 != 0 {
8734 return Err(format!(
8735 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
8736 ));
8737 }
8738 let code_row = matrix.in_features / 2;
8739 let scale_row = matrix.in_features / 16;
8740 let local_code = local_in / 2;
8741 let local_scale = local_in / 16;
8742 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
8743 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
8744 for row in 0..matrix.out_features {
8745 let code_start = row * code_row + rank * local_code;
8746 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
8747 let scale_start = row * scale_row + rank * local_scale;
8748 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
8749 }
8750 Ok((codes, scales, local_in))
8751}
8752
8753fn run_rank_nvfp4(
8757 engine: &Engine,
8758 matrix: Nvfp4BlockMatrix<'_>,
8759 activations: &[f32],
8760 tokens: usize,
8761) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8762 matrix.validate()?;
8763 validate_activations(activations, tokens, matrix.in_features)?;
8764 let _main = engine.gpu.enter_main()?;
8765 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
8766 let activations = engine.htod(activations)?;
8767 let output = engine.qmatvec_nvfp4_fast(
8768 &blocks.slice(0..blocks.len()),
8769 &activations,
8770 tokens,
8771 matrix.in_features,
8772 matrix.out_features,
8773 nvfp4_row_bytes(matrix.in_features),
8774 )?;
8775 engine.dtoh(&output)
8776}
8777
8778fn upload_rank_nvfp4(
8779 engine: &Engine,
8780 matrix: Nvfp4BlockMatrix<'_>,
8781) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
8782 matrix.validate()?;
8783 let _main = engine.gpu.enter_main()?;
8784 Ok(ResidentNvfp4Rank {
8785 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
8786 macro_scale: matrix.macro_scale,
8787 out_features: matrix.out_features,
8788 in_features: matrix.in_features,
8789 row_bytes: nvfp4_row_bytes(matrix.in_features),
8790 })
8791}
8792
8793fn run_resident_rank_nvfp4(
8794 engine: &Engine,
8795 rank: &ResidentNvfp4Rank,
8796 activations: &[f32],
8797 tokens: usize,
8798) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8799 validate_activations(activations, tokens, rank.in_features)?;
8800 let _main = engine.gpu.enter_main()?;
8801 let activations = engine.htod(activations)?;
8802 let output = engine.qmatvec_nvfp4_fast(
8803 &rank.blocks.slice(0..rank.blocks.len()),
8804 &activations,
8805 tokens,
8806 rank.in_features,
8807 rank.out_features,
8808 rank.row_bytes,
8809 )?;
8810 engine.dtoh(&output)
8811}
8812
8813fn apply_macro(values: &mut [f32], macro_scale: f32) {
8814 for value in values.iter_mut() {
8815 *value *= macro_scale;
8816 }
8817}
8818
8819impl TpE4m3HostBounce {
8820 pub fn full_nvfp4(
8822 &self,
8823 matrix: Nvfp4BlockMatrix<'_>,
8824 activations: &[f32],
8825 tokens: usize,
8826 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8827 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
8828 apply_macro(&mut output, matrix.macro_scale);
8829 Ok(output)
8830 }
8831
8832 pub fn column_parallel_nvfp4(
8835 &self,
8836 matrix: Nvfp4BlockMatrix<'_>,
8837 activations: &[f32],
8838 tokens: usize,
8839 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
8840 matrix.validate()?;
8841 validate_activations(activations, tokens, matrix.in_features)?;
8842 let tp = self.ranks.len();
8843 let local_out = matrix.out_features / tp;
8844 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8845 let mut rank_outputs = Vec::with_capacity(tp);
8846 for (rank_index, rank) in self.ranks.iter().enumerate() {
8847 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
8848 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
8849 let row_start = rank_index * local_out;
8850 for token in 0..tokens {
8851 gathered[token * matrix.out_features + row_start
8852 ..token * matrix.out_features + row_start + local_out]
8853 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8854 }
8855 rank_outputs.push(output);
8856 }
8857 apply_macro(&mut gathered, matrix.macro_scale);
8858 Ok(ColumnParallelResult {
8859 gathered,
8860 rank_outputs,
8861 })
8862 }
8863
8864 pub fn row_parallel_nvfp4(
8867 &self,
8868 matrix: Nvfp4BlockMatrix<'_>,
8869 activations: &[f32],
8870 tokens: usize,
8871 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
8872 matrix.validate()?;
8873 validate_activations(activations, tokens, matrix.in_features)?;
8874 let tp = self.ranks.len();
8875 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8876 let mut rank_partials = Vec::with_capacity(tp);
8877 for (rank_index, rank) in self.ranks.iter().enumerate() {
8878 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
8879 let local_activations =
8880 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8881 let shard = Nvfp4BlockMatrix {
8882 codes: &codes,
8883 scales: &scales,
8884 macro_scale: matrix.macro_scale,
8885 out_features: matrix.out_features,
8886 in_features: local_in,
8887 };
8888 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
8889 for (sum, value) in reduced.iter_mut().zip(&partial) {
8890 *sum += *value;
8891 }
8892 rank_partials.push(partial);
8893 }
8894 apply_macro(&mut reduced, matrix.macro_scale);
8895 Ok(RowParallelResult {
8896 reduced,
8897 rank_partials,
8898 })
8899 }
8900
8901 pub fn upload_expert_nvfp4(
8902 &self,
8903 gate: Nvfp4BlockMatrix<'_>,
8904 up: Nvfp4BlockMatrix<'_>,
8905 down: Nvfp4BlockMatrix<'_>,
8906 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
8907 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8908 return Err("NVFP4 TP expert gate/up dimensions differ".into());
8909 }
8910 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8911 return Err(format!(
8912 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
8913 down.out_features, down.in_features, gate.out_features, gate.in_features
8914 )
8915 .into());
8916 }
8917 let tp = self.ranks.len();
8918 let mut gate_ranks = Vec::with_capacity(tp);
8919 let mut up_ranks = Vec::with_capacity(tp);
8920 let mut down_ranks = Vec::with_capacity(tp);
8921 for (rank_index, engine) in self.ranks.iter().enumerate() {
8922 gate_ranks.push(upload_rank_nvfp4(
8923 engine,
8924 nvfp4_column_shard(gate, tp, rank_index)?,
8925 )?);
8926 up_ranks.push(upload_rank_nvfp4(
8927 engine,
8928 nvfp4_column_shard(up, tp, rank_index)?,
8929 )?);
8930 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
8931 down_ranks.push(upload_rank_nvfp4(
8932 engine,
8933 Nvfp4BlockMatrix {
8934 codes: &codes,
8935 scales: &scales,
8936 macro_scale: down.macro_scale,
8937 out_features: down.out_features,
8938 in_features: local_in,
8939 },
8940 )?);
8941 }
8942 Ok(ResidentTpNvfp4Expert {
8943 gate: ResidentNvfp4ColumnParallel {
8944 ranks: gate_ranks,
8945 out_features: gate.out_features,
8946 in_features: gate.in_features,
8947 },
8948 up: ResidentNvfp4ColumnParallel {
8949 ranks: up_ranks,
8950 out_features: up.out_features,
8951 in_features: up.in_features,
8952 },
8953 down: ResidentNvfp4RowParallel {
8954 ranks: down_ranks,
8955 out_features: down.out_features,
8956 in_features: down.in_features,
8957 },
8958 input_width: gate.in_features,
8959 expert_width: gate.out_features,
8960 })
8961 }
8962
8963 fn column_parallel_resident_nvfp4(
8964 &self,
8965 matrix: &ResidentNvfp4ColumnParallel,
8966 activations: &[f32],
8967 tokens: usize,
8968 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8969 validate_activations(activations, tokens, matrix.in_features)?;
8970 let local_out = matrix.out_features / self.ranks.len();
8971 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8972 let mut macro_scale = None;
8973 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8974 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
8975 let row_start = rank_index * local_out;
8976 for token in 0..tokens {
8977 gathered[token * matrix.out_features + row_start
8978 ..token * matrix.out_features + row_start + local_out]
8979 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8980 }
8981 macro_scale = Some(shard.macro_scale);
8982 }
8983 apply_macro(
8984 &mut gathered,
8985 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
8986 );
8987 Ok(gathered)
8988 }
8989
8990 fn row_parallel_resident_nvfp4(
8991 &self,
8992 matrix: &ResidentNvfp4RowParallel,
8993 activations: &[f32],
8994 tokens: usize,
8995 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8996 validate_activations(activations, tokens, matrix.in_features)?;
8997 let tp = self.ranks.len();
8998 let local_in = matrix.in_features / tp;
8999 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
9000 let mut macro_scale = None;
9001 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
9002 if shard.in_features != local_in {
9003 return Err(format!(
9004 "NVFP4 resident row shard in_features {} != expected {local_in}",
9005 shard.in_features
9006 )
9007 .into());
9008 }
9009 let local_activations =
9010 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
9011 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
9012 for (sum, value) in reduced.iter_mut().zip(&partial) {
9013 *sum += *value;
9014 }
9015 macro_scale = Some(shard.macro_scale);
9016 }
9017 apply_macro(
9018 &mut reduced,
9019 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
9020 );
9021 Ok(reduced)
9022 }
9023
9024 pub fn run_expert_nvfp4(
9025 &self,
9026 expert: &ResidentTpNvfp4Expert,
9027 input: &[f32],
9028 tokens: usize,
9029 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9030 validate_activations(input, tokens, expert.input_width)?;
9031 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
9032 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
9033 let activated: Vec<f32> = gate
9034 .iter()
9035 .zip(&up)
9036 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
9037 .collect();
9038 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
9039 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
9040 }
9041
9042 pub fn upload_tensor_parallel_nvfp4(
9044 &self,
9045 gate: Nvfp4ExpertBank<'_>,
9046 up: Nvfp4ExpertBank<'_>,
9047 down: Nvfp4ExpertBank<'_>,
9048 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
9049 gate.validate()?;
9050 up.validate()?;
9051 down.validate()?;
9052 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9053 return Err("NVFP4 TP gate/up/down expert counts differ".into());
9054 }
9055 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9056 return Err("NVFP4 TP gate/up dimensions differ".into());
9057 }
9058 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9059 return Err(format!(
9060 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
9061 down.out_features, down.in_features, gate.out_features, gate.in_features
9062 )
9063 .into());
9064 }
9065 let tp = self.ranks.len();
9066 if gate.out_features % tp != 0 {
9067 return Err(format!(
9068 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
9069 gate.out_features
9070 )
9071 .into());
9072 }
9073 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
9074 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
9075 {
9076 return Err(format!(
9077 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
9078 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
9079 down.in_features
9080 )
9081 .into());
9082 }
9083 if tp > NVFP4_CANONICAL_ROW_SHARDS {
9084 return Err(format!(
9085 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
9086 ({NVFP4_CANONICAL_ROW_SHARDS})"
9087 )
9088 .into());
9089 }
9090
9091 let ep2 = step_nvfp4_ep2_on() && tp == 2;
9092 let mut gate_ranks = Vec::with_capacity(tp);
9093 let mut up_ranks = Vec::with_capacity(tp);
9094 let mut macros_gate_dev = Vec::with_capacity(tp);
9095 let mut macros_up_dev = Vec::with_capacity(tp);
9096 let mut macros_down_dev = Vec::with_capacity(tp);
9097 for (rank_index, engine) in self.ranks.iter().enumerate() {
9098 let _main = engine.gpu.enter_main()?;
9099 let mut gate_host: Vec<u8> = Vec::new();
9105 let mut up_host: Vec<u8> = Vec::new();
9106 let mut owned = 0usize;
9107 for expert in 0..gate.expert_count {
9108 if ep2 {
9109 if expert % 2 != rank_index {
9110 continue;
9111 }
9112 owned += 1;
9113 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate.expert(expert)?));
9114 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up.expert(expert)?));
9115 } else {
9116 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
9117 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
9118 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
9119 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
9120 }
9121 }
9122 let bank_experts = if ep2 { owned } else { gate.expert_count };
9123 let gate_expert_bytes = gate_host.len() / bank_experts.max(1);
9124 let up_expert_bytes = up_host.len() / bank_experts.max(1);
9125 let local_out = if ep2 {
9126 gate.out_features
9127 } else {
9128 gate.out_features / tp
9129 };
9130 gate_ranks.push(ResidentNvfp4ColumnBankRank {
9131 bank: engine.htod_bytes(&gate_host)?,
9132 expert_bytes: gate_expert_bytes,
9133 local_out,
9134 in_features: gate.in_features,
9135 row_bytes: nvfp4_row_bytes(gate.in_features),
9136 });
9137 up_ranks.push(ResidentNvfp4ColumnBankRank {
9138 bank: engine.htod_bytes(&up_host)?,
9139 expert_bytes: up_expert_bytes,
9140 local_out,
9141 in_features: up.in_features,
9142 row_bytes: nvfp4_row_bytes(up.in_features),
9143 });
9144 macros_gate_dev.push(engine.htod(gate.macros)?);
9145 macros_up_dev.push(engine.htod(up.macros)?);
9146 macros_down_dev.push(engine.htod(down.macros)?);
9147 }
9148 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
9152 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
9153 let device_rank = shard_index % tp;
9154 let engine = &self.ranks[device_rank];
9155 let _main = engine.gpu.enter_main()?;
9156 let mut down_host: Vec<u8> = Vec::new();
9157 let mut owned = 0usize;
9158 for expert in 0..down.expert_count {
9159 let down_matrix = down.expert(expert)?;
9160 if ep2 {
9161 if expert % 2 != device_rank {
9164 continue;
9165 }
9166 owned += 1;
9167 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(down_matrix));
9168 } else {
9169 let (codes, scales, local_in) =
9170 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
9171 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
9172 codes: &codes,
9173 scales: &scales,
9174 macro_scale: down_matrix.macro_scale,
9175 out_features: down_matrix.out_features,
9176 in_features: local_in,
9177 }));
9178 }
9179 }
9180 let bank_experts = if ep2 { owned } else { down.expert_count };
9181 let down_expert_bytes = down_host.len() / bank_experts.max(1);
9182 let local_in = if ep2 {
9183 down.in_features
9184 } else {
9185 down.in_features / NVFP4_CANONICAL_ROW_SHARDS
9186 };
9187 down_ranks.push(ResidentNvfp4RowBankRank {
9188 bank: engine.htod_bytes(&down_host)?,
9189 expert_bytes: down_expert_bytes,
9190 device_rank,
9191 out_features: down.out_features,
9192 local_in,
9193 row_bytes: nvfp4_row_bytes(local_in),
9194 });
9195 }
9196 Ok(ResidentNvfp4TensorParallel {
9197 gate: gate_ranks,
9198 up: up_ranks,
9199 down: down_ranks,
9200 macros_gate: gate.macros.to_vec(),
9201 macros_up: up.macros.to_vec(),
9202 macros_down: down.macros.to_vec(),
9203 macros_gate_dev,
9204 macros_up_dev,
9205 macros_down_dev,
9206 expert_count: gate.expert_count,
9207 input_width: gate.in_features,
9208 expert_width: gate.out_features,
9209 device_workspace: std::sync::Mutex::new(None),
9210 t2_workspace: std::sync::Mutex::new(None),
9211 ep2,
9212 })
9213 }
9214
9215 fn run_full_bank_expert_nvfp4(
9219 &self,
9220 ranks: &[ResidentNvfp4ColumnBankRank],
9221 macros: &[f32],
9222 expert: usize,
9223 input: &[f32],
9224 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9225 let owner = expert & 1;
9226 let slot = expert >> 1;
9227 let bank = ranks
9228 .get(owner)
9229 .ok_or("NVFP4 EP2 column bank missing owner rank")?;
9230 let engine = &self.ranks[owner];
9231 let _main = engine.gpu.enter_main()?;
9232 let activations = engine.htod(input)?;
9233 let output = if nvfp4_bank_v2_on() {
9234 engine.qmatvec_nvfp4_fast_v2(
9235 &bank.expert(slot),
9236 &activations,
9237 1,
9238 bank.in_features,
9239 bank.local_out,
9240 bank.row_bytes,
9241 )?
9242 } else {
9243 engine.qmatvec_nvfp4_fast(
9244 &bank.expert(slot),
9245 &activations,
9246 1,
9247 bank.in_features,
9248 bank.local_out,
9249 bank.row_bytes,
9250 )?
9251 };
9252 let mut out = engine.dtoh(&output)?;
9253 apply_macro(&mut out, macros[expert]);
9254 Ok(out)
9255 }
9256
9257 fn run_full_down_expert_nvfp4(
9260 &self,
9261 shards: &[ResidentNvfp4RowBankRank],
9262 macros: &[f32],
9263 expert: usize,
9264 input: &[f32],
9265 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9266 let owner = expert & 1;
9267 let slot = expert >> 1;
9268 let shard = shards
9269 .get(owner)
9270 .ok_or("NVFP4 EP2 down bank missing owner rank")?;
9271 let engine = &self.ranks[owner];
9272 let _main = engine.gpu.enter_main()?;
9273 let activations = engine.htod(input)?;
9274 let output = if nvfp4_bank_v2_on() {
9275 engine.qmatvec_nvfp4_fast_v2(
9276 &shard.expert(slot),
9277 &activations,
9278 1,
9279 shard.local_in,
9280 shard.out_features,
9281 shard.row_bytes,
9282 )?
9283 } else {
9284 engine.qmatvec_nvfp4_fast(
9285 &shard.expert(slot),
9286 &activations,
9287 1,
9288 shard.local_in,
9289 shard.out_features,
9290 shard.row_bytes,
9291 )?
9292 };
9293 let mut out = engine.dtoh(&output)?;
9294 apply_macro(&mut out, macros[expert]);
9295 Ok(out)
9296 }
9297
9298 fn run_column_bank_expert_nvfp4(
9299 &self,
9300 ranks: &[ResidentNvfp4ColumnBankRank],
9301 macros: &[f32],
9302 expert: usize,
9303 input: &[f32],
9304 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9305 let local_out = ranks
9306 .first()
9307 .ok_or("NVFP4 TP column bank has no ranks")?
9308 .local_out;
9309 let mut gathered = vec![0.0f32; local_out * ranks.len()];
9310 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
9311 let _main = engine.gpu.enter_main()?;
9312 let activations = engine.htod(input)?;
9313 let output = if nvfp4_bank_v2_on() {
9314 engine.qmatvec_nvfp4_fast_v2(
9315 &bank.expert(expert),
9316 &activations,
9317 1,
9318 bank.in_features,
9319 bank.local_out,
9320 bank.row_bytes,
9321 )?
9322 } else {
9323 engine.qmatvec_nvfp4_fast(
9324 &bank.expert(expert),
9325 &activations,
9326 1,
9327 bank.in_features,
9328 bank.local_out,
9329 bank.row_bytes,
9330 )?
9331 };
9332 let output = engine.dtoh(&output)?;
9333 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
9334 }
9335 apply_macro(&mut gathered, macros[expert]);
9336 Ok(gathered)
9337 }
9338
9339 fn run_row_bank_expert_nvfp4(
9343 &self,
9344 shards: &[ResidentNvfp4RowBankRank],
9345 macros: &[f32],
9346 expert: usize,
9347 input: &[f32],
9348 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9349 let out_features = shards
9350 .first()
9351 .ok_or("NVFP4 TP row bank has no canonical shards")?
9352 .out_features;
9353 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
9354 let mut reduced = vec![0.0f32; out_features];
9355 for (shard_index, shard) in shards.iter().enumerate() {
9356 let engine = self
9357 .ranks
9358 .get(shard.device_rank)
9359 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
9360 let _main = engine.gpu.enter_main()?;
9361 let local_activations =
9362 activation_shard(input, 1, in_features, shards.len(), shard_index);
9363 let activations = engine.htod(&local_activations)?;
9364 let output = if nvfp4_bank_v2_on() {
9365 engine.qmatvec_nvfp4_fast_v2(
9366 &shard.expert(expert),
9367 &activations,
9368 1,
9369 shard.local_in,
9370 shard.out_features,
9371 shard.row_bytes,
9372 )?
9373 } else {
9374 engine.qmatvec_nvfp4_fast(
9375 &shard.expert(expert),
9376 &activations,
9377 1,
9378 shard.local_in,
9379 shard.out_features,
9380 shard.row_bytes,
9381 )?
9382 };
9383 let partial = engine.dtoh(&output)?;
9384 for (sum, value) in reduced.iter_mut().zip(&partial) {
9385 *sum += *value;
9386 }
9387 }
9388 apply_macro(&mut reduced, macros[expert]);
9389 Ok(reduced)
9390 }
9391
9392 pub fn upload_expert_parallel_nvfp4(
9396 &self,
9397 gate: Nvfp4ExpertBank<'_>,
9398 up: Nvfp4ExpertBank<'_>,
9399 down: Nvfp4ExpertBank<'_>,
9400 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
9401 gate.validate()?;
9402 up.validate()?;
9403 down.validate()?;
9404 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
9405 return Err("NVFP4 EP gate/up/down expert counts differ".into());
9406 }
9407 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9408 return Err("NVFP4 EP gate/up dimensions differ".into());
9409 }
9410 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9411 return Err(format!(
9412 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9413 down.out_features, down.in_features, gate.out_features, gate.in_features
9414 )
9415 .into());
9416 }
9417 let world = self.ranks.len();
9418 if gate.expert_count % world != 0 {
9419 return Err(format!(
9420 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9421 gate.expert_count
9422 )
9423 .into());
9424 }
9425 let experts_per_rank = gate.expert_count / world;
9426 let mut ranks = Vec::with_capacity(world);
9427 for (rank_index, engine) in self.ranks.iter().enumerate() {
9428 let _main = engine.gpu.enter_main()?;
9429 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9430 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9431 let mut up_experts = Vec::with_capacity(experts_per_rank);
9432 let mut down_experts = Vec::with_capacity(experts_per_rank);
9433 for expert in expert_range.clone() {
9434 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9435 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9436 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9437 }
9438 ranks.push(ResidentNvfp4EpRank {
9439 gate: gate_experts,
9440 up: up_experts,
9441 down: down_experts,
9442 expert_range,
9443 });
9444 }
9445 Ok(ResidentNvfp4ExpertParallel {
9446 ranks,
9447 macros_gate: gate.macros.to_vec(),
9448 macros_up: up.macros.to_vec(),
9449 macros_down: down.macros.to_vec(),
9450 expert_count: gate.expert_count,
9451 input_width: gate.in_features,
9452 expert_width: gate.out_features,
9453 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9454 down_row_bytes: nvfp4_row_bytes(down.in_features),
9455 })
9456 }
9457
9458 #[allow(clippy::too_many_arguments)]
9464 pub fn run_routed_experts_nvfp4(
9465 &self,
9466 experts: &ResidentNvfp4ExpertParallel,
9467 input: &[f32],
9468 tokens: usize,
9469 selected: &[usize],
9470 route_weights: &[f32],
9471 experts_per_token: usize,
9472 activation_limit: Option<f32>,
9473 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9474 validate_activations(input, tokens, experts.input_width)?;
9475 let pairs = tokens
9476 .checked_mul(experts_per_token)
9477 .ok_or("NVFP4 EP route count overflow")?;
9478 if selected.len() != pairs || route_weights.len() != pairs {
9479 return Err(format!(
9480 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9481 {experts_per_token} ({pairs})",
9482 selected.len(),
9483 route_weights.len(),
9484 )
9485 .into());
9486 }
9487 if !route_weights.iter().all(|weight| weight.is_finite()) {
9488 return Err("NVFP4 EP route weights contain a non-finite value".into());
9489 }
9490 let experts_per_rank = experts.expert_count / experts.ranks.len();
9491 let mut output = vec![0.0f32; tokens * experts.input_width];
9492 for token in 0..tokens {
9493 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9494 for slot in 0..experts_per_token {
9495 let pair = token * experts_per_token + slot;
9496 let expert = selected[pair];
9497 if expert >= experts.expert_count {
9498 return Err(format!(
9499 "NVFP4 EP selected expert {expert} outside 0..{}",
9500 experts.expert_count
9501 )
9502 .into());
9503 }
9504 let owner = expert / experts_per_rank;
9505 let local = expert - owner * experts_per_rank;
9506 let rank = &experts.ranks[owner];
9507 let engine = &self.ranks[owner];
9508 let _main = engine.gpu.enter_main()?;
9509 let device_input = engine.htod(input_row)?;
9510 let gate_out = engine.qmatvec_nvfp4_fast(
9511 &rank.gate[local].slice(0..rank.gate[local].len()),
9512 &device_input,
9513 1,
9514 experts.input_width,
9515 experts.expert_width,
9516 experts.gate_row_bytes,
9517 )?;
9518 let up_out = engine.qmatvec_nvfp4_fast(
9519 &rank.up[local].slice(0..rank.up[local].len()),
9520 &device_input,
9521 1,
9522 experts.input_width,
9523 experts.expert_width,
9524 experts.gate_row_bytes,
9525 )?;
9526 let mut gate_host = engine.dtoh(&gate_out)?;
9527 let mut up_host = engine.dtoh(&up_out)?;
9528 apply_macro(&mut gate_host, experts.macros_gate[expert]);
9529 apply_macro(&mut up_host, experts.macros_up[expert]);
9530 let activated: Vec<f32> = gate_host
9531 .iter()
9532 .zip(&up_host)
9533 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
9534 .collect();
9535 let device_activated = engine.htod(&activated)?;
9536 let down_out = engine.qmatvec_nvfp4_fast(
9537 &rank.down[local].slice(0..rank.down[local].len()),
9538 &device_activated,
9539 1,
9540 experts.expert_width,
9541 experts.input_width,
9542 experts.down_row_bytes,
9543 )?;
9544 let mut down_host = engine.dtoh(&down_out)?;
9545 apply_macro(&mut down_host, experts.macros_down[expert]);
9546 let weight = route_weights[pair];
9547 for (sum, value) in output
9548 [token * experts.input_width..(token + 1) * experts.input_width]
9549 .iter_mut()
9550 .zip(down_host)
9551 {
9552 *sum += weight * value;
9553 }
9554 }
9555 }
9556 Ok(output)
9557 }
9558
9559 pub fn run_tensor_parallel_routes_nvfp4_device(
9573 &self,
9574 experts: &ResidentNvfp4TensorParallel,
9575 input: &[f32],
9576 selected: &[usize],
9577 route_weights: &[f32],
9578 experts_per_token: usize,
9579 activation_limit: Option<f32>,
9580 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9581 validate_activations(input, 1, experts.input_width)?;
9582 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9583 return Err(format!(
9584 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
9585 selected.len(),
9586 route_weights.len(),
9587 )
9588 .into());
9589 }
9590 if !route_weights.iter().all(|weight| weight.is_finite()) {
9591 return Err("NVFP4 device route weights contain a non-finite value".into());
9592 }
9593 let world = self.ranks.len();
9594 if world != NVFP4_CANONICAL_ROW_SHARDS {
9595 return Err(format!(
9596 "NVFP4 device routes require world == canonical shard grid \
9597 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9598 )
9599 .into());
9600 }
9601 let local_out = if experts.ep2 {
9602 experts.expert_width
9603 } else {
9604 experts.expert_width / world
9605 };
9606
9607 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9611 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9612 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9613 let started = timing.then(std::time::Instant::now);
9614
9615 let n_sel = experts_per_token;
9616 let mut workspace_guard = experts
9617 .device_workspace
9618 .lock()
9619 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9620 if workspace_guard.is_none() {
9621 let mut gate_out = Vec::with_capacity(world);
9622 let mut up_out = Vec::with_capacity(world);
9623 let mut act_q = Vec::with_capacity(world);
9624 let mut act_d = Vec::with_capacity(world);
9625 let mut sel = Vec::with_capacity(world);
9626 let mut partial = Vec::with_capacity(world);
9627 let mut accumulator = Vec::with_capacity(world);
9628 let mut combine_w = Vec::with_capacity(world);
9629 let mut route_w = Vec::with_capacity(world);
9630 let mut in_q = Vec::with_capacity(world);
9631 let mut in_d = Vec::with_capacity(world);
9632 let mut input = Vec::with_capacity(world);
9633 let mut ev_rank = Vec::with_capacity(world);
9634 let moe_direct = moe_direct_on();
9635 for (rank, engine) in self.ranks.iter().enumerate() {
9636 let _main = engine.gpu.enter_main()?;
9637 gate_out.push(engine.uninit(n_sel * local_out)?);
9638 up_out.push(engine.uninit(n_sel * local_out)?);
9639 act_q.push(engine.uninit_i8(n_sel * local_out)?);
9640 act_d.push(engine.uninit(n_sel * local_out / 32)?);
9641 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
9642 partial.push(engine.uninit(n_sel * experts.input_width)?);
9643 if moe_direct && rank != 0 {
9645 let root = &self.ranks[0];
9646 let _root_main = root.gpu.enter_main()?;
9647 accumulator.push(root.zeros(experts.input_width)?);
9648 } else {
9649 accumulator.push(engine.zeros(experts.input_width)?);
9650 }
9651 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9652 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9653 in_q.push(engine.uninit_i8(experts.input_width)?);
9654 in_d.push(engine.uninit(experts.input_width / 32)?);
9655 input.push(engine.uninit(experts.input_width)?);
9656 ev_rank.push(engine.ctx().new_event(None)?);
9657 }
9658 let root = &self.ranks[0];
9659 let _main = root.gpu.enter_main()?;
9660 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
9661 prestaged: false,
9662 rank1_routed: false,
9663 ev_input: None,
9664 fence_flags_raw: 0,
9665 fence_ticket: 0,
9666 gate_out,
9667 up_out,
9668 act_q,
9669 act_d,
9670 sel,
9671 partial,
9672 accumulator,
9673 combine_w,
9674 route_w,
9675 in_q,
9676 in_d,
9677 dev_route_e: None,
9678 in_stage_e: None,
9679 out_stage_e: None,
9680 routes_graph: None,
9681 raw_dev_route_e: None,
9682 raw_combine: None,
9683 raw_input: Vec::new(),
9684 raw_sel: Vec::new(),
9685 raw_route_w: Vec::new(),
9686 remote: root.uninit(experts.input_width)?,
9687 combined: root.uninit(experts.input_width)?,
9688 n_sel,
9689 input,
9690 ev_rank,
9691 ev_done: Some(root.ctx().new_event(None)?),
9692 ev_entry: None,
9693 });
9694 }
9695 let workspace = workspace_guard
9696 .as_mut()
9697 .expect("NVFP4 device routes workspace initialized above");
9698 if experts.ep2 {
9701 return Ok(vec![0.0f32; experts.input_width]);
9702 }
9703 if workspace.n_sel != n_sel {
9704 return Err(format!(
9705 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
9706 workspace.n_sel
9707 )
9708 .into());
9709 }
9710 for &expert in selected {
9711 if expert >= experts.expert_count {
9712 return Err(format!(
9713 "NVFP4 device selected expert {expert} outside 0..{}",
9714 experts.expert_count
9715 )
9716 .into());
9717 }
9718 }
9719 let sel_i32 = selected
9720 .iter()
9721 .map(|&expert| expert as i32)
9722 .collect::<Vec<_>>();
9723
9724 for (rank_index, engine) in self.ranks.iter().enumerate() {
9731 let _main = engine.gpu.enter_main()?;
9732 let device_input = engine.htod(input)?;
9733 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
9734 engine.quantize_q8_1_into(
9735 &device_input,
9736 1,
9737 experts.input_width,
9738 &mut in_q[rank_index],
9739 &mut in_d[rank_index],
9740 )?;
9741 }
9743 self.nvfp4_routes_batched_sweeps(
9744 experts,
9745 workspace,
9746 selected,
9747 route_weights,
9748 &sel_i32,
9749 local_out,
9750 n_sel,
9751 activation_limit,
9752 false,
9753 )?;
9754
9755 let root = &self.ranks[0];
9758 for engine in &self.ranks[1..] {
9759 let _main = engine.gpu.enter_main()?;
9760 engine.stream().synchronize()?;
9761 }
9762 let _main = root.gpu.enter_main()?;
9763 root.stream()
9764 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
9765 root.add(
9766 &workspace.accumulator[0],
9767 &workspace.remote,
9768 &mut workspace.combined,
9769 experts.input_width,
9770 )?;
9771 let output = root.dtoh(&workspace.combined)?;
9772 if let Some(started) = started {
9773 use std::sync::atomic::Ordering;
9774 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9775 + started.elapsed().as_nanos() as u64;
9776 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9777 if calls % 430 == 0 {
9778 eprintln!(
9779 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9780 ns as f64 / 1.0e6,
9781 ns as f64 / calls as f64 / 1.0e3,
9782 );
9783 }
9784 }
9785 Ok(output)
9786 }
9787
9788 #[allow(clippy::too_many_arguments)]
9793 fn nvfp4_routes_batched_sweeps(
9794 &self,
9795 experts: &ResidentNvfp4TensorParallel,
9796 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9797 selected: &[usize],
9798 route_weights: &[f32],
9799 sel_i32: &[i32],
9800 local_out: usize,
9801 n_sel: usize,
9802 activation_limit: Option<f32>,
9803 device_routed: bool,
9804 ) -> Result<(), Box<dyn std::error::Error>> {
9805 for rank_index in 0..self.ranks.len() {
9806 self.nvfp4_routes_batched_sweeps_rank(
9807 experts,
9808 workspace,
9809 selected,
9810 route_weights,
9811 sel_i32,
9812 local_out,
9813 n_sel,
9814 activation_limit,
9815 device_routed,
9816 rank_index,
9817 )?;
9818 }
9819 Ok(())
9820 }
9821
9822 #[allow(clippy::too_many_arguments)]
9825 fn nvfp4_routes_batched_sweeps_rank(
9826 &self,
9827 experts: &ResidentNvfp4TensorParallel,
9828 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9829 selected: &[usize],
9830 route_weights: &[f32],
9831 sel_i32: &[i32],
9832 local_out: usize,
9833 n_sel: usize,
9834 activation_limit: Option<f32>,
9835 device_routed: bool,
9836 rank_index: usize,
9837 ) -> Result<(), Box<dyn std::error::Error>> {
9838 {
9839 let engine = &self.ranks[rank_index];
9840 let _main = engine.gpu.enter_main()?;
9841 if experts.ep2 {
9846 if !device_routed {
9847 return Err("NVFP4 EP2 banks support the device-routed decode arm only".into());
9848 }
9849 let gate_bank = &experts.gate[rank_index];
9850 let up_bank = &experts.up[rank_index];
9851 if gate_bank.local_out != experts.expert_width
9852 || gate_bank.expert_bytes != up_bank.expert_bytes
9853 {
9854 return Err("NVFP4 EP2 bank geometry drifted".into());
9855 }
9856 {
9857 let Nvfp4DeviceRoutesWorkspace {
9858 sel,
9859 gate_out,
9860 up_out,
9861 in_q,
9862 in_d,
9863 ..
9864 } = &mut *workspace;
9865 engine.qmatvec_nvfp4_sel_gu_ep_into(
9866 &gate_bank.bank,
9867 &up_bank.bank,
9868 &sel[rank_index],
9869 &in_q[rank_index],
9870 &in_d[rank_index],
9871 &mut gate_out[rank_index],
9872 &mut up_out[rank_index],
9873 n_sel,
9874 gate_bank.in_features,
9875 gate_bank.local_out,
9876 gate_bank.row_bytes,
9877 gate_bank.expert_bytes,
9878 rank_index,
9879 )?;
9880 }
9881 {
9882 let Nvfp4DeviceRoutesWorkspace {
9883 gate_out,
9884 up_out,
9885 sel,
9886 act_q,
9887 act_d,
9888 ..
9889 } = &mut *workspace;
9890 engine.silu_mul_scaled_q8_1_sel_ep_into(
9891 &gate_out[rank_index],
9892 &up_out[rank_index],
9893 &experts.macros_gate_dev[rank_index],
9894 &experts.macros_up_dev[rank_index],
9895 &sel[rank_index],
9896 activation_limit,
9897 &mut act_q[rank_index],
9898 &mut act_d[rank_index],
9899 local_out,
9900 n_sel,
9901 rank_index,
9902 )?;
9903 }
9904 let shard = &experts.down[rank_index];
9905 if shard.device_rank != rank_index || shard.local_in != local_out {
9906 return Err("NVFP4 EP2 down bank placement drifted".into());
9907 }
9908 {
9909 let Nvfp4DeviceRoutesWorkspace {
9910 sel,
9911 act_q,
9912 act_d,
9913 route_w,
9914 accumulator,
9915 ..
9916 } = &mut *workspace;
9917 engine.qmatvec_nvfp4_sel_down8_ep_into(
9918 &shard.bank,
9919 &sel[rank_index],
9920 &act_q[rank_index],
9921 &act_d[rank_index],
9922 &route_w[rank_index],
9923 &experts.macros_down_dev[rank_index],
9924 &mut accumulator[rank_index],
9925 n_sel,
9926 shard.local_in,
9927 shard.out_features,
9928 shard.row_bytes,
9929 shard.expert_bytes,
9930 local_out,
9931 local_out / 32,
9932 rank_index,
9933 )?;
9934 }
9935 return Ok(());
9936 }
9937 if !device_routed {
9938 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
9939 let folded = (0..n_sel)
9942 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
9943 .collect::<Vec<_>>();
9944 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
9945 engine.stream().memcpy_htod(&folded, &mut view)?;
9946 }
9947 let gate_bank = &experts.gate[rank_index];
9948 let up_bank = &experts.up[rank_index];
9949 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
9950 let gu_fused = nvfp4_bank_v2_on()
9953 && gate_bank.in_features == up_bank.in_features
9954 && gate_bank.local_out == up_bank.local_out
9955 && gate_bank.row_bytes == up_bank.row_bytes
9956 && gate_bank.expert_bytes == up_bank.expert_bytes;
9957 if gu_fused {
9958 let Nvfp4DeviceRoutesWorkspace {
9959 sel,
9960 gate_out,
9961 up_out,
9962 in_q,
9963 in_d,
9964 ..
9965 } = &mut *workspace;
9966 engine.qmatvec_nvfp4_sel_gu_into(
9967 &gate_bank.bank,
9968 &up_bank.bank,
9969 &sel[rank_index],
9970 &in_q[rank_index],
9971 &in_d[rank_index],
9972 &mut gate_out[rank_index],
9973 &mut up_out[rank_index],
9974 n_sel,
9975 gate_bank.in_features,
9976 gate_bank.local_out,
9977 gate_bank.row_bytes,
9978 gate_bank.expert_bytes,
9979 )?;
9980 } else {
9981 engine.qmatvec_nvfp4_sel_into(
9982 &gate_bank.bank,
9983 &workspace.sel[rank_index],
9984 aq,
9985 ad,
9986 &mut workspace.gate_out[rank_index],
9987 n_sel,
9988 gate_bank.in_features,
9989 gate_bank.local_out,
9990 gate_bank.row_bytes,
9991 gate_bank.expert_bytes,
9992 0,
9993 0,
9994 )?;
9995 engine.qmatvec_nvfp4_sel_into(
9996 &up_bank.bank,
9997 &workspace.sel[rank_index],
9998 aq,
9999 ad,
10000 &mut workspace.up_out[rank_index],
10001 n_sel,
10002 up_bank.in_features,
10003 up_bank.local_out,
10004 up_bank.row_bytes,
10005 up_bank.expert_bytes,
10006 0,
10007 0,
10008 )?;
10009 }
10010 {
10014 let Nvfp4DeviceRoutesWorkspace {
10015 gate_out,
10016 up_out,
10017 sel,
10018 act_q,
10019 act_d,
10020 ..
10021 } = &mut *workspace;
10022 engine.silu_mul_scaled_q8_1_sel_into(
10023 &gate_out[rank_index],
10024 &up_out[rank_index],
10025 &experts.macros_gate_dev[rank_index],
10026 &experts.macros_up_dev[rank_index],
10027 &sel[rank_index],
10028 activation_limit,
10029 &mut act_q[rank_index],
10030 &mut act_d[rank_index],
10031 local_out,
10032 n_sel,
10033 )?;
10034 }
10035 let shard = &experts.down[rank_index];
10036 if shard.device_rank != rank_index || shard.local_in != local_out {
10037 return Err(
10038 "NVFP4 device routes: down canonical shard placement drifted from \
10039 the gate/up column split"
10040 .into(),
10041 );
10042 }
10043 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
10050 if down8 {
10051 let Nvfp4DeviceRoutesWorkspace {
10052 sel,
10053 act_q,
10054 act_d,
10055 route_w,
10056 accumulator,
10057 ..
10058 } = &mut *workspace;
10059 engine.qmatvec_nvfp4_sel_down8_into(
10060 &shard.bank,
10061 &sel[rank_index],
10062 &act_q[rank_index],
10063 &act_d[rank_index],
10064 &route_w[rank_index],
10065 &experts.macros_down_dev[rank_index],
10066 &mut accumulator[rank_index],
10067 n_sel,
10068 shard.local_in,
10069 shard.out_features,
10070 shard.row_bytes,
10071 shard.expert_bytes,
10072 local_out,
10073 local_out / 32,
10074 )?;
10075 } else {
10076 let Nvfp4DeviceRoutesWorkspace {
10077 sel,
10078 act_q,
10079 act_d,
10080 partial,
10081 ..
10082 } = &mut *workspace;
10083 engine.qmatvec_nvfp4_sel_into(
10084 &shard.bank,
10085 &sel[rank_index],
10086 &act_q[rank_index],
10087 &act_d[rank_index],
10088 &mut partial[rank_index],
10089 n_sel,
10090 shard.local_in,
10091 shard.out_features,
10092 shard.row_bytes,
10093 shard.expert_bytes,
10094 local_out,
10095 local_out / 32,
10096 )?;
10097 }
10098 if !down8 {
10103 let Nvfp4DeviceRoutesWorkspace {
10104 partial,
10105 combine_w,
10106 route_w,
10107 sel,
10108 accumulator,
10109 ..
10110 } = &mut *workspace;
10111 if device_routed {
10112 engine.axpy_rows_seq_md_into(
10113 &partial[rank_index],
10114 &route_w[rank_index],
10115 &experts.macros_down_dev[rank_index],
10116 &sel[rank_index],
10117 &mut accumulator[rank_index],
10118 experts.input_width,
10119 n_sel,
10120 )?;
10121 } else {
10122 engine.axpy_rows_seq_into(
10123 &partial[rank_index],
10124 &combine_w[rank_index],
10125 &mut accumulator[rank_index],
10126 experts.input_width,
10127 n_sel,
10128 )?;
10129 }
10130 }
10131 }
10132 Ok(())
10133 }
10134
10135 pub fn run_tensor_parallel_routes_nvfp4_device_io(
10143 &self,
10144 experts: &ResidentNvfp4TensorParallel,
10145 e: &Engine,
10146 input_dev: &crate::CudaSlice<f32>,
10147 selected: &[usize],
10148 route_weights: &[f32],
10149 experts_per_token: usize,
10150 activation_limit: Option<f32>,
10151 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10152 if input_dev.len() != experts.input_width {
10153 return Err(format!(
10154 "NVFP4 device-io routes input {} != width {}",
10155 input_dev.len(),
10156 experts.input_width
10157 )
10158 .into());
10159 }
10160 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
10161 return Err(format!(
10162 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
10163 selected.len(),
10164 route_weights.len(),
10165 )
10166 .into());
10167 }
10168 if !route_weights.iter().all(|weight| weight.is_finite()) {
10169 return Err("NVFP4 device route weights contain a non-finite value".into());
10170 }
10171 let world = self.ranks.len();
10172 if world != NVFP4_CANONICAL_ROW_SHARDS {
10173 return Err(format!(
10174 "NVFP4 device routes require world == canonical shard grid \
10175 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10176 )
10177 .into());
10178 }
10179 let local_out = experts.expert_width / world;
10180 let n_sel = experts_per_token;
10181
10182 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10183 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10184 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10185 let started = timing.then(std::time::Instant::now);
10186
10187 let mut workspace_guard = experts
10188 .device_workspace
10189 .lock()
10190 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10191 if workspace_guard.is_none() {
10192 drop(workspace_guard);
10193 let zero = vec![0.0f32; experts.input_width];
10196 let zero_sel = vec![0usize; n_sel];
10197 let zero_w = vec![0.0f32; n_sel];
10198 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10199 experts,
10200 &zero,
10201 &zero_sel,
10202 &zero_w,
10203 n_sel,
10204 activation_limit,
10205 )?;
10206 workspace_guard = experts
10207 .device_workspace
10208 .lock()
10209 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10210 }
10211 let workspace = workspace_guard
10212 .as_mut()
10213 .expect("NVFP4 device routes workspace initialized above");
10214 if workspace.n_sel != n_sel {
10215 return Err(format!(
10216 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10217 workspace.n_sel
10218 )
10219 .into());
10220 }
10221 for &expert in selected {
10222 if expert >= experts.expert_count {
10223 return Err(format!(
10224 "NVFP4 device selected expert {expert} outside 0..{}",
10225 experts.expert_count
10226 )
10227 .into());
10228 }
10229 }
10230 let sel_i32 = selected
10231 .iter()
10232 .map(|&expert| expert as i32)
10233 .collect::<Vec<_>>();
10234
10235 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10239 if *device != e.ctx().ordinal() {
10240 return Err("NVFP4 device-io routes engine changed".into());
10241 }
10242 } else {
10243 let _main = e.gpu.enter_main()?;
10244 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10245 }
10246 {
10247 let _main = e.gpu.enter_main()?;
10248 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10249 ev_entry.record(&e.stream())?;
10250 }
10251 for (rank_index, engine) in self.ranks.iter().enumerate() {
10252 let _main = engine.gpu.enter_main()?;
10253 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10254 engine.stream().wait(ev_entry)?;
10255 {
10256 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10257 engine
10258 .stream()
10259 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10260 }
10261 {
10262 let Nvfp4DeviceRoutesWorkspace {
10263 input, in_q, in_d, ..
10264 } = &mut *workspace;
10265 engine.quantize_q8_1_into(
10266 &input[rank_index],
10267 1,
10268 experts.input_width,
10269 &mut in_q[rank_index],
10270 &mut in_d[rank_index],
10271 )?;
10272 }
10273 }
10274 self.nvfp4_routes_batched_sweeps(
10275 experts,
10276 workspace,
10277 selected,
10278 route_weights,
10279 &sel_i32,
10280 local_out,
10281 n_sel,
10282 activation_limit,
10283 false,
10284 )?;
10285
10286 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10292 let _main = engine.gpu.enter_main()?;
10293 workspace.ev_rank[rank_index].record(&engine.stream())?;
10294 }
10295 if moe_direct_on() && self.ranks.len() == 2 {
10296 {
10303 let root = &self.ranks[0];
10304 let _main = root.gpu.enter_main()?;
10305 workspace
10306 .ev_done
10307 .as_ref()
10308 .expect("device routes done event")
10309 .record(&root.stream())?;
10310 }
10311 let _main = e.gpu.enter_main()?;
10312 e.stream().wait(
10313 workspace
10314 .ev_done
10315 .as_ref()
10316 .expect("device routes done event"),
10317 )?;
10318 for ev in workspace.ev_rank.iter().skip(1) {
10319 e.stream().wait(ev)?;
10320 }
10321 let mut output = e.uninit(experts.input_width)?;
10322 e.add(
10323 &workspace.accumulator[0],
10324 &workspace.accumulator[1],
10325 &mut output,
10326 experts.input_width,
10327 )?;
10328 let output = output;
10329 if let Some(started) = started {
10330 use std::sync::atomic::Ordering;
10331 let ns = TIMING_NS
10332 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10333 + started.elapsed().as_nanos() as u64;
10334 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10335 if calls % 430 == 0 {
10336 eprintln!(
10337 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10338 ns as f64 / 1.0e6,
10339 ns as f64 / calls as f64 / 1.0e3,
10340 );
10341 }
10342 }
10343 return Ok(output);
10344 }
10345 {
10346 let root = &self.ranks[0];
10347 let _main = root.gpu.enter_main()?;
10348 for ev in workspace.ev_rank.iter().skip(1) {
10349 root.stream().wait(ev)?;
10350 }
10351 root.stream()
10352 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10353 {
10354 let Nvfp4DeviceRoutesWorkspace {
10355 accumulator,
10356 remote,
10357 combined,
10358 ..
10359 } = &mut *workspace;
10360 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10361 }
10362 workspace
10363 .ev_done
10364 .as_ref()
10365 .expect("device routes done event")
10366 .record(&root.stream())?;
10367 }
10368 let output = {
10369 let _main = e.gpu.enter_main()?;
10370 e.stream().wait(
10371 workspace
10372 .ev_done
10373 .as_ref()
10374 .expect("device routes done event"),
10375 )?;
10376 let mut output = e.uninit(experts.input_width)?;
10379 e.stream().memcpy_dtod(
10380 &workspace.combined.slice(0..experts.input_width),
10381 &mut output.slice_mut(0..experts.input_width),
10382 )?;
10383 output
10384 };
10385 if let Some(started) = started {
10386 use std::sync::atomic::Ordering;
10387 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10388 + started.elapsed().as_nanos() as u64;
10389 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10390 if calls % 430 == 0 {
10391 eprintln!(
10392 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10393 ns as f64 / 1.0e6,
10394 ns as f64 / calls as f64 / 1.0e3,
10395 );
10396 }
10397 }
10398 Ok(output)
10399 }
10400
10401 #[allow(clippy::too_many_arguments)]
10407 pub fn nvfp4_routes_prestage(
10412 &self,
10413 experts: &ResidentNvfp4TensorParallel,
10414 e: &Engine,
10415 input_dev: &crate::CudaSlice<f32>,
10416 ) -> Result<bool, Box<dyn std::error::Error>> {
10417 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
10418 }
10419
10420 pub fn nvfp4_routes_prestage_with(
10426 &self,
10427 experts: &ResidentNvfp4TensorParallel,
10428 e: &Engine,
10429 input_dev: &crate::CudaSlice<f32>,
10430 rank1_router: impl FnOnce(
10431 &Engine,
10432 &crate::CudaSlice<f32>,
10433 &mut crate::CudaSlice<i32>,
10434 &mut crate::CudaSlice<f32>,
10435 ) -> Result<bool, Box<dyn std::error::Error>>,
10436 ) -> Result<bool, Box<dyn std::error::Error>> {
10437 if !routes_prestage_on() || step_tp_graph_enabled()? {
10438 return Ok(false);
10439 }
10440 if input_dev.len() != experts.input_width {
10441 return Err("NVFP4 prestage input width mismatch".into());
10442 }
10443 let mut workspace_guard = experts
10444 .device_workspace
10445 .lock()
10446 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10447 let Some(workspace) = workspace_guard.as_mut() else {
10448 return Ok(false);
10449 };
10450 if workspace.ev_input.is_none() {
10451 let _main = e.gpu.enter_main()?;
10452 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10453 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
10454 return Err("NVFP4 prestage engine changed".into());
10455 }
10456 {
10457 let _main = e.gpu.enter_main()?;
10458 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10459 ev.record(&e.stream())?;
10460 }
10461 for (rank_index, engine) in self.ranks.iter().enumerate() {
10462 let _main = engine.gpu.enter_main()?;
10463 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
10464 engine.stream().wait(ev)?;
10465 {
10466 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10467 engine
10468 .stream()
10469 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10470 }
10471 {
10472 let Nvfp4DeviceRoutesWorkspace {
10473 input, in_q, in_d, ..
10474 } = &mut *workspace;
10475 engine.quantize_q8_1_into(
10476 &input[rank_index],
10477 1,
10478 experts.input_width,
10479 &mut in_q[rank_index],
10480 &mut in_d[rank_index],
10481 )?;
10482 }
10483 }
10484 if self.ranks.len() == 2 {
10485 let rank1 = &self.ranks[1];
10486 let _r1 = rank1.gpu.enter_main()?;
10487 let Nvfp4DeviceRoutesWorkspace {
10488 input,
10489 sel,
10490 route_w,
10491 ..
10492 } = &mut *workspace;
10493 let (in1, rest_sel) = (&input[1], &mut sel[1]);
10494 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
10495 workspace.rank1_routed = true;
10496 }
10497 }
10498 workspace.prestaged = true;
10499 Ok(true)
10500 }
10501
10502 #[allow(clippy::too_many_arguments)]
10513 pub fn run_tensor_parallel_routes_nvfp4_device_routed_tn(
10514 &self,
10515 experts: &ResidentNvfp4TensorParallel,
10516 e: &Engine,
10517 z_t: &crate::CudaSlice<f32>,
10518 sel_d: &crate::CudaSlice<i32>,
10519 w_d: &crate::CudaSlice<f32>,
10520 t: usize,
10521 n_sel_col: usize,
10522 activation_limit: Option<f32>,
10523 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10524 let world = self.ranks.len();
10525 if world != NVFP4_CANONICAL_ROW_SHARDS {
10526 return Err("NVFP4 t-row routes require the canonical 2-shard grid".into());
10527 }
10528 let width = experts.input_width;
10529 let n_sel = t * n_sel_col;
10530 if t == 0 || t > 8 || z_t.len() < t * width || sel_d.len() < n_sel || w_d.len() < n_sel {
10531 return Err("NVFP4 t-row routes geometry".into());
10532 }
10533 if !nvfp4_bank_v2_on() {
10534 return Err("NVFP4 t-row routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
10535 }
10536 let local_out = experts.expert_width / world;
10537 let mut guard = experts
10538 .t2_workspace
10539 .lock()
10540 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
10541 if guard.as_ref().is_none_or(|ws| ws.n_sel != n_sel) {
10542 let mut input2 = Vec::new();
10543 let mut in_q2 = Vec::new();
10544 let mut in_d2 = Vec::new();
10545 let mut sel2 = Vec::new();
10546 let mut route_w2 = Vec::new();
10547 let mut gate_out2 = Vec::new();
10548 let mut up_out2 = Vec::new();
10549 let mut act_q2 = Vec::new();
10550 let mut act_d2 = Vec::new();
10551 let mut partial2 = Vec::new();
10552 let mut acc_a = Vec::new();
10553 let mut acc_b = Vec::new();
10554 let mut acc2 = Vec::new();
10555 let mut ev_rank = Vec::new();
10556 for engine in &self.ranks {
10557 let _m = engine.gpu.enter_main()?;
10558 input2.push(engine.uninit(t * width)?);
10559 in_q2.push(engine.alloc_i8_uninit(t * width)?);
10560 in_d2.push(engine.uninit(t * (width / 32))?);
10561 sel2.push(engine.htod_i32(&vec![0i32; n_sel])?);
10562 route_w2.push(engine.uninit(n_sel)?);
10563 gate_out2.push(engine.uninit(n_sel * local_out)?);
10564 up_out2.push(engine.uninit(n_sel * local_out)?);
10565 act_q2.push(engine.alloc_i8_uninit(n_sel * local_out)?);
10566 act_d2.push(engine.uninit(n_sel * (local_out / 32))?);
10567 partial2.push(engine.uninit(n_sel * width)?);
10568 acc_a.push(engine.uninit(width)?);
10569 acc_b.push(engine.uninit(width)?);
10570 acc2.push(engine.uninit(t * width)?);
10571 ev_rank.push(engine.ctx().new_event(None)?);
10572 }
10573 let root = &self.ranks[0];
10574 let (peer_a, peer_b, omix_a, omix_b, peer2, omix2, ev_root) = {
10575 let _m = root.gpu.enter_main()?;
10576 (
10577 root.uninit(width)?,
10578 root.uninit(width)?,
10579 root.uninit(width)?,
10580 root.uninit(width)?,
10581 root.uninit(t * width)?,
10582 root.uninit(t * width)?,
10583 root.ctx().new_event(None)?,
10584 )
10585 };
10586 let ev_entry = {
10587 let _m = e.gpu.enter_main()?;
10588 e.ctx().new_event(None)?
10589 };
10590 *guard = Some(Nvfp4T2Workspace {
10591 input2,
10592 in_q2,
10593 in_d2,
10594 sel2,
10595 route_w2,
10596 gate_out2,
10597 up_out2,
10598 act_q2,
10599 act_d2,
10600 partial2,
10601 acc_a,
10602 acc_b,
10603 acc2,
10604 peer2,
10605 omix2,
10606 peer_a,
10607 peer_b,
10608 omix_a,
10609 omix_b,
10610 ev_entry,
10611 ev_rank,
10612 ev_root,
10613 n_sel,
10614 e_device: e.ctx().ordinal(),
10615 });
10616 }
10617 let ws = guard.as_mut().expect("armed above");
10618 if ws.e_device != e.ctx().ordinal() {
10619 return Err("NVFP4 t2 routes engine changed".into());
10620 }
10621 {
10622 let _main = e.gpu.enter_main()?;
10623 ws.ev_entry.record(&e.stream())?;
10624 }
10625 let down8 = sel_down8_on() && (local_out >> 5) <= 32 && n_sel_col <= 8;
10629 if !down8 && t != 2 {
10630 return Err(
10631 "NVFP4 t-row routes at t != 2 require MEMRA_SEL_DOWN8=1 (fused rows kernel)".into(),
10632 );
10633 }
10634 for rank in 0..world {
10635 let engine = &self.ranks[rank];
10636 let _main = engine.gpu.enter_main()?;
10637 engine.stream().wait(&ws.ev_entry)?;
10638 {
10639 let mut dst = ws.input2[rank].slice_mut(0..t * width);
10640 engine
10641 .stream()
10642 .memcpy_dtod(&z_t.slice(0..t * width), &mut dst)?;
10643 }
10644 {
10645 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
10646 engine
10647 .stream()
10648 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10649 }
10650 {
10651 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
10652 engine
10653 .stream()
10654 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10655 }
10656 {
10657 let Nvfp4T2Workspace {
10658 input2,
10659 in_q2,
10660 in_d2,
10661 ..
10662 } = &mut *ws;
10663 engine.quantize_q8_1_into(
10664 &input2[rank],
10665 t,
10666 width,
10667 &mut in_q2[rank],
10668 &mut in_d2[rank],
10669 )?;
10670 }
10671 let gate_bank = &experts.gate[rank];
10672 let up_bank = &experts.up[rank];
10673 if gate_bank.in_features != up_bank.in_features
10674 || gate_bank.local_out != up_bank.local_out
10675 || gate_bank.row_bytes != up_bank.row_bytes
10676 || gate_bank.expert_bytes != up_bank.expert_bytes
10677 {
10678 return Err("NVFP4 t-row routes need matched gate/up bank geometry".into());
10679 }
10680 {
10681 let Nvfp4T2Workspace {
10682 sel2,
10683 in_q2,
10684 in_d2,
10685 gate_out2,
10686 up_out2,
10687 ..
10688 } = &mut *ws;
10689 engine.qmatvec_nvfp4_sel_gu_tcol_into(
10690 &gate_bank.bank,
10691 &up_bank.bank,
10692 &sel2[rank],
10693 &in_q2[rank],
10694 &in_d2[rank],
10695 &mut gate_out2[rank],
10696 &mut up_out2[rank],
10697 n_sel,
10698 n_sel_col,
10699 gate_bank.in_features,
10700 gate_bank.local_out,
10701 gate_bank.row_bytes,
10702 gate_bank.expert_bytes,
10703 width,
10704 width / 32,
10705 )?;
10706 }
10707 {
10708 let Nvfp4T2Workspace {
10709 gate_out2,
10710 up_out2,
10711 sel2,
10712 act_q2,
10713 act_d2,
10714 ..
10715 } = &mut *ws;
10716 engine.silu_mul_scaled_q8_1_sel_into(
10717 &gate_out2[rank],
10718 &up_out2[rank],
10719 &experts.macros_gate_dev[rank],
10720 &experts.macros_up_dev[rank],
10721 &sel2[rank],
10722 activation_limit,
10723 &mut act_q2[rank],
10724 &mut act_d2[rank],
10725 local_out,
10726 n_sel,
10727 )?;
10728 }
10729 let shard = &experts.down[rank];
10730 if shard.device_rank != rank || shard.local_in != local_out {
10731 return Err("NVFP4 t-row routes: down shard placement drifted".into());
10732 }
10733 if down8 {
10737 let Nvfp4T2Workspace {
10738 sel2,
10739 act_q2,
10740 act_d2,
10741 route_w2,
10742 acc2,
10743 ..
10744 } = &mut *ws;
10745 engine.qmatvec_nvfp4_sel_down8_rows_into(
10746 &shard.bank,
10747 &sel2[rank],
10748 &act_q2[rank],
10749 &act_d2[rank],
10750 &route_w2[rank],
10751 &experts.macros_down_dev[rank],
10752 &mut acc2[rank],
10753 t,
10754 n_sel_col,
10755 shard.local_in,
10756 shard.out_features,
10757 shard.row_bytes,
10758 shard.expert_bytes,
10759 local_out,
10760 local_out / 32,
10761 )?;
10762 } else {
10763 {
10764 let Nvfp4T2Workspace {
10765 sel2,
10766 act_q2,
10767 act_d2,
10768 partial2,
10769 ..
10770 } = &mut *ws;
10771 engine.qmatvec_nvfp4_sel_into(
10772 &shard.bank,
10773 &sel2[rank],
10774 &act_q2[rank],
10775 &act_d2[rank],
10776 &mut partial2[rank],
10777 n_sel,
10778 shard.local_in,
10779 shard.out_features,
10780 shard.row_bytes,
10781 shard.expert_bytes,
10782 local_out,
10783 local_out / 32,
10784 )?;
10785 }
10786 let Nvfp4T2Workspace {
10787 partial2,
10788 route_w2,
10789 sel2,
10790 acc_a,
10791 acc_b,
10792 ..
10793 } = &mut *ws;
10794 engine.axpy_rows_seq_md_off_into(
10795 &partial2[rank],
10796 &route_w2[rank],
10797 &experts.macros_down_dev[rank],
10798 &sel2[rank],
10799 &mut acc_a[rank],
10800 width,
10801 n_sel_col,
10802 0,
10803 )?;
10804 engine.axpy_rows_seq_md_off_into(
10805 &partial2[rank],
10806 &route_w2[rank],
10807 &experts.macros_down_dev[rank],
10808 &sel2[rank],
10809 &mut acc_b[rank],
10810 width,
10811 n_sel_col,
10812 n_sel_col,
10813 )?;
10814 }
10815 if rank != 0 {
10816 ws.ev_rank[rank].record(&engine.stream())?;
10817 }
10818 }
10819 let root = &self.ranks[0];
10820 {
10821 let _main = root.gpu.enter_main()?;
10822 for ev in ws.ev_rank.iter().skip(1) {
10823 root.stream().wait(ev)?;
10824 }
10825 if down8 {
10826 let Nvfp4T2Workspace {
10829 acc2, peer2, omix2, ..
10830 } = &mut *ws;
10831 {
10832 let mut dst = peer2.slice_mut(0..t * width);
10833 root.stream()
10834 .memcpy_dtod(&acc2[1].slice(0..t * width), &mut dst)?;
10835 }
10836 root.add(&acc2[0], peer2, omix2, t * width)?;
10837 } else {
10838 let Nvfp4T2Workspace {
10839 acc_a,
10840 acc_b,
10841 peer_a,
10842 peer_b,
10843 omix_a,
10844 omix_b,
10845 ..
10846 } = &mut *ws;
10847 {
10848 let mut dst = peer_a.slice_mut(0..width);
10849 root.stream()
10850 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
10851 }
10852 {
10853 let mut dst = peer_b.slice_mut(0..width);
10854 root.stream()
10855 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
10856 }
10857 root.add(&acc_a[0], peer_a, omix_a, width)?;
10858 root.add(&acc_b[0], peer_b, omix_b, width)?;
10859 }
10860 ws.ev_root.record(&root.stream())?;
10861 }
10862 let _main = e.gpu.enter_main()?;
10863 e.stream().wait(&ws.ev_root)?;
10864 let mut out = e.uninit(t * width)?;
10865 if down8 {
10866 e.stream().memcpy_dtod(
10867 &ws.omix2.slice(0..t * width),
10868 &mut out.slice_mut(0..t * width),
10869 )?;
10870 } else {
10871 e.stream()
10872 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
10873 e.stream().memcpy_dtod(
10874 &ws.omix_b.slice(0..width),
10875 &mut out.slice_mut(width..2 * width),
10876 )?;
10877 }
10878 Ok(out)
10879 }
10880
10881 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
10882 &self,
10883 experts: &ResidentNvfp4TensorParallel,
10884 e: &Engine,
10885 input_dev: &crate::CudaSlice<f32>,
10886 sel_d: &crate::CudaSlice<i32>,
10887 w_d: &crate::CudaSlice<f32>,
10888 experts_per_token: usize,
10889 activation_limit: Option<f32>,
10890 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10891 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10892 experts,
10893 e,
10894 input_dev,
10895 sel_d,
10896 w_d,
10897 experts_per_token,
10898 activation_limit,
10899 || Ok(()),
10900 )
10901 }
10902
10903 #[allow(clippy::too_many_arguments)]
10909 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10910 &self,
10911 experts: &ResidentNvfp4TensorParallel,
10912 e: &Engine,
10913 input_dev: &crate::CudaSlice<f32>,
10914 sel_d: &crate::CudaSlice<i32>,
10915 w_d: &crate::CudaSlice<f32>,
10916 experts_per_token: usize,
10917 activation_limit: Option<f32>,
10918 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10919 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10920 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10921 experts,
10922 e,
10923 input_dev,
10924 sel_d,
10925 w_d,
10926 experts_per_token,
10927 activation_limit,
10928 pre_join,
10929 None,
10930 )
10931 }
10932
10933 #[allow(clippy::too_many_arguments)]
10938 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10939 &self,
10940 experts: &ResidentNvfp4TensorParallel,
10941 e: &Engine,
10942 input_dev: &crate::CudaSlice<f32>,
10943 sel_d: &crate::CudaSlice<i32>,
10944 w_d: &crate::CudaSlice<f32>,
10945 experts_per_token: usize,
10946 activation_limit: Option<f32>,
10947 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10948 post_add: Option<(u64, u64)>,
10949 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10950 if input_dev.len() != experts.input_width {
10951 return Err(format!(
10952 "NVFP4 device-routed input {} != width {}",
10953 input_dev.len(),
10954 experts.input_width
10955 )
10956 .into());
10957 }
10958 let n_sel = experts_per_token;
10959 if sel_d.len() < n_sel || w_d.len() < n_sel {
10960 return Err(format!(
10961 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
10962 sel_d.len(),
10963 w_d.len()
10964 )
10965 .into());
10966 }
10967 let world = self.ranks.len();
10968 if world != NVFP4_CANONICAL_ROW_SHARDS {
10969 return Err(format!(
10970 "NVFP4 device routes require world == canonical shard grid \
10971 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10972 )
10973 .into());
10974 }
10975 let local_out = if experts.ep2 {
10976 experts.expert_width
10977 } else {
10978 experts.expert_width / world
10979 };
10980
10981 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10982 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10983 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10984 let started = timing.then(std::time::Instant::now);
10985
10986 let mut workspace_guard = experts
10987 .device_workspace
10988 .lock()
10989 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10990 if workspace_guard.is_none() {
10991 drop(workspace_guard);
10992 let zero = vec![0.0f32; experts.input_width];
10993 let zero_sel = vec![0usize; n_sel];
10994 let zero_w = vec![0.0f32; n_sel];
10995 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10996 experts,
10997 &zero,
10998 &zero_sel,
10999 &zero_w,
11000 n_sel,
11001 activation_limit,
11002 )?;
11003 workspace_guard = experts
11004 .device_workspace
11005 .lock()
11006 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
11007 }
11008 let workspace = workspace_guard
11009 .as_mut()
11010 .expect("NVFP4 device routes workspace initialized above");
11011 if workspace.n_sel != n_sel {
11012 return Err(format!(
11013 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
11014 workspace.n_sel
11015 )
11016 .into());
11017 }
11018
11019 if step_tp_graph_enabled()? {
11024 if experts.ep2 {
11025 return Err(
11026 "MEMRA_STEP_TP_GRAPH=1 with MEMRA_STEP_NVFP4_EP2=1 has never been \
11027 co-gated; unset one"
11028 .into(),
11029 );
11030 }
11031 if workspace.dev_route_e.is_none() {
11032 let _main = e.gpu.enter_main()?;
11033 workspace.dev_route_e = Some((
11034 e.htod_i32(&vec![0i32; n_sel])?,
11035 e.htod(&vec![0.0f32; n_sel])?,
11036 ));
11037 }
11038 if workspace.in_stage_e.is_none() {
11039 let _main = e.gpu.enter_main()?;
11040 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11041 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
11042 }
11043 if workspace.routes_graph.is_none() {
11044 let graph = self.nvfp4_routes_build_graph(
11045 experts,
11046 workspace,
11047 local_out,
11048 n_sel,
11049 activation_limit,
11050 )?;
11051 workspace.routes_graph = Some(graph);
11052 eprintln!(
11053 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
11054 children=3 updates=none performance_claim=false"
11055 );
11056 }
11057 let output = {
11058 let _main = e.gpu.enter_main()?;
11059 {
11060 let (sel_e, w_e) = workspace
11061 .dev_route_e
11062 .as_mut()
11063 .expect("device route staging set above");
11064 {
11065 let mut dst = sel_e.slice_mut(0..n_sel);
11066 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11067 }
11068 {
11069 let mut dst = w_e.slice_mut(0..n_sel);
11070 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11071 }
11072 }
11073 {
11074 let in_stage = workspace
11075 .in_stage_e
11076 .as_mut()
11077 .expect("graph staging set above");
11078 let mut dst = in_stage.slice_mut(0..experts.input_width);
11079 e.stream()
11080 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
11081 }
11082 unsafe {
11083 let r = cudarc::driver::sys::cuGraphLaunch(
11084 workspace
11085 .routes_graph
11086 .as_ref()
11087 .expect("routes graph built above")
11088 .exec,
11089 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
11090 );
11091 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
11092 return Err(format!("routes graph launch: {r:?}").into());
11093 }
11094 }
11095 let mut output = e.uninit(experts.input_width)?;
11096 {
11097 let out_stage = workspace
11098 .out_stage_e
11099 .as_ref()
11100 .expect("graph staging set above");
11101 e.stream().memcpy_dtod(
11102 &out_stage.slice(0..experts.input_width),
11103 &mut output.slice_mut(0..experts.input_width),
11104 )?;
11105 }
11106 output
11107 };
11108 if let Some(started) = started {
11109 use std::sync::atomic::Ordering;
11110 let ns = TIMING_NS
11111 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11112 + started.elapsed().as_nanos() as u64;
11113 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11114 if calls % 430 == 0 {
11115 eprintln!(
11116 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11117 ns as f64 / 1.0e6,
11118 ns as f64 / calls as f64 / 1.0e3,
11119 );
11120 }
11121 }
11122 return Ok(output);
11123 }
11124
11125 if let Some((_, device)) = workspace.ev_entry.as_ref() {
11129 if *device != e.ctx().ordinal() {
11130 return Err("NVFP4 device-routed routes engine changed".into());
11131 }
11132 } else {
11133 let _main = e.gpu.enter_main()?;
11134 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
11135 }
11136 if workspace.dev_route_e.is_none() {
11137 let _main = e.gpu.enter_main()?;
11138 workspace.dev_route_e = Some((
11139 e.htod_i32(&vec![0i32; n_sel])?,
11140 e.htod(&vec![0.0f32; n_sel])?,
11141 ));
11142 }
11143 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
11149 let e_device = e.ctx().ordinal();
11150 let rank1_routed_peek = workspace.rank1_routed;
11152 let stage_needed = !mirror
11153 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
11154 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
11155 });
11156 {
11157 let _main = e.gpu.enter_main()?;
11158 if stage_needed {
11159 let (sel_e, w_e) = workspace
11160 .dev_route_e
11161 .as_mut()
11162 .expect("device route staging set above");
11163 {
11164 let mut dst = sel_e.slice_mut(0..n_sel);
11165 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
11166 }
11167 {
11168 let mut dst = w_e.slice_mut(0..n_sel);
11169 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
11170 }
11171 }
11172 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11173 ev_entry.record(&e.stream())?;
11174 }
11175 let prestaged = std::mem::take(&mut workspace.prestaged);
11178 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
11179 for (rank_index, engine) in self.ranks.iter().enumerate() {
11180 let _main = engine.gpu.enter_main()?;
11181 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
11182 engine.stream().wait(ev_entry)?;
11183 if !prestaged {
11184 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
11185 engine
11186 .stream()
11187 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
11188 }
11189 if !(rank1_routed && rank_index == 1) {
11190 let same_dev = engine.ctx().ordinal() == e_device;
11194 if mirror {
11195 let Nvfp4DeviceRoutesWorkspace {
11198 sel,
11199 route_w,
11200 dev_route_e,
11201 ..
11202 } = &mut *workspace;
11203 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
11204 if same_dev {
11205 (sel_d, w_d)
11206 } else {
11207 let (sel_e, w_e) = dev_route_e
11208 .as_ref()
11209 .expect("device route staging set above");
11210 (sel_e, w_e)
11211 };
11212 engine.moe_sel_w_mirror(
11213 src_sel,
11214 src_w,
11215 &mut sel[rank_index],
11216 &mut route_w[rank_index],
11217 n_sel,
11218 )?;
11219 } else {
11220 let (sel_e, w_e) = workspace
11221 .dev_route_e
11222 .as_ref()
11223 .expect("device route staging set above");
11224 {
11225 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
11226 engine
11227 .stream()
11228 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
11229 }
11230 {
11231 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
11232 engine
11233 .stream()
11234 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
11235 }
11236 }
11237 }
11238 if !prestaged {
11239 let Nvfp4DeviceRoutesWorkspace {
11240 input, in_q, in_d, ..
11241 } = &mut *workspace;
11242 engine.quantize_q8_1_into(
11243 &input[rank_index],
11244 1,
11245 experts.input_width,
11246 &mut in_q[rank_index],
11247 &mut in_d[rank_index],
11248 )?;
11249 }
11250 }
11251 self.nvfp4_routes_batched_sweeps(
11252 experts,
11253 workspace,
11254 &[],
11255 &[],
11256 &[],
11257 local_out,
11258 n_sel,
11259 activation_limit,
11260 true,
11261 )?;
11262
11263 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
11266 let _main = engine.gpu.enter_main()?;
11267 workspace.ev_rank[rank_index].record(&engine.stream())?;
11268 }
11269 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
11272 let mut ticket = 0u32;
11273 if memops {
11274 use cudarc::driver::sys;
11275 if workspace.fence_flags_raw == 0 {
11276 let root = &self.ranks[0];
11277 let _main = root.gpu.enter_main()?;
11278 let mut ptr: sys::CUdeviceptr = 0;
11279 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
11280 if r != sys::CUresult::CUDA_SUCCESS {
11281 return Err(format!("fence flag alloc: {r:?}").into());
11282 }
11283 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
11284 if r != sys::CUresult::CUDA_SUCCESS {
11285 return Err(format!("fence flag memset: {r:?}").into());
11286 }
11287 workspace.fence_flags_raw = ptr as u64;
11288 }
11289 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
11290 ticket = workspace.fence_ticket;
11291 let base = workspace.fence_flags_raw;
11292 if fence_rank1_on() {
11298 let peer = &self.ranks[1];
11299 let _pmain = peer.gpu.enter_main()?;
11300 peer.ring_flag_raw(base, ticket)?;
11301 }
11302 {
11303 let root = &self.ranks[0];
11304 let _main = root.gpu.enter_main()?;
11305 let r = unsafe {
11306 sys::cuStreamWriteValue32_v2(
11307 root.stream().cu_stream() as sys::CUstream,
11308 (base + 4) as sys::CUdeviceptr,
11309 ticket,
11310 0,
11311 )
11312 };
11313 if r != sys::CUresult::CUDA_SUCCESS {
11314 return Err(format!("fence write root: {r:?}").into());
11315 }
11316 }
11317 }
11318 pre_join()?;
11321
11322 if moe_direct_on() && self.ranks.len() == 2 {
11323 let _main = e.gpu.enter_main()?;
11330 if memops {
11331 use cudarc::driver::sys;
11332 let base = workspace.fence_flags_raw;
11333 let r = unsafe {
11334 sys::cuStreamWaitValue32_v2(
11335 e.stream().cu_stream() as sys::CUstream,
11336 (base + 4) as sys::CUdeviceptr,
11337 ticket,
11338 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11339 )
11340 };
11341 if r != sys::CUresult::CUDA_SUCCESS {
11342 return Err(format!("fence wait: {r:?}").into());
11343 }
11344 if fence_rank1_on() {
11345 let r = unsafe {
11347 sys::cuStreamWaitValue32_v2(
11348 e.stream().cu_stream() as sys::CUstream,
11349 base as sys::CUdeviceptr,
11350 ticket,
11351 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
11352 )
11353 };
11354 if r != sys::CUresult::CUDA_SUCCESS {
11355 return Err(format!("fence wait rank1: {r:?}").into());
11356 }
11357 } else {
11358 for ev in workspace.ev_rank.iter().skip(1) {
11359 e.stream().wait(ev)?;
11360 }
11361 }
11362 } else {
11363 {
11364 let root = &self.ranks[0];
11365 let _rmain = root.gpu.enter_main()?;
11366 workspace
11367 .ev_done
11368 .as_ref()
11369 .expect("device routes done event")
11370 .record(&root.stream())?;
11371 }
11372 e.stream().wait(
11373 workspace
11374 .ev_done
11375 .as_ref()
11376 .expect("device routes done event"),
11377 )?;
11378 for ev in workspace.ev_rank.iter().skip(1) {
11379 e.stream().wait(ev)?;
11380 }
11381 }
11382 let mut output = e.uninit(experts.input_width)?;
11383 if let Some((sh_raw, scale_raw)) = post_add {
11384 e.add3_raw(
11387 &workspace.accumulator[0],
11388 &workspace.accumulator[1],
11389 sh_raw,
11390 scale_raw,
11391 &mut output,
11392 experts.input_width,
11393 )?;
11394 } else {
11395 e.add(
11396 &workspace.accumulator[0],
11397 &workspace.accumulator[1],
11398 &mut output,
11399 experts.input_width,
11400 )?;
11401 }
11402 let output = output;
11403 if let Some(started) = started {
11404 use std::sync::atomic::Ordering;
11405 let ns = TIMING_NS
11406 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11407 + started.elapsed().as_nanos() as u64;
11408 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11409 if calls % 430 == 0 {
11410 eprintln!(
11411 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11412 ns as f64 / 1.0e6,
11413 ns as f64 / calls as f64 / 1.0e3,
11414 );
11415 }
11416 }
11417 return Ok(output);
11418 }
11419 {
11420 let root = &self.ranks[0];
11421 let _main = root.gpu.enter_main()?;
11422 for ev in workspace.ev_rank.iter().skip(1) {
11423 root.stream().wait(ev)?;
11424 }
11425 root.stream()
11426 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
11427 {
11428 let Nvfp4DeviceRoutesWorkspace {
11429 accumulator,
11430 remote,
11431 combined,
11432 ..
11433 } = &mut *workspace;
11434 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11435 }
11436 workspace
11437 .ev_done
11438 .as_ref()
11439 .expect("device routes done event")
11440 .record(&root.stream())?;
11441 }
11442 let output = {
11443 let _main = e.gpu.enter_main()?;
11444 e.stream().wait(
11445 workspace
11446 .ev_done
11447 .as_ref()
11448 .expect("device routes done event"),
11449 )?;
11450 let mut output = e.uninit(experts.input_width)?;
11453 e.stream().memcpy_dtod(
11454 &workspace.combined.slice(0..experts.input_width),
11455 &mut output.slice_mut(0..experts.input_width),
11456 )?;
11457 output
11458 };
11459 if let Some(started) = started {
11460 use std::sync::atomic::Ordering;
11461 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
11462 + started.elapsed().as_nanos() as u64;
11463 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
11464 if calls % 430 == 0 {
11465 eprintln!(
11466 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
11467 ns as f64 / 1.0e6,
11468 ns as f64 / calls as f64 / 1.0e3,
11469 );
11470 }
11471 }
11472 Ok(output)
11473 }
11474
11475 pub(crate) fn decode_v2_finish_root_fused(
11479 &self,
11480 ws: &mut StepTpDecodeV2Ws,
11481 ) -> Result<(), Box<dyn std::error::Error>> {
11482 let root = &self.ranks[0];
11483 let _main = root.gpu.enter_main()?;
11484 if ws.raw_peer_partial != 0 {
11485 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
11487 } else {
11488 root.stream()
11489 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
11490 }
11491 {
11492 let StepTpDecodeV2Ws {
11493 o_partials,
11494 peer_partial,
11495 reduce_a,
11496 o_out,
11497 ..
11498 } = &mut *ws;
11499 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
11500 }
11501 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
11502 if shadows {
11503 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
11506 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
11507 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
11508 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
11509 }
11510 if shadows && ws.raw_peer_partial != 0 {
11511 raw_copy_bytes(
11512 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
11513 ws.raw_k1,
11514 ws.local_kv_dim * 4,
11515 root,
11516 )?;
11517 raw_copy_bytes(
11518 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
11519 ws.raw_v1,
11520 ws.local_kv_dim * 4,
11521 root,
11522 )?;
11523 } else if shadows {
11524 let start = ws.local_kv_dim;
11525 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
11526 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
11527 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
11528 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
11529 }
11530 if ws.raw_mixed_stage_e != 0 {
11531 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
11534 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
11535 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
11536 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
11537 }
11538 Ok(())
11539 }
11540
11541 pub(crate) fn decode_v2_arm_token_mirrors(
11544 &self,
11545 ws: &mut StepTpDecodeV2Ws,
11546 mixed_stage_e: u64,
11547 shadow_stage_e: (u64, u64),
11548 ) -> Result<(), Box<dyn std::error::Error>> {
11549 use cudarc::driver::DevicePtr;
11550 let root = &self.ranks[0];
11551 let _main = root.gpu.enter_main()?;
11552 let stream = root.stream();
11553 let (a, _g) = ws.reduce_a.device_ptr(&stream);
11554 ws.raw_reduce_a = a as u64;
11555 ws.raw_mixed_stage_e = mixed_stage_e;
11556 ws.raw_shadow_stage_e = shadow_stage_e;
11557 Ok(())
11558 }
11559
11560 fn nvfp4_routes_build_graph(
11566 &self,
11567 experts: &ResidentNvfp4TensorParallel,
11568 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11569 local_out: usize,
11570 n_sel: usize,
11571 activation_limit: Option<f32>,
11572 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
11573 use cudarc::driver::DevicePtr;
11574 use cudarc::driver::sys;
11575 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
11576 if r == sys::CUresult::CUDA_SUCCESS {
11577 Ok(())
11578 } else {
11579 Err(format!("{what}: {r:?}").into())
11580 }
11581 }
11582 let world = self.ranks.len();
11583 if world != 2 {
11584 return Err("routes graph door is built for the TP2 pair".into());
11585 }
11586 let width = experts.input_width;
11587
11588 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
11590 let stream = engine.stream();
11591 let (ptr, _g) = buf.device_ptr(&stream);
11592 ptr as u64
11593 };
11594 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
11595 let stream = engine.stream();
11596 let (ptr, _g) = buf.device_ptr(&stream);
11597 ptr as u64
11598 };
11599 let (sel_e, w_e) = workspace
11600 .dev_route_e
11601 .as_ref()
11602 .expect("device route staging set before graph build");
11603 let root_engine = &self.ranks[0];
11604 let p_in_stage = ptr_f32(
11605 workspace.in_stage_e.as_ref().expect("graph staging"),
11606 root_engine,
11607 );
11608 let p_out_stage = ptr_f32(
11609 workspace.out_stage_e.as_ref().expect("graph staging"),
11610 root_engine,
11611 );
11612 let p_sel_e = ptr_i32(sel_e, root_engine);
11613 let p_w_e = ptr_f32(w_e, root_engine);
11614 let p_input: Vec<u64> = (0..world)
11615 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
11616 .collect();
11617 let p_sel: Vec<u64> = (0..world)
11618 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
11619 .collect();
11620 let p_route_w: Vec<u64> = (0..world)
11621 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
11622 .collect();
11623 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
11624 let p_remote = ptr_f32(&workspace.remote, root_engine);
11625 let p_combined = ptr_f32(&workspace.combined, root_engine);
11626
11627 let raw_copy = |dst: u64,
11628 src: u64,
11629 bytes: usize,
11630 engine: &Engine|
11631 -> Result<(), Box<dyn std::error::Error>> {
11632 unsafe {
11633 cu_try(
11634 sys::cuMemcpyAsync(
11635 dst as sys::CUdeviceptr,
11636 src as sys::CUdeviceptr,
11637 bytes,
11638 engine.stream().cu_stream() as sys::CUstream,
11639 ),
11640 "routes graph cuMemcpyAsync",
11641 )
11642 }
11643 };
11644
11645 let mut children = Vec::with_capacity(3);
11646 for rank in 0..world {
11647 let engine = &self.ranks[rank];
11648 let _main = engine.gpu.enter_main()?;
11649 let (child, _retained) = engine.capture_graph_retained(|_| {
11650 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
11651 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
11652 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
11653 {
11654 let Nvfp4DeviceRoutesWorkspace {
11655 input, in_q, in_d, ..
11656 } = &mut *workspace;
11657 engine.quantize_q8_1_into(
11658 &input[rank],
11659 1,
11660 width,
11661 &mut in_q[rank],
11662 &mut in_d[rank],
11663 )?;
11664 }
11665 self.nvfp4_routes_batched_sweeps_rank(
11666 experts,
11667 workspace,
11668 &[],
11669 &[],
11670 &[],
11671 local_out,
11672 n_sel,
11673 activation_limit,
11674 true,
11675 rank,
11676 )?;
11677 Ok(())
11678 })?;
11679 children.push(child);
11680 }
11681 {
11682 let root = &self.ranks[0];
11683 let _main = root.gpu.enter_main()?;
11684 let (child, _retained) = root.capture_graph_retained(|_| {
11685 raw_copy(p_remote, p_acc1, width * 4, root)?;
11686 {
11687 let Nvfp4DeviceRoutesWorkspace {
11688 accumulator,
11689 remote,
11690 combined,
11691 ..
11692 } = &mut *workspace;
11693 root.add(&accumulator[0], remote, combined, width)?;
11694 }
11695 raw_copy(p_out_stage, p_combined, width * 4, root)?;
11696 Ok(())
11697 })?;
11698 children.push(child);
11699 }
11700
11701 let mut parent: sys::CUgraph = std::ptr::null_mut();
11702 unsafe {
11703 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
11704 }
11705 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
11706 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
11707 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
11708 unsafe {
11709 cu_try(
11710 sys::cuGraphAddChildGraphNode(
11711 &mut n0,
11712 parent,
11713 std::ptr::null(),
11714 0,
11715 children[0].cu_graph(),
11716 ),
11717 "routes child r0",
11718 )?;
11719 cu_try(
11720 sys::cuGraphAddChildGraphNode(
11721 &mut n1,
11722 parent,
11723 std::ptr::null(),
11724 0,
11725 children[1].cu_graph(),
11726 ),
11727 "routes child r1",
11728 )?;
11729 let deps = [n0, n1];
11730 cu_try(
11731 sys::cuGraphAddChildGraphNode(
11732 &mut n2,
11733 parent,
11734 deps.as_ptr(),
11735 2,
11736 children[2].cu_graph(),
11737 ),
11738 "routes child root",
11739 )?;
11740 }
11741 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
11742 unsafe {
11743 cu_try(
11744 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
11745 "routes instantiate",
11746 )?;
11747 }
11748 Ok(RoutesGraph {
11749 exec,
11750 parent,
11751 _children: children,
11752 })
11753 }
11754
11755 #[allow(clippy::too_many_arguments)]
11759 pub(crate) fn routes_rank_section(
11760 &self,
11761 experts: &ResidentNvfp4TensorParallel,
11762 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11763 raw_input_src: u64,
11764 local_out: usize,
11765 n_sel: usize,
11766 activation_limit: Option<f32>,
11767 rank_index: usize,
11768 ) -> Result<(), Box<dyn std::error::Error>> {
11769 let engine = &self.ranks[rank_index];
11770 {
11771 let _main = engine.gpu.enter_main()?;
11772 let (sel_e_ptr, w_e_ptr) = workspace
11774 .raw_dev_route_e
11775 .ok_or("routes rank section requires armed staging pointers")?;
11776 raw_copy_bytes(
11777 workspace.raw_input[rank_index],
11778 raw_input_src,
11779 experts.input_width * 4,
11780 engine,
11781 )?;
11782 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
11783 raw_copy_bytes(
11784 workspace.raw_route_w[rank_index],
11785 w_e_ptr,
11786 n_sel * 4,
11787 engine,
11788 )?;
11789 {
11790 let Nvfp4DeviceRoutesWorkspace {
11791 input, in_q, in_d, ..
11792 } = &mut *workspace;
11793 engine.quantize_q8_1_into(
11794 &input[rank_index],
11795 1,
11796 experts.input_width,
11797 &mut in_q[rank_index],
11798 &mut in_d[rank_index],
11799 )?;
11800 }
11801 }
11802 self.nvfp4_routes_batched_sweeps_rank(
11803 experts,
11804 workspace,
11805 &[],
11806 &[],
11807 &[],
11808 local_out,
11809 n_sel,
11810 activation_limit,
11811 true,
11812 rank_index,
11813 )
11814 }
11815
11816 pub(crate) fn routes_root_section(
11819 &self,
11820 experts: &ResidentNvfp4TensorParallel,
11821 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11822 ) -> Result<(), Box<dyn std::error::Error>> {
11823 let root = &self.ranks[0];
11824 let _main = root.gpu.enter_main()?;
11825 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
11826 .raw_combine
11827 .ok_or("routes root section requires armed combine pointers")?;
11828 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
11829 {
11830 let Nvfp4DeviceRoutesWorkspace {
11831 accumulator,
11832 remote,
11833 combined,
11834 ..
11835 } = &mut *workspace;
11836 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11837 }
11838 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
11839 Ok(())
11840 }
11841
11842 pub(crate) fn routes_arm_raw(
11845 &self,
11846 experts: &ResidentNvfp4TensorParallel,
11847 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11848 ) -> Result<(), Box<dyn std::error::Error>> {
11849 use cudarc::driver::DevicePtr;
11850 if workspace.raw_dev_route_e.is_some() {
11851 return Ok(());
11852 }
11853 let _ = experts;
11854 let (sel_e, w_e) = workspace
11855 .dev_route_e
11856 .as_ref()
11857 .ok_or("routes staging not armed")?;
11858 let root = &self.ranks[0];
11859 {
11860 let _main = root.gpu.enter_main()?;
11861 let stream = root.stream();
11862 let (a, _g) = sel_e.device_ptr(&stream);
11863 let (b, _g) = w_e.device_ptr(&stream);
11864 workspace.raw_dev_route_e = Some((a as u64, b as u64));
11865 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
11866 let (d, _g) = workspace.remote.device_ptr(&stream);
11867 let (f, _g) = workspace.combined.device_ptr(&stream);
11868 let out_stage = workspace
11869 .out_stage_e
11870 .as_ref()
11871 .ok_or("routes out stage not armed")?;
11872 let (g_, _g) = out_stage.device_ptr(&stream);
11873 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
11874 }
11875 for rank in 0..self.ranks.len() {
11876 let engine = &self.ranks[rank];
11877 let _main = engine.gpu.enter_main()?;
11878 let stream = engine.stream();
11879 let (a, _g) = workspace.input[rank].device_ptr(&stream);
11880 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
11881 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
11882 workspace.raw_input.push(a as u64);
11883 workspace.raw_sel.push(b as u64);
11884 workspace.raw_route_w.push(c as u64);
11885 }
11886 Ok(())
11887 }
11888
11889 pub fn run_tensor_parallel_routes_nvfp4(
11893 &self,
11894 experts: &ResidentNvfp4TensorParallel,
11895 input: &[f32],
11896 tokens: usize,
11897 selected: &[usize],
11898 route_weights: &[f32],
11899 experts_per_token: usize,
11900 activation_limit: Option<f32>,
11901 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11902 validate_activations(input, tokens, experts.input_width)?;
11903 let pairs = tokens
11904 .checked_mul(experts_per_token)
11905 .ok_or("NVFP4 TP route count overflow")?;
11906 if selected.len() != pairs || route_weights.len() != pairs {
11907 return Err(format!(
11908 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
11909 {experts_per_token} ({pairs})",
11910 selected.len(),
11911 route_weights.len(),
11912 )
11913 .into());
11914 }
11915 if !route_weights.iter().all(|weight| weight.is_finite()) {
11916 return Err("NVFP4 TP route weights contain a non-finite value".into());
11917 }
11918
11919 let mut output = vec![0.0f32; tokens * experts.input_width];
11920 for token in 0..tokens {
11921 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11922 for slot in 0..experts_per_token {
11923 let pair = token * experts_per_token + slot;
11924 let expert = selected[pair];
11925 if expert >= experts.expert_count {
11926 return Err(format!(
11927 "NVFP4 TP selected expert {expert} outside 0..{}",
11928 experts.expert_count
11929 )
11930 .into());
11931 }
11932 let gate = if experts.ep2 {
11938 self.run_full_bank_expert_nvfp4(
11939 &experts.gate,
11940 &experts.macros_gate,
11941 expert,
11942 input_row,
11943 )?
11944 } else {
11945 self.run_column_bank_expert_nvfp4(
11946 &experts.gate,
11947 &experts.macros_gate,
11948 expert,
11949 input_row,
11950 )?
11951 };
11952 let up = if experts.ep2 {
11953 self.run_full_bank_expert_nvfp4(
11954 &experts.up,
11955 &experts.macros_up,
11956 expert,
11957 input_row,
11958 )?
11959 } else {
11960 self.run_column_bank_expert_nvfp4(
11961 &experts.up,
11962 &experts.macros_up,
11963 expert,
11964 input_row,
11965 )?
11966 };
11967 let activated: Vec<f32> = gate
11968 .iter()
11969 .zip(&up)
11970 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11971 .collect();
11972 debug_assert_eq!(activated.len(), experts.expert_width);
11973 let down = if experts.ep2 {
11974 self.run_full_down_expert_nvfp4(
11975 &experts.down,
11976 &experts.macros_down,
11977 expert,
11978 &activated,
11979 )?
11980 } else {
11981 self.run_row_bank_expert_nvfp4(
11982 &experts.down,
11983 &experts.macros_down,
11984 expert,
11985 &activated,
11986 )?
11987 };
11988 let weight = route_weights[pair];
11989 for (sum, value) in output
11990 [token * experts.input_width..(token + 1) * experts.input_width]
11991 .iter_mut()
11992 .zip(down)
11993 {
11994 *sum += weight * value;
11995 }
11996 }
11997 }
11998 Ok(output)
11999 }
12000}
12001
12002#[cfg(test)]
12003mod tests {
12004 use super::*;
12005
12006 #[test]
12007 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
12008 let limit = Some(7.0);
12009 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
12010 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
12011 assert!(
12012 step_expert_activation_host(-20.0, 9.0, limit).abs()
12013 < step_expert_activation_host(-20.0, 9.0, None).abs()
12014 );
12015 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
12016 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
12017 assert!(validate_step_expert_activation_limit(limit).is_ok());
12018 }
12019
12020 #[test]
12021 fn moe_residual_host_preserves_official_add_order() {
12022 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
12023 assert_eq!(output, [0.0]);
12024 assert_eq!(
12025 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
12026 "MoE residual lengths residual=1 routed=2 shared=1"
12027 );
12028 }
12029
12030 #[test]
12031 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
12032 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
12033 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
12034 assert_eq!(owners.len(), 4);
12035 for (rank, owner) in owners.iter().enumerate() {
12036 assert_eq!(owner.rank, rank);
12037 assert_eq!(owner.selected, vec![0, 36]);
12038 assert_eq!(owner.token_rows, vec![0, 0]);
12039 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
12040 }
12041 }
12042
12043 #[test]
12044 fn expert_owner_routes_validate_geometry_and_selected_experts() {
12045 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
12046 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
12047 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
12048 assert!(error.contains("outside 0..288"));
12049 }
12050
12051 #[test]
12052 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
12053 let selected = [
12054 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
12055 ];
12056 assert_eq!(
12057 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
12058 16
12059 );
12060 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
12061 assert_eq!(
12062 owners
12063 .iter()
12064 .map(|owner| owner.selected.len())
12065 .collect::<Vec<_>>(),
12066 vec![2, 4, 6, 4]
12067 );
12068 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
12069 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
12070 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
12071 }
12072
12073 #[test]
12074 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
12075 let owner0 = [0usize, 3];
12076 let owner1 = [1usize, 2];
12077 let owners = [owner0.as_slice(), owner1.as_slice()];
12078 assert_eq!(
12079 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
12080 .unwrap(),
12081 WeightedRouteCombineShape {
12082 pairs: 4,
12083 max_pairs: 12,
12084 }
12085 );
12086 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
12087 assert!(
12088 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
12089 .is_err()
12090 );
12091 assert!(
12092 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
12093 .is_err()
12094 );
12095 assert!(
12096 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
12097 .is_err()
12098 );
12099 }
12100
12101 #[test]
12102 fn native_p2p_door_is_strict_and_default_off() {
12103 assert!(!parse_step_tp_native_p2p(None).unwrap());
12104 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
12105 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
12106 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
12107 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
12108 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
12109 }
12110
12111 #[test]
12112 fn bulk_p2p_door_is_strict_and_default_off() {
12113 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
12114 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
12115 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
12116 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
12117 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
12118 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
12119 }
12120
12121 #[test]
12122 fn ep_device_arithmetic_door_is_strict_and_default_off() {
12123 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
12124 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
12125 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
12126 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
12127 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
12128 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
12129 }
12130
12131 #[test]
12132 fn f32_mirror_door_is_strict_and_default_off() {
12133 assert!(!parse_step_tp_f32_mirror(None).unwrap());
12134 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
12135 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
12136 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
12137 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
12138 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
12139 }
12140
12141 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
12142 let codes = (0..out_features * in_features)
12143 .map(|index| (index % 251) as u8)
12144 .collect();
12145 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
12146 .map(|index| index as f32 + 1.0)
12147 .collect();
12148 (codes, scales)
12149 }
12150
12151 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
12152 (0..out_features * in_features)
12153 .flat_map(|value| (value as u16).to_le_bytes())
12154 .collect()
12155 }
12156
12157 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
12158 bytes
12159 .chunks_exact(2)
12160 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
12161 .collect()
12162 }
12163
12164 #[test]
12165 fn bf16_matrix_rejects_wrong_byte_count() {
12166 let bytes = vec![0u8; 4 * 4 * 2 - 1];
12167 let matrix = Bf16Matrix {
12168 bytes: &bytes,
12169 out_features: 4,
12170 in_features: 4,
12171 };
12172 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
12173 }
12174
12175 #[test]
12176 fn replicated_device_rows_require_exact_rank_local_shapes() {
12177 assert_eq!(
12178 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
12179 12_288
12180 );
12181 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
12182 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
12183 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
12184 assert!(
12185 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
12186 );
12187 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
12188 }
12189
12190 #[test]
12191 fn replicated_device_row_refresh_requires_exact_root_source() {
12192 assert_eq!(
12193 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
12194 12_288
12195 );
12196 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
12197 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
12198 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
12199 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
12200 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
12201 }
12202
12203 #[test]
12204 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
12205 for tp in [1, 2, 4, 8] {
12206 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
12207 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
12208 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
12209 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
12210 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
12211 }
12212 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
12213 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
12214 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
12215 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
12216 }
12217
12218 #[test]
12219 fn cache_rows_split_by_token_then_rank() {
12220 let rows = (0u8..24).collect::<Vec<_>>();
12221 assert_eq!(
12222 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
12223 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
12224 );
12225 assert_eq!(
12226 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
12227 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
12228 );
12229 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
12230 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
12231 }
12232
12233 #[test]
12234 fn bf16_column_shard_preserves_contiguous_output_rows() {
12235 let bytes = bf16_matrix_bytes(4, 4);
12236 let matrix = Bf16Matrix {
12237 bytes: &bytes,
12238 out_features: 4,
12239 in_features: 4,
12240 };
12241 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
12242 assert_eq!(shard.out_features, 2);
12243 assert_eq!(shard.in_features, 4);
12244 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
12245 }
12246
12247 #[test]
12248 fn bf16_row_shard_preserves_each_input_column_window() {
12249 let bytes = bf16_matrix_bytes(3, 4);
12250 let matrix = Bf16Matrix {
12251 bytes: &bytes,
12252 out_features: 3,
12253 in_features: 4,
12254 };
12255 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
12256 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
12257 }
12258
12259 #[test]
12260 fn bf16_row_block_preserves_global_column_order() {
12261 let bytes = bf16_matrix_bytes(3, 8);
12262 let matrix = Bf16Matrix {
12263 bytes: &bytes,
12264 out_features: 3,
12265 in_features: 8,
12266 };
12267 let block = bf16_row_block(matrix, 2, 3).unwrap();
12268 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
12269 }
12270
12271 #[test]
12272 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
12273 let (codes, scales) = matrix(1280, 4096);
12274 let matrix = E4m3BlockMatrix {
12275 codes: &codes,
12276 scales: &scales,
12277 out_features: 1280,
12278 in_features: 4096,
12279 };
12280 let shard = column_shard(matrix, 2, 1).unwrap();
12281 assert_eq!(shard.out_features, 640);
12282 assert_eq!(shard.codes, &codes[640 * 4096..]);
12283 assert_eq!(shard.scales, &scales[5 * 32..]);
12284 }
12285
12286 #[test]
12287 fn row_shard_preserves_each_weight_and_scale_column_window() {
12288 let (codes, scales) = matrix(4096, 1280);
12289 let matrix = E4m3BlockMatrix {
12290 codes: &codes,
12291 scales: &scales,
12292 out_features: 4096,
12293 in_features: 1280,
12294 };
12295 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
12296 assert_eq!(shard_codes.len(), 4096 * 640);
12297 assert_eq!(&shard_codes[..640], &codes[640..1280]);
12298 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
12299 assert_eq!(shard_scales.len(), 32 * 5);
12300 assert_eq!(&shard_scales[..5], &scales[5..10]);
12301 assert_eq!(&shard_scales[5..10], &scales[15..20]);
12302 }
12303
12304 #[test]
12305 fn activation_shards_keep_token_rows_separate() {
12306 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
12307 assert_eq!(
12308 activation_shard(&activations, 2, 8, 2, 1),
12309 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
12310 );
12311 }
12312
12313 #[test]
12314 fn expert_bank_selects_expert_major_code_and_scale_planes() {
12315 let expert_count = 2;
12316 let out_features = 128;
12317 let in_features = 128;
12318 let code_stride = out_features * in_features;
12319 let codes: Vec<u8> = (0..expert_count * code_stride)
12320 .map(|index| (index % 251) as u8)
12321 .collect();
12322 let scales = vec![1.0f32, 2.0];
12323 let bank = E4m3ExpertBank {
12324 codes: &codes,
12325 scales: &scales,
12326 expert_count,
12327 out_features,
12328 in_features,
12329 };
12330 bank.validate().unwrap();
12331 let expert = bank.expert(1).unwrap();
12332 assert_eq!(expert.codes, &codes[code_stride..]);
12333 assert_eq!(expert.scales, &[2.0]);
12334 }
12335
12336 #[test]
12337 fn expert_bank_rejects_non_positive_scale() {
12338 let codes = vec![0u8; 128 * 128];
12339 let scales = vec![0.0f32];
12340 let bank = E4m3ExpertBank {
12341 codes: &codes,
12342 scales: &scales,
12343 expert_count: 1,
12344 out_features: 128,
12345 in_features: 128,
12346 };
12347 assert!(bank.validate().unwrap_err().contains("non-positive"));
12348 }
12349
12350 #[test]
12351 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
12352 let expert_count = 2;
12353 let out_features = 256;
12354 let in_features = 128;
12355 let code_stride = out_features * in_features;
12356 let scale_stride = 2;
12357 let codes = (0..expert_count * code_stride)
12358 .map(|index| (index % 251) as u8)
12359 .collect::<Vec<_>>();
12360 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12361 let bank = E4m3ExpertBank {
12362 codes: &codes,
12363 scales: &scales,
12364 expert_count,
12365 out_features,
12366 in_features,
12367 };
12368
12369 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
12370 assert_eq!(rank.out_features, 128);
12371 assert_eq!(rank.in_features, 128);
12372 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12373 assert_eq!(rank.scales, vec![11.0, 21.0]);
12374 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
12375 assert_eq!(
12376 &rank.codes[128 * 128..],
12377 &codes[code_stride + 128 * 128..2 * code_stride]
12378 );
12379 assert_eq!(scale_stride, scales.len() / expert_count);
12380 }
12381
12382 #[test]
12383 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
12384 let expert_count = 2;
12385 let out_features = 128;
12386 let in_features = 256;
12387 let code_stride = out_features * in_features;
12388 let codes = (0..expert_count * code_stride)
12389 .map(|index| (index % 251) as u8)
12390 .collect::<Vec<_>>();
12391 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
12392 let bank = E4m3ExpertBank {
12393 codes: &codes,
12394 scales: &scales,
12395 expert_count,
12396 out_features,
12397 in_features,
12398 };
12399
12400 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12401 assert_eq!(rank.out_features, 128);
12402 assert_eq!(rank.in_features, 128);
12403 assert_eq!(rank.k_blocks, Some(1));
12404 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
12405 assert_eq!(rank.scales, vec![11.0, 21.0]);
12406 assert_eq!(&rank.codes[..128], &codes[128..256]);
12407 assert_eq!(
12408 &rank.codes[128 * 128..128 * 128 + 128],
12409 &codes[code_stride + 128..code_stride + 256]
12410 );
12411 }
12412
12413 #[test]
12414 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
12415 let expert_count = 2;
12416 let out_features = 256;
12417 let in_features = 512;
12418 let code_stride = out_features * in_features;
12419 let mut codes = vec![0u8; expert_count * code_stride];
12420 for expert in 0..expert_count {
12421 for row in 0..out_features {
12422 for block in 0..4 {
12423 let value = (expert * 80 + block * 16 + row % 16) as u8;
12424 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
12425 codes[start..start + FP8_BLOCK].fill(value);
12426 }
12427 }
12428 }
12429 let scales = vec![
12430 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,
12431 112.0, 113.0, 114.0,
12432 ];
12433 let bank = E4m3ExpertBank {
12434 codes: &codes,
12435 scales: &scales,
12436 expert_count,
12437 out_features,
12438 in_features,
12439 };
12440
12441 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
12442 assert_eq!(rank.out_features, out_features);
12443 assert_eq!(rank.in_features, 256);
12444 assert_eq!(rank.k_blocks, Some(2));
12445 assert_eq!(rank.code_stride, out_features * 256);
12446 assert_eq!(rank.scale_stride, 4);
12447 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
12448 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
12449
12450 let block_stride = out_features * FP8_BLOCK;
12451 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
12452 assert!(
12453 rank.codes[block_stride..block_stride + FP8_BLOCK]
12454 .iter()
12455 .all(|&code| code == 48)
12456 );
12457 assert!(
12458 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
12459 .iter()
12460 .all(|&code| code == 112)
12461 );
12462 assert!(
12463 rank.codes
12464 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
12465 .iter()
12466 .all(|&code| code == 128)
12467 );
12468 }
12469
12470 #[test]
12471 fn step_ep_layer_specs_are_literal_and_fail_closed() {
12472 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
12473 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
12474 assert_eq!(
12475 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
12476 vec![StepEpLayerSpec {
12477 layer: 24,
12478 devices: vec![1, 2],
12479 }]
12480 );
12481 assert_eq!(
12482 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
12483 vec![
12484 StepEpLayerSpec {
12485 layer: 24,
12486 devices: vec![1, 2],
12487 },
12488 StepEpLayerSpec {
12489 layer: 25,
12490 devices: vec![1, 2],
12491 },
12492 StepEpLayerSpec {
12493 layer: 31,
12494 devices: vec![0, 2],
12495 },
12496 ]
12497 );
12498 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
12499 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
12500 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
12501 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
12502 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
12503 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12504 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
12505 }
12506
12507 #[test]
12508 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
12509 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
12510 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
12511 assert_eq!(
12512 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
12513 vec![
12514 StepTpLayerSpec {
12515 layer: 24,
12516 devices: vec![1, 2],
12517 },
12518 StepTpLayerSpec {
12519 layer: 25,
12520 devices: vec![1, 2],
12521 },
12522 ]
12523 );
12524 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
12525 assert!(error.contains("MEMRA_STEP_TP"));
12526 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
12527 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
12528
12529 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
12530 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
12531 assert_eq!(all.first().unwrap().layer, 0);
12532 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
12533 let devices = (0..8).collect::<Vec<_>>();
12534 assert!(all.iter().all(|spec| spec.devices == devices));
12535 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
12536 }
12537}
12538
12539struct TokenGraphChild {
12551 graph: cudarc::driver::CudaGraph,
12552 node: cudarc::driver::sys::CUgraphNode,
12553 ctx: cudarc::driver::sys::CUcontext,
12554}
12555
12556struct TokenGraphFaSite {
12560 ctx: cudarc::driver::sys::CUcontext,
12561 memset_o: cudarc::driver::sys::CUgraphNode,
12562 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
12563 fa: cudarc::driver::sys::CUgraphNode,
12564 combine: cudarc::driver::sys::CUgraphNode,
12565 window: usize,
12566 n_head: usize,
12567 n_head_kv: usize,
12568 head_dim: usize,
12569}
12570
12571pub struct TokenGraphBuilder {
12572 parent: cudarc::driver::sys::CUgraph,
12573 children: Vec<TokenGraphChild>,
12574 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
12577 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
12580 group: Option<(
12583 u32,
12584 Vec<cudarc::driver::sys::CUgraphNode>,
12585 Vec<cudarc::driver::sys::CUgraphNode>,
12586 )>,
12587}
12588
12589unsafe impl Send for TokenGraphBuilder {}
12591
12592impl TokenGraphBuilder {
12593 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
12594 use cudarc::driver::sys;
12595 let mut parent: sys::CUgraph = std::ptr::null_mut();
12596 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
12597 if r != sys::CUresult::CUDA_SUCCESS {
12598 return Err(format!("token graph create: {r:?}").into());
12599 }
12600 Ok(Self {
12601 parent,
12602 children: Vec::new(),
12603 frontier: Vec::new(),
12604 pending_detached: Vec::new(),
12605 group: None,
12606 })
12607 }
12608
12609 fn push_child(
12610 &mut self,
12611 graph: cudarc::driver::CudaGraph,
12612 parallel_group: Option<u32>,
12613 detached: bool,
12614 absorb: bool,
12615 ctx: cudarc::driver::sys::CUcontext,
12616 ) -> Result<(), Box<dyn std::error::Error>> {
12617 use cudarc::driver::sys;
12618 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
12622 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
12623 (state, Some(group)) => {
12624 if let Some((_, _, members)) = state.take() {
12626 self.frontier = members;
12627 }
12628 let base = self.frontier.clone();
12629 *state = Some((group, base.clone(), Vec::new()));
12630 base
12631 }
12632 (state, None) if detached => match state.as_ref() {
12633 Some((_, base, _)) => base.clone(),
12634 None => self.frontier.clone(),
12635 },
12636 (state, None) => {
12637 if let Some((_, _, members)) = state.take() {
12638 self.frontier = members;
12639 }
12640 let mut deps = self.frontier.clone();
12641 if absorb {
12642 deps.append(&mut self.pending_detached);
12643 }
12644 deps
12645 }
12646 };
12647 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12648 let r = unsafe {
12649 sys::cuGraphAddChildGraphNode(
12650 &mut node,
12651 self.parent,
12652 if deps.is_empty() {
12653 std::ptr::null()
12654 } else {
12655 deps.as_ptr()
12656 },
12657 deps.len(),
12658 graph.cu_graph(),
12659 )
12660 };
12661 if r != sys::CUresult::CUDA_SUCCESS {
12662 return Err(format!("token graph child: {r:?}").into());
12663 }
12664 match (&mut self.group, parallel_group, detached) {
12665 (_, None, true) => self.pending_detached.push(node),
12666 (Some((_, _, members)), Some(_), _) => members.push(node),
12667 _ => self.frontier = vec![node],
12668 }
12669 self.children.push(TokenGraphChild { graph, node, ctx });
12670 Ok(())
12671 }
12672
12673 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
12674 use cudarc::driver::sys;
12675 if let Some((_, _, members)) = self.group.take() {
12676 self.frontier = members;
12677 }
12678 let mut fa_sites = Vec::new();
12681 for child in &self.children {
12682 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
12683 fa_sites.push(site);
12684 }
12685 }
12686 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12687 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
12688 if r != sys::CUresult::CUDA_SUCCESS {
12689 return Err(format!("token graph instantiate: {r:?}").into());
12690 }
12691 Ok(TokenGraph {
12692 exec,
12693 parent: self.parent,
12694 _children: self.children,
12695 fa_sites,
12696 })
12697 }
12698}
12699
12700fn discover_fa_site(
12703 child_node: cudarc::driver::sys::CUgraphNode,
12704 ctx: cudarc::driver::sys::CUcontext,
12705) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
12706 use cudarc::driver::sys;
12707 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12708 if r == sys::CUresult::CUDA_SUCCESS {
12709 Ok(())
12710 } else {
12711 Err(format!("{what}: {r:?}").into())
12712 }
12713 }
12714 let mut graph: sys::CUgraph = std::ptr::null_mut();
12715 unsafe {
12716 cu_try(
12717 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
12718 "fa-site child GetGraph",
12719 )?;
12720 }
12721 let mut count: usize = 0;
12722 unsafe {
12723 cu_try(
12724 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
12725 "fa-site GetNodes(count)",
12726 )?;
12727 }
12728 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
12729 unsafe {
12730 cu_try(
12731 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
12732 "fa-site GetNodes",
12733 )?;
12734 }
12735 nodes.truncate(count);
12736 let node_type =
12737 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
12738 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
12739 unsafe {
12740 cu_try(
12741 sys::cuGraphNodeGetType(node, &mut ty),
12742 "fa-site NodeGetType",
12743 )?;
12744 }
12745 Ok(ty)
12746 };
12747 let memsets: Vec<sys::CUgraphNode> = {
12748 let mut v = Vec::new();
12749 for &node in &nodes {
12750 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
12751 v.push(node);
12752 }
12753 }
12754 v
12755 };
12756 if memsets.len() != 3 {
12757 return Ok(None);
12758 }
12759 let dependents =
12761 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
12762 let mut n: usize = 0;
12763 unsafe {
12764 cu_try(
12765 sys::cuGraphNodeGetDependentNodes_v2(
12766 node,
12767 std::ptr::null_mut(),
12768 std::ptr::null_mut(),
12769 &mut n,
12770 ),
12771 "fa-site GetDependentNodes(count)",
12772 )?;
12773 }
12774 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
12775 unsafe {
12776 cu_try(
12777 sys::cuGraphNodeGetDependentNodes_v2(
12778 node,
12779 v.as_mut_ptr(),
12780 std::ptr::null_mut(),
12781 &mut n,
12782 ),
12783 "fa-site GetDependentNodes",
12784 )?;
12785 }
12786 v.truncate(n);
12787 Ok(v)
12788 };
12789 let mut fa: Option<sys::CUgraphNode> = None;
12792 let mut last_memset: Option<sys::CUgraphNode> = None;
12793 for &ms in &memsets {
12794 for dep in dependents(ms)? {
12795 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12796 fa = Some(dep);
12797 last_memset = Some(ms);
12798 }
12799 }
12800 }
12801 let (Some(fa), Some(_last)) = (fa, last_memset) else {
12802 return Ok(None);
12803 };
12804 let mut combine: Option<sys::CUgraphNode> = None;
12805 for dep in dependents(fa)? {
12806 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12807 combine = Some(dep);
12808 }
12809 }
12810 let Some(combine) = combine else {
12811 return Ok(None);
12812 };
12813 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12816 unsafe {
12817 cu_try(
12818 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
12819 "fa-site KernelNodeGetParams",
12820 )?;
12821 }
12822 let arg_i32 =
12823 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
12824 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
12825 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
12827 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12828 unsafe {
12829 cu_try(
12830 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12831 "fa-site MemsetNodeGetParams",
12832 )?;
12833 }
12834 Ok(mp.width)
12835 };
12836 let mut widest = memsets[0];
12837 for &ms in &memsets[1..] {
12838 if width_of(ms)? > width_of(widest)? {
12839 widest = ms;
12840 }
12841 }
12842 let memset_m: Vec<sys::CUgraphNode> =
12843 memsets.iter().copied().filter(|&m| m != widest).collect();
12844 Ok(Some(TokenGraphFaSite {
12845 ctx,
12846 memset_o: widest,
12847 memset_m: [memset_m[0], memset_m[1]],
12848 fa,
12849 combine,
12850 window: win as usize,
12851 n_head: nh as usize,
12852 n_head_kv: nhkv as usize,
12853 head_dim: hd as usize,
12854 }))
12855}
12856
12857pub struct TokenGraph {
12858 exec: cudarc::driver::sys::CUgraphExec,
12859 parent: cudarc::driver::sys::CUgraph,
12860 _children: Vec<TokenGraphChild>,
12861 fa_sites: Vec<TokenGraphFaSite>,
12862}
12863
12864unsafe impl Send for TokenGraph {}
12865
12866impl TokenGraph {
12867 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
12872 use cudarc::driver::sys;
12873 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12874 if r == sys::CUresult::CUDA_SUCCESS {
12875 Ok(())
12876 } else {
12877 Err(format!("{what}: {r:?}").into())
12878 }
12879 }
12880 for site in &self.fa_sites {
12881 let layer_bucket = if site.window > 0 {
12882 bucket.min(site.window)
12883 } else {
12884 bucket
12885 };
12886 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
12887 let nsp = layer_bucket.div_ceil(sp).max(1);
12888 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12890 unsafe {
12891 cu_try(
12892 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
12893 "retarget fa GetParams",
12894 )?;
12895 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
12896 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
12897 params.gridDimY = nsp as u32;
12898 cu_try(
12899 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
12900 "retarget fa SetParams",
12901 )?;
12902 }
12903 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12905 unsafe {
12906 cu_try(
12907 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
12908 "retarget combine GetParams",
12909 )?;
12910 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
12911 cu_try(
12912 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
12913 "retarget combine SetParams",
12914 )?;
12915 }
12916 let set_width =
12918 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
12919 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12920 unsafe {
12921 cu_try(
12922 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12923 "retarget memset GetParams",
12924 )?;
12925 }
12926 mp.width = width;
12927 unsafe {
12928 cu_try(
12929 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
12930 "retarget memset SetParams",
12931 )?;
12932 }
12933 Ok(())
12934 };
12935 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
12936 set_width(site.memset_m[0], site.n_head * nsp)?;
12937 set_width(site.memset_m[1], site.n_head * nsp)?;
12938 }
12939 Ok(())
12940 }
12941
12942 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
12943 use cudarc::driver::sys;
12944 let _main = e.gpu.enter_main()?;
12945 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
12946 if r != sys::CUresult::CUDA_SUCCESS {
12947 return Err(format!("token graph launch: {r:?}").into());
12948 }
12949 Ok(())
12950 }
12951}
12952
12953impl Drop for TokenGraph {
12954 fn drop(&mut self) {
12955 unsafe {
12956 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
12957 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
12958 }
12959 }
12960}
12961
12962std::thread_local! {
12963 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
12964 const { std::cell::RefCell::new(None) };
12965}
12966
12967pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
12969 let builder = TokenGraphBuilder::new()?;
12970 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
12971 Ok(())
12972}
12973
12974pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
12976 let builder = TOKEN_GRAPH_BUILDER
12977 .with(|cell| cell.borrow_mut().take())
12978 .ok_or("token graph build was not begun")?;
12979 builder.finish()
12980}
12981
12982pub fn token_graph_building() -> bool {
12984 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
12985}
12986
12987pub fn graph_section<F>(
12992 engine: &Engine,
12993 parallel_group: Option<u32>,
12994 f: F,
12995) -> Result<(), Box<dyn std::error::Error>>
12996where
12997 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12998{
12999 graph_section_opts(engine, parallel_group, false, false, f)
13000}
13001
13002pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13004where
13005 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13006{
13007 graph_section_opts(engine, None, false, true, f)
13008}
13009
13010pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
13013where
13014 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13015{
13016 graph_section_opts(engine, None, true, false, f)
13017}
13018
13019pub fn graph_section_opts<F>(
13020 engine: &Engine,
13021 parallel_group: Option<u32>,
13022 detached: bool,
13023 absorb: bool,
13024 f: F,
13025) -> Result<(), Box<dyn std::error::Error>>
13026where
13027 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
13028{
13029 let building = token_graph_building();
13030 if !building {
13031 let mut f = f;
13032 return f();
13033 }
13034 let (child, ctx) = {
13035 let _main = engine.gpu.enter_main()?;
13036 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
13037 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
13038 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
13039 return Err(format!("graph section ctx query: {r:?}").into());
13040 }
13041 let mut f = f;
13042 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
13045 (child, ctx)
13046 };
13047 TOKEN_GRAPH_BUILDER.with(|cell| {
13048 cell.borrow_mut()
13049 .as_mut()
13050 .expect("builder checked above")
13051 .push_child(child, parallel_group, detached, absorb, ctx)
13052 })
13053}