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 sel_down8_on() -> bool {
228 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
229 *ON.get_or_init(|| std::env::var("MEMRA_SEL_DOWN8").as_deref() == Ok("1"))
230}
231
232pub(crate) fn oproj_direct_on() -> bool {
233 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
234 *ON.get_or_init(|| std::env::var("MEMRA_OPROJ_DIRECT").as_deref() == Ok("1"))
235}
236
237pub(crate) fn raw_copy_bytes(
238 dst: u64,
239 src: u64,
240 bytes: usize,
241 engine: &Engine,
242) -> Result<(), Box<dyn std::error::Error>> {
243 use cudarc::driver::sys;
244 let r = unsafe {
245 sys::cuMemcpyAsync(
246 dst as sys::CUdeviceptr,
247 src as sys::CUdeviceptr,
248 bytes,
249 engine.stream().cu_stream() as sys::CUstream,
250 )
251 };
252 if r == sys::CUresult::CUDA_SUCCESS {
253 Ok(())
254 } else {
255 Err(format!("raw_copy_bytes: {r:?}").into())
256 }
257}
258
259pub fn step_expert_activation_host(gate: f32, up: f32, limit: Option<f32>) -> f32 {
260 let silu = gate / (1.0 + (-gate).exp());
261 match limit {
262 Some(limit) => silu.min(limit) * up.clamp(-limit, limit),
263 None => silu * up,
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq)]
268struct ExpertOwnerRoutes {
269 rank: usize,
270 selected: Vec<usize>,
271 token_rows: Vec<usize>,
272 global_pairs: Vec<usize>,
273}
274
275fn partition_expert_owner_routes(
276 expert_count: usize,
277 ranks: usize,
278 tokens: usize,
279 experts_per_token: usize,
280 selected: &[usize],
281) -> Result<Vec<ExpertOwnerRoutes>, String> {
282 if expert_count == 0
283 || ranks == 0
284 || tokens == 0
285 || experts_per_token == 0
286 || expert_count % ranks != 0
287 {
288 return Err(format!(
289 "invalid expert-owner route geometry experts={expert_count} ranks={ranks} \
290 tokens={tokens} experts_per_token={experts_per_token}"
291 ));
292 }
293 let pairs = tokens
294 .checked_mul(experts_per_token)
295 .ok_or("expert-owner route count overflow")?;
296 if selected.len() != pairs {
297 return Err(format!(
298 "expert-owner routes {} != {tokens}x{experts_per_token} ({pairs})",
299 selected.len()
300 ));
301 }
302 let per_rank = expert_count / ranks;
303 let mut owners = (0..ranks)
304 .map(|rank| ExpertOwnerRoutes {
305 rank,
306 selected: Vec::new(),
307 token_rows: Vec::new(),
308 global_pairs: Vec::new(),
309 })
310 .collect::<Vec<_>>();
311 for (pair, &expert) in selected.iter().enumerate() {
312 if expert >= expert_count {
313 return Err(format!(
314 "expert-owner route {pair} selects expert {expert} outside 0..{expert_count}"
315 ));
316 }
317 let rank = expert / per_rank;
318 owners[rank].selected.push(expert - rank * per_rank);
319 owners[rank].token_rows.push(pair / experts_per_token);
320 owners[rank].global_pairs.push(pair);
321 }
322 Ok(owners)
323}
324
325fn validate_step_grouped_owner_routes(
326 expert_count: usize,
327 tokens: usize,
328 selected: &[usize],
329) -> Result<usize, String> {
330 if expert_count != STEP_GROUPED_FP8_EXPERTS || tokens == 0 {
331 return Err(format!(
332 "official Step owner-grouped FP8 requires {} experts and nonzero tokens, got \
333 experts={expert_count} tokens={tokens}",
334 STEP_GROUPED_FP8_EXPERTS
335 ));
336 }
337 let pairs = tokens
338 .checked_mul(STEP_GROUPED_FP8_TOP_K)
339 .ok_or("official Step owner-grouped FP8 route count overflow")?;
340 if selected.len() != pairs {
341 return Err(format!(
342 "official Step owner-grouped FP8 routes {} != {tokens}x{} ({pairs})",
343 selected.len(),
344 STEP_GROUPED_FP8_TOP_K,
345 ));
346 }
347 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
348 let mut unique = routes.to_vec();
349 unique.sort_unstable();
350 unique.dedup();
351 if unique.len() != STEP_GROUPED_FP8_TOP_K {
352 return Err(format!(
353 "official Step owner-grouped FP8 token {token} routes are not top-8 unique: \
354 {routes:?}"
355 ));
356 }
357 }
358 Ok(pairs)
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362struct WeightedRouteCombineShape {
363 pairs: usize,
364 max_pairs: usize,
365}
366
367fn validate_weighted_route_combine(
368 width: usize,
369 experts_per_token: usize,
370 max_tokens: usize,
371 tokens: usize,
372 owner_global_pairs: &[&[usize]],
373 route_weights: &[f32],
374) -> Result<WeightedRouteCombineShape, String> {
375 if width == 0
376 || experts_per_token == 0
377 || max_tokens == 0
378 || tokens == 0
379 || tokens > max_tokens
380 || width > i32::MAX as usize
381 || experts_per_token > i32::MAX as usize
382 || tokens > i32::MAX as usize
383 {
384 return Err(format!(
385 "invalid weighted route combine geometry width={width} experts_per_token=\
386 {experts_per_token} tokens={tokens}/{max_tokens}"
387 ));
388 }
389 let pairs = tokens
390 .checked_mul(experts_per_token)
391 .ok_or("weighted route combine pair count overflow")?;
392 let max_pairs = max_tokens
393 .checked_mul(experts_per_token)
394 .ok_or("weighted route combine capacity overflow")?;
395 if route_weights.len() != pairs || !route_weights.iter().all(|weight| weight.is_finite()) {
396 return Err(format!(
397 "weighted route combine weights {} != pairs {pairs} or contain a non-finite value",
398 route_weights.len()
399 ));
400 }
401 let mut seen = vec![false; pairs];
402 let mut observed = 0usize;
403 for pairs_for_owner in owner_global_pairs {
404 observed = observed
405 .checked_add(pairs_for_owner.len())
406 .ok_or("weighted route combine observed pair count overflow")?;
407 for &pair in *pairs_for_owner {
408 if pair >= pairs || std::mem::replace(&mut seen[pair], true) {
409 return Err(format!(
410 "weighted route combine pair {pair} is outside 0..{pairs} or duplicated"
411 ));
412 }
413 }
414 }
415 if observed != pairs || seen.iter().any(|present| !present) {
416 return Err(format!(
417 "weighted route combine owner schedules cover {observed} of {pairs} canonical pairs"
418 ));
419 }
420 Ok(WeightedRouteCombineShape { pairs, max_pairs })
421}
422
423fn cache_rank_rows(
424 rows: &[u8],
425 tokens: usize,
426 local_token_bytes: usize,
427 ranks: usize,
428 rank: usize,
429) -> Result<Vec<u8>, String> {
430 if ranks == 0 || rank >= ranks {
431 return Err(format!(
432 "TP cache rank {rank} is outside a {ranks}-rank layout"
433 ));
434 }
435 let global_token_bytes = local_token_bytes
436 .checked_mul(ranks)
437 .ok_or("TP cache global token-byte overflow")?;
438 let expected = tokens
439 .checked_mul(global_token_bytes)
440 .ok_or("TP cache row-byte overflow")?;
441 if rows.len() != expected {
442 return Err(format!(
443 "TP cache rows contain {} bytes, expected {tokens}x{global_token_bytes}={expected}",
444 rows.len()
445 ));
446 }
447 let mut shard = Vec::with_capacity(tokens * local_token_bytes);
448 for token in 0..tokens {
449 let start = token * global_token_bytes + rank * local_token_bytes;
450 shard.extend_from_slice(&rows[start..start + local_token_bytes]);
451 }
452 Ok(shard)
453}
454
455fn parse_step_tp_native_p2p(value: Option<&str>) -> Result<bool, String> {
456 match value {
457 None | Some("") | Some("0") => Ok(false),
458 Some("1") => Ok(true),
459 Some(value) => Err(format!(
460 "MEMRA_STEP_TP_NATIVE_P2P={value:?} is invalid; expected 0 or 1"
461 )),
462 }
463}
464
465pub fn step_tp_native_p2p_enabled() -> Result<bool, String> {
466 parse_step_tp_native_p2p(std::env::var("MEMRA_STEP_TP_NATIVE_P2P").ok().as_deref())
467}
468
469fn parse_step_tp_bulk_p2p(value: Option<&str>) -> Result<bool, String> {
470 match value {
471 None | Some("") | Some("0") => Ok(false),
472 Some("1") => Ok(true),
473 Some(value) => Err(format!(
474 "MEMRA_STEP_TP_BULK_P2P={value:?} is invalid; expected 0 or 1"
475 )),
476 }
477}
478
479pub fn step_tp_bulk_p2p_enabled() -> Result<bool, String> {
480 parse_step_tp_bulk_p2p(std::env::var("MEMRA_STEP_TP_BULK_P2P").ok().as_deref())
481}
482
483fn parse_step_ep_device_arithmetic(value: Option<&str>) -> Result<bool, String> {
484 match value {
485 None | Some("") | Some("0") => Ok(false),
486 Some("1") => Ok(true),
487 Some(value) => Err(format!(
488 "MEMRA_STEP_EP_DEVICE_ARITHMETIC={value:?} is invalid; expected 0 or 1"
489 )),
490 }
491}
492
493fn parse_step_nvfp4_dev_routes(value: Option<&str>) -> Result<bool, String> {
494 match value {
495 None | Some("") | Some("0") => Ok(false),
496 Some("1") => Ok(true),
497 Some(value) => Err(format!(
498 "MEMRA_STEP_NVFP4_DEV_ROUTES={value:?} is invalid; expected 0 or 1"
499 )),
500 }
501}
502
503pub fn step_nvfp4_dev_routes_enabled() -> Result<bool, String> {
506 parse_step_nvfp4_dev_routes(std::env::var("MEMRA_STEP_NVFP4_DEV_ROUTES").ok().as_deref())
507}
508
509pub fn step_ep_device_arithmetic_enabled() -> Result<bool, String> {
510 parse_step_ep_device_arithmetic(
511 std::env::var("MEMRA_STEP_EP_DEVICE_ARITHMETIC")
512 .ok()
513 .as_deref(),
514 )
515}
516
517fn parse_step_tp_f32_mirror(value: Option<&str>) -> Result<bool, String> {
518 match value {
519 None | Some("") | Some("0") => Ok(false),
520 Some("1") => Ok(true),
521 Some(value) => Err(format!(
522 "MEMRA_STEP_TP_F32_MIRROR={value:?} is invalid; expected 0 or 1"
523 )),
524 }
525}
526
527pub fn step_tp_f32_mirror_enabled() -> Result<bool, String> {
528 parse_step_tp_f32_mirror(std::env::var("MEMRA_STEP_TP_F32_MIRROR").ok().as_deref())
529}
530
531fn parse_step_tp_decode_v2(value: Option<&str>) -> Result<bool, String> {
532 match value {
533 None | Some("") | Some("0") => Ok(false),
534 Some("1") => Ok(true),
535 Some(value) => Err(format!(
536 "MEMRA_STEP_TP_DECODE_V2={value:?} is invalid; expected 0 or 1"
537 )),
538 }
539}
540
541pub fn step_tp_decode_v2_enabled() -> Result<bool, String> {
546 parse_step_tp_decode_v2(std::env::var("MEMRA_STEP_TP_DECODE_V2").ok().as_deref())
547}
548
549fn parse_step_tp_qkv_fused(value: Option<&str>) -> Result<bool, String> {
550 match value {
551 None | Some("") | Some("0") => Ok(false),
552 Some("1") => Ok(true),
553 Some(value) => Err(format!(
554 "MEMRA_STEP_TP_QKV_FUSED={value:?} is invalid; expected 0 or 1"
555 )),
556 }
557}
558
559fn parse_step_tp_dev_router(value: Option<&str>) -> Result<bool, String> {
560 match value {
561 None | Some("") | Some("0") => Ok(false),
562 Some("1") => Ok(true),
563 Some(value) => Err(format!(
564 "MEMRA_STEP_TP_DEV_ROUTER={value:?} is invalid; expected 0 or 1"
565 )),
566 }
567}
568
569pub fn step_tp_dev_router_enabled() -> Result<bool, String> {
573 parse_step_tp_dev_router(std::env::var("MEMRA_STEP_TP_DEV_ROUTER").ok().as_deref())
574}
575
576fn parse_step_tp_graph(value: Option<&str>) -> Result<bool, String> {
577 match value {
578 None | Some("") | Some("0") => Ok(false),
579 Some("1") => Ok(true),
580 Some(value) => Err(format!(
581 "MEMRA_STEP_TP_GRAPH={value:?} is invalid; expected 0 or 1"
582 )),
583 }
584}
585
586fn parse_step_tp_dcw(value: Option<&str>) -> Result<bool, String> {
587 match value {
588 None | Some("") | Some("0") => Ok(false),
589 Some("1") => Ok(true),
590 Some(value) => Err(format!(
591 "MEMRA_STEP_TP_DCW={value:?} is invalid; expected 0 or 1"
592 )),
593 }
594}
595
596pub fn step_tp_dcw_enabled() -> Result<bool, String> {
601 parse_step_tp_dcw(std::env::var("MEMRA_STEP_TP_DCW").ok().as_deref())
602}
603
604pub fn step_tp_graph_enabled() -> Result<bool, String> {
609 parse_step_tp_graph(std::env::var("MEMRA_STEP_TP_GRAPH").ok().as_deref())
610}
611
612pub fn step_tp_qkv_fused_enabled() -> Result<bool, String> {
616 parse_step_tp_qkv_fused(std::env::var("MEMRA_STEP_TP_QKV_FUSED").ok().as_deref())
617}
618
619#[derive(Debug, Clone, PartialEq, Eq)]
620pub struct StepEpLayerSpec {
621 pub layer: usize,
622 pub devices: Vec<usize>,
623}
624
625pub type StepTpLayerSpec = StepEpLayerSpec;
626
627fn parse_step_layer_specs(
628 flag: &str,
629 value: Option<&str>,
630 allow_full_model: bool,
631) -> Result<Vec<StepEpLayerSpec>, String> {
632 let Some(value) = value else {
633 return Ok(Vec::new());
634 };
635 if value.is_empty() || value == "0" {
636 return Ok(Vec::new());
637 }
638
639 let mut specs = Vec::new();
640 for item in value.split(';') {
641 let (layers, devices) = item.split_once('@').ok_or_else(|| {
642 let layers = if allow_full_model {
643 "LAYER[-LAYER] or all"
644 } else {
645 "LAYER[-LAYER]"
646 };
647 format!("{flag} must be {layers}@DEVICE,DEVICE[;...]")
648 })?;
649 let (first, last) = if layers == "all" {
650 if !allow_full_model {
651 return Err(format!(
652 "{flag} does not support the full-model shorthand; assign routed layers \
653 explicitly"
654 ));
655 }
656 (0, STEP37_TRUNK_LAYERS - 1)
657 } else {
658 match layers.split_once('-') {
659 Some((first, last)) => {
660 let first = first
661 .parse::<usize>()
662 .map_err(|_| format!("{flag} layer {first:?} is not an integer"))?;
663 let last = last
664 .parse::<usize>()
665 .map_err(|_| format!("{flag} layer {last:?} is not an integer"))?;
666 if first > last {
667 return Err(format!("{flag} layer range {first}-{last} is reversed"));
668 }
669 if last - first + 1 > 128 {
670 return Err(format!(
671 "{flag} layer range {first}-{last} exceeds the 128-layer parser cap"
672 ));
673 }
674 (first, last)
675 }
676 None => {
677 let layer = layers
678 .parse::<usize>()
679 .map_err(|_| format!("{flag} layer {layers:?} is not an integer"))?;
680 (layer, layer)
681 }
682 }
683 };
684 let devices = devices
685 .split(',')
686 .map(|device| {
687 device
688 .parse::<usize>()
689 .map_err(|_| format!("{flag} device {device:?} is not an integer"))
690 })
691 .collect::<Result<Vec<_>, _>>()?;
692 if !(2..=8).contains(&devices.len()) {
693 return Err(format!(
694 "{flag} requires 2..=8 devices, got {}",
695 devices.len()
696 ));
697 }
698 let mut unique = devices.clone();
699 unique.sort_unstable();
700 unique.dedup();
701 if unique.len() != devices.len() {
702 return Err(format!("{flag} devices must be distinct, got {devices:?}"));
703 }
704 for layer in first..=last {
705 if specs
706 .iter()
707 .any(|existing: &StepEpLayerSpec| existing.layer == layer)
708 {
709 return Err(format!("{flag} assigns layer {layer} more than once"));
710 }
711 specs.push(StepEpLayerSpec {
712 layer,
713 devices: devices.clone(),
714 });
715 }
716 }
717 Ok(specs)
718}
719
720pub fn parse_step_ep_layer_specs(value: Option<&str>) -> Result<Vec<StepEpLayerSpec>, String> {
721 parse_step_layer_specs("MEMRA_STEP_EP", value, false)
722}
723
724pub fn step_ep_layer_specs() -> Result<Vec<StepEpLayerSpec>, String> {
725 parse_step_ep_layer_specs(std::env::var("MEMRA_STEP_EP").ok().as_deref())
726}
727
728pub fn parse_step_tp_layer_specs(value: Option<&str>) -> Result<Vec<StepTpLayerSpec>, String> {
729 parse_step_layer_specs("MEMRA_STEP_TP", value, true)
730}
731
732pub fn step_tp_layer_specs() -> Result<Vec<StepTpLayerSpec>, String> {
733 parse_step_tp_layer_specs(std::env::var("MEMRA_STEP_TP").ok().as_deref())
734}
735
736#[derive(Clone, Copy)]
737pub struct E4m3BlockMatrix<'a> {
738 pub codes: &'a [u8],
739 pub scales: &'a [f32],
740 pub out_features: usize,
741 pub in_features: usize,
742}
743
744impl E4m3BlockMatrix<'_> {
745 fn validate(&self) -> Result<(), String> {
746 let code_count = self
747 .out_features
748 .checked_mul(self.in_features)
749 .ok_or_else(|| "E4M3 matrix size overflow".to_string())?;
750 if self.codes.len() != code_count {
751 return Err(format!(
752 "E4M3 code count {} != {}x{} ({code_count})",
753 self.codes.len(),
754 self.out_features,
755 self.in_features,
756 ));
757 }
758 let scale_count =
759 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
760 if self.scales.len() != scale_count {
761 return Err(format!(
762 "E4M3 scale count {} != {scale_count} for {}x{}",
763 self.scales.len(),
764 self.out_features,
765 self.in_features,
766 ));
767 }
768 if !self
769 .scales
770 .iter()
771 .all(|scale| scale.is_finite() && *scale > 0.0)
772 {
773 return Err("E4M3 scale grid contains a non-finite or non-positive value".to_string());
774 }
775 Ok(())
776 }
777}
778
779#[derive(Clone, Copy)]
780pub struct E4m3ExpertBank<'a> {
781 pub codes: &'a [u8],
782 pub scales: &'a [f32],
783 pub expert_count: usize,
784 pub out_features: usize,
785 pub in_features: usize,
786}
787
788impl E4m3ExpertBank<'_> {
789 fn validate(&self) -> Result<(), String> {
790 if self.expert_count == 0 {
791 return Err("E4M3 expert bank is empty".to_string());
792 }
793 let code_stride = self
794 .out_features
795 .checked_mul(self.in_features)
796 .ok_or_else(|| "E4M3 expert code stride overflow".to_string())?;
797 let code_count = self
798 .expert_count
799 .checked_mul(code_stride)
800 .ok_or_else(|| "E4M3 expert code count overflow".to_string())?;
801 if self.codes.len() != code_count {
802 return Err(format!(
803 "E4M3 expert code count {} != {}x{} ({code_count})",
804 self.codes.len(),
805 self.expert_count,
806 code_stride,
807 ));
808 }
809 let scale_stride =
810 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
811 let scale_count = self
812 .expert_count
813 .checked_mul(scale_stride)
814 .ok_or_else(|| "E4M3 expert scale count overflow".to_string())?;
815 if self.scales.len() != scale_count {
816 return Err(format!(
817 "E4M3 expert scale count {} != {}x{} ({scale_count})",
818 self.scales.len(),
819 self.expert_count,
820 scale_stride,
821 ));
822 }
823 if !self
824 .scales
825 .iter()
826 .all(|scale| scale.is_finite() && *scale > 0.0)
827 {
828 return Err(
829 "E4M3 expert scale grid contains a non-finite or non-positive value".to_string(),
830 );
831 }
832 Ok(())
833 }
834
835 pub fn expert(&self, expert: usize) -> Result<E4m3BlockMatrix<'_>, String> {
836 if expert >= self.expert_count {
837 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
838 }
839 let code_stride = self.out_features * self.in_features;
840 let scale_stride =
841 self.out_features.div_ceil(FP8_BLOCK) * self.in_features.div_ceil(FP8_BLOCK);
842 Ok(E4m3BlockMatrix {
843 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
844 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
845 out_features: self.out_features,
846 in_features: self.in_features,
847 })
848 }
849}
850
851pub struct ColumnParallelResult {
852 pub gathered: Vec<f32>,
853 pub rank_outputs: Vec<Vec<f32>>,
854}
855
856pub struct RowParallelResult {
857 pub reduced: Vec<f32>,
858 pub rank_partials: Vec<Vec<f32>>,
859}
860
861#[derive(Clone, Copy)]
862pub struct Bf16Matrix<'a> {
863 pub bytes: &'a [u8],
864 pub out_features: usize,
865 pub in_features: usize,
866}
867
868impl Bf16Matrix<'_> {
869 pub fn validate(&self) -> Result<(), String> {
870 if self.out_features == 0 || self.in_features == 0 {
871 return Err("BF16 matrix dimensions must be nonzero".into());
872 }
873 let expected = self
874 .out_features
875 .checked_mul(self.in_features)
876 .and_then(|values| values.checked_mul(2))
877 .ok_or("BF16 matrix byte count overflow")?;
878 if self.bytes.len() != expected {
879 return Err(format!(
880 "BF16 matrix bytes {} != {}x{}x2 ({expected})",
881 self.bytes.len(),
882 self.out_features,
883 self.in_features,
884 ));
885 }
886 Ok(())
887 }
888}
889
890struct ResidentE4m3Rank {
891 codes: CudaSlice<u8>,
892 scales: CudaSlice<f32>,
893 out_features: usize,
894 in_features: usize,
895}
896
897enum ResidentBf16Weight {
898 Bf16(CudaSlice<u8>),
899 F32(CudaSlice<f32>),
900}
901
902impl ResidentBf16Weight {
903 fn ordinal(&self) -> usize {
904 match self {
905 Self::Bf16(bytes) => bytes.ordinal(),
906 Self::F32(values) => values.ordinal(),
907 }
908 }
909}
910
911struct ResidentBf16Rank {
912 weight: ResidentBf16Weight,
913 out_features: usize,
914 in_features: usize,
915}
916
917pub struct ResidentColumnParallel {
918 ranks: Vec<ResidentE4m3Rank>,
919 out_features: usize,
920 in_features: usize,
921}
922
923pub struct ResidentRowParallel {
924 ranks: Vec<ResidentE4m3Rank>,
925 out_features: usize,
926 in_features: usize,
927}
928
929pub struct ResidentBf16ColumnParallel {
930 ranks: Vec<ResidentBf16Rank>,
931 out_features: usize,
932 in_features: usize,
933 canonical_chunk_rows: Option<usize>,
934}
935
936pub struct ResidentBf16RowParallel {
937 ranks: Vec<ResidentBf16Rank>,
938 out_features: usize,
939 in_features: usize,
940}
941
942pub struct ResidentStepBf16RowParallel {
943 ranks: Vec<Vec<ResidentBf16Rank>>,
944 out_features: usize,
945 in_features: usize,
946 canonical_chunk_cols: usize,
947}
948
949pub struct ResidentSigmoidTopKRouter {
951 weight: CudaSlice<f32>,
952 correction_bias: CudaSlice<f32>,
953 active: CudaSlice<u8>,
954 root_device: usize,
955 input_width: usize,
956 expert_count: usize,
957 experts_per_token: usize,
958 active_count: usize,
959 scaling_factor: f32,
960 route_norm: bool,
961}
962
963pub struct SigmoidTopKHostOutput {
964 pub logits: Vec<f32>,
965 pub selected: Vec<u32>,
966 pub weights: Vec<f32>,
967}
968
969pub struct ResidentReplicatedBf16SwiGlu {
971 gate: Vec<ResidentBf16Rank>,
972 up: Vec<ResidentBf16Rank>,
973 down: Vec<ResidentBf16Rank>,
974 input_width: usize,
975 intermediate_width: usize,
976}
977
978pub struct ResidentReplicatedDeviceRows {
983 ranks: Vec<CudaSlice<f32>>,
984 tokens: usize,
985 width: usize,
986}
987
988impl ResidentReplicatedDeviceRows {
989 pub fn tokens(&self) -> usize {
990 self.tokens
991 }
992
993 pub fn width(&self) -> usize {
994 self.width
995 }
996
997 pub fn ranks(&self) -> usize {
998 self.ranks.len()
999 }
1000}
1001
1002pub fn moe_residual_host(
1004 residual: &[f32],
1005 routed: &[f32],
1006 shared: &[f32],
1007) -> Result<Vec<f32>, String> {
1008 if residual.len() != routed.len() || residual.len() != shared.len() {
1009 return Err(format!(
1010 "MoE residual lengths residual={} routed={} shared={}",
1011 residual.len(),
1012 routed.len(),
1013 shared.len()
1014 ));
1015 }
1016 let ffn = routed
1017 .iter()
1018 .zip(shared)
1019 .map(|(&routed, &shared)| routed + shared)
1020 .collect::<Vec<_>>();
1021 Ok(residual
1022 .iter()
1023 .zip(ffn)
1024 .map(|(&residual, ffn)| residual + ffn)
1025 .collect())
1026}
1027
1028pub use memra_kv::{
1029 KvRingAppend, ResidentTpKvCache, ResidentTpKvCacheRank, TpKvAppendPlan, TpKvTransaction,
1030};
1031
1032pub struct ResidentTpExpert {
1038 gate: ResidentColumnParallel,
1039 up: ResidentColumnParallel,
1040 down: ResidentRowParallel,
1041 input_width: usize,
1042 expert_width: usize,
1043}
1044
1045struct ResidentE4m3ExpertBankRank {
1046 codes: CudaSlice<u8>,
1047 scales: CudaSlice<f32>,
1048 expert_range: Range<usize>,
1049 out_features: usize,
1050 in_features: usize,
1051 code_stride: usize,
1052 scale_stride: usize,
1053 k_blocks: Option<usize>,
1056}
1057
1058struct PackedE4m3ExpertBankRank {
1059 codes: Vec<u8>,
1060 scales: Vec<f32>,
1061 expert_range: Range<usize>,
1062 out_features: usize,
1063 in_features: usize,
1064 code_stride: usize,
1065 scale_stride: usize,
1066 k_blocks: Option<usize>,
1067}
1068
1069struct ResidentEpRank {
1070 gate: ResidentE4m3ExpertBankRank,
1071 up: ResidentE4m3ExpertBankRank,
1072 down: ResidentE4m3ExpertBankRank,
1073}
1074
1075pub struct ResidentExpertParallel {
1082 ranks: Vec<ResidentEpRank>,
1083 expert_count: usize,
1084 input_width: usize,
1085 expert_width: usize,
1086}
1087
1088pub struct StepGroupedFp8ProjectionOutput {
1093 pub gate: Vec<f32>,
1094 pub up: Vec<f32>,
1095 pub down: Vec<f32>,
1096}
1097
1098pub struct PreparedStepGroupedFp8Gate {
1103 device: usize,
1104 gate: ResidentE4m3ExpertBankRank,
1105 up: ResidentE4m3ExpertBankRank,
1106 down: ResidentE4m3ExpertBankRank,
1107 input: CudaSlice<f32>,
1108 route_csr: DeviceExpertCsr,
1109 down_csr: DeviceExpertCsr,
1110 gate_workspace: Fp8GroupedWorkspace,
1111 up_workspace: Fp8GroupedWorkspace,
1112 down_workspace: Fp8GroupedWorkspace,
1113 activation: CudaSlice<f32>,
1114 activation_limit: Option<f32>,
1115 tokens: usize,
1116 pairs: usize,
1117}
1118
1119impl PreparedStepGroupedFp8Gate {
1120 pub fn tokens(&self) -> usize {
1121 self.tokens
1122 }
1123
1124 pub fn pairs(&self) -> usize {
1125 self.pairs
1126 }
1127}
1128
1129struct PreparedStepGroupedExpertOwner {
1130 rank: usize,
1131 global_pairs: Vec<usize>,
1132 route_csr: DeviceExpertCsr,
1133 down_csr: DeviceExpertCsr,
1134 gate_workspace: Fp8GroupedWorkspace,
1135 up_workspace: Fp8GroupedWorkspace,
1136 down_workspace: Fp8GroupedWorkspace,
1137 activation: CudaSlice<f32>,
1138}
1139
1140struct StepGroupedExpertOwnerSchedule {
1141 global_pairs: Vec<usize>,
1142 route_csr: ExpertCsr,
1143 down_csr: ExpertCsr,
1144}
1145
1146pub struct PreparedStepGroupedExpertParallelGate {
1152 rank_inputs: Vec<CudaSlice<f32>>,
1153 owners: Vec<PreparedStepGroupedExpertOwner>,
1154 activation_limit: Option<f32>,
1155 tokens: usize,
1156 pairs: usize,
1157 max_tokens: usize,
1158 max_pairs: usize,
1159 input_width: usize,
1160 expert_width: usize,
1161 generation: u64,
1162 executed_generation: Option<u64>,
1163 ready: bool,
1164}
1165
1166impl PreparedStepGroupedExpertParallelGate {
1167 pub fn tokens(&self) -> usize {
1168 self.tokens
1169 }
1170
1171 pub fn pairs(&self) -> usize {
1172 self.pairs
1173 }
1174
1175 pub fn max_tokens(&self) -> usize {
1176 self.max_tokens
1177 }
1178
1179 pub fn input_width(&self) -> usize {
1180 self.input_width
1181 }
1182
1183 pub fn expert_width(&self) -> usize {
1184 self.expert_width
1185 }
1186
1187 pub fn set_activation_limit(&mut self, limit: Option<f32>) -> Result<(), String> {
1188 validate_step_expert_activation_limit(limit)?;
1189 self.activation_limit = limit;
1190 self.executed_generation = None;
1191 Ok(())
1192 }
1193
1194 pub fn active_owners(&self) -> usize {
1195 self.owners
1196 .iter()
1197 .filter(|owner| !owner.global_pairs.is_empty())
1198 .count()
1199 }
1200
1201 pub fn owner_pair_counts(&self) -> Vec<usize> {
1202 self.owners
1203 .iter()
1204 .map(|owner| owner.global_pairs.len())
1205 .collect()
1206 }
1207
1208 pub fn generation(&self) -> u64 {
1209 self.generation
1210 }
1211}
1212
1213struct PreparedPeerWeightedRouteOwner {
1214 token_rows: CudaSlice<i32>,
1215 slots: CudaSlice<i32>,
1216 weights: CudaSlice<f32>,
1217 active_pairs: usize,
1218}
1219
1220pub struct PreparedPeerWeightedRouteCombine {
1226 root_device: usize,
1227 owners: Vec<PreparedPeerWeightedRouteOwner>,
1228 peer_staging: CudaSlice<f32>,
1229 slots: CudaSlice<f32>,
1230 weights: CudaSlice<f32>,
1231 output: CudaSlice<f32>,
1232 peer_devices: Vec<usize>,
1233 peer_outputs: Vec<CudaSlice<f32>>,
1234 width: usize,
1235 experts_per_token: usize,
1236 max_tokens: usize,
1237 max_pairs: usize,
1238 tokens: usize,
1239 pairs: usize,
1240 projection_generation: u64,
1241 output_generation: Option<u64>,
1242 broadcast_generation: Option<u64>,
1243 ready: bool,
1244}
1245
1246impl PreparedPeerWeightedRouteCombine {
1247 pub fn tokens(&self) -> usize {
1248 self.tokens
1249 }
1250
1251 pub fn pairs(&self) -> usize {
1252 self.pairs
1253 }
1254
1255 pub fn owner_pair_counts(&self) -> Vec<usize> {
1256 self.owners.iter().map(|owner| owner.active_pairs).collect()
1257 }
1258
1259 pub fn distributed_ranks(&self) -> usize {
1260 1 + self.peer_outputs.len()
1261 }
1262}
1263
1264struct ResidentTpExpertBank {
1265 gate: Vec<ResidentE4m3ExpertBankRank>,
1266 up: Vec<ResidentE4m3ExpertBankRank>,
1267 down: Vec<ResidentE4m3ExpertBankRank>,
1268 expert_count: usize,
1269 input_width: usize,
1270 expert_width: usize,
1271}
1272
1273pub struct ResidentTensorParallel {
1279 bank: ResidentTpExpertBank,
1280}
1281
1282pub struct TpE4m3HostBounce {
1288 devices: Vec<usize>,
1289 ranks: Vec<Engine>,
1290 native_p2p: bool,
1291 ep_device_arithmetic: bool,
1292 bulk_p2p: bool,
1293 decode_v2: std::sync::Mutex<Vec<StepTpDecodeV2Ws>>,
1296}
1297
1298pub enum StepTpGateShards<'a> {
1307 F32(&'a [crate::CudaSlice<f32>]),
1308 Bf16(&'a [crate::CudaSlice<u8>]),
1309}
1310
1311pub struct StepTpDecodeV2Ws {
1312 pub(crate) tcol_q: Vec<CudaSlice<f32>>,
1316 pub(crate) tcol_k: Vec<CudaSlice<f32>>,
1317 pub(crate) tcol_v: Vec<CudaSlice<f32>>,
1318 pub(crate) tcol_g: Vec<CudaSlice<f32>>,
1319 pub(crate) tcol_in: Vec<CudaSlice<f32>>,
1320 pub(crate) tcol_cap: usize,
1321 pub(crate) fa2_q: Vec<CudaSlice<f32>>,
1328 pub(crate) fa2_gate: Vec<CudaSlice<f32>>,
1329 pub(crate) fa2_gated: Vec<CudaSlice<f32>>,
1330 pub(crate) fa2_cap: usize,
1331 tcol_gated: Vec<CudaSlice<f32>>,
1332 tcol_opart: Vec<CudaSlice<f32>>,
1333 tcol_opeer: Option<CudaSlice<f32>>,
1334 tcol_omix: Option<CudaSlice<f32>>,
1335 tcol_ocap: usize,
1336 pub(crate) q_raw: Vec<CudaSlice<f32>>,
1339 pub(crate) k_raw: Vec<CudaSlice<f32>>,
1340 pub(crate) v_raw: Vec<CudaSlice<f32>>,
1341 pub(crate) q: Vec<CudaSlice<f32>>,
1342 pub(crate) k: Vec<CudaSlice<f32>>,
1343 pub(crate) pos: Vec<CudaSlice<i32>>,
1344 pub(crate) fuse_ctr: Vec<CudaSlice<u32>>,
1346 pub(crate) gate: Vec<CudaSlice<f32>>,
1347 pub(crate) attn_out: Vec<CudaSlice<f32>>,
1348 pub(crate) gated: Vec<CudaSlice<f32>>,
1349 o_partials: Vec<Vec<CudaSlice<f32>>>,
1351 ev_rank: Vec<CudaEvent>,
1353 peer_partial: CudaSlice<f32>,
1355 reduce_a: CudaSlice<f32>,
1356 reduce_b: CudaSlice<f32>,
1357 zeros: CudaSlice<f32>,
1359 pub(crate) k_shadow: CudaSlice<f32>,
1360 pub(crate) v_shadow: CudaSlice<f32>,
1361 ev_refresh: CudaEvent,
1362 ev_oproj: CudaEvent,
1363 gate_e: CudaSlice<f32>,
1365 pub(crate) h_stage: Option<CudaSlice<f32>>,
1368 pub(crate) pos_stage: Option<CudaSlice<i32>>,
1369 attn_in: Vec<CudaSlice<f32>>,
1373 raw_h_stage: u64,
1375 raw_pos_stage: u64,
1376 raw_attn_in: Vec<u64>,
1377 raw_pos: Vec<u64>,
1378 raw_o_partial1: u64,
1379 raw_peer_partial: u64,
1380 raw_k1: u64,
1381 raw_v1: u64,
1382 raw_k_shadow: u64,
1383 raw_v_shadow: u64,
1384 raw_mixed_stage_e: u64,
1388 raw_reduce_a: u64,
1389 raw_shadow_stage_e: (u64, u64),
1390 ev_entry: CudaEvent,
1391 e_device: usize,
1392 local_q_dim: usize,
1394 local_kv_dim: usize,
1395 heads: usize,
1396 pub(crate) o_out: usize,
1397 o_block_cols: usize,
1398 blocks_per_rank: usize,
1399}
1400
1401impl TpE4m3HostBounce {
1402 pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1403 Self::new_inner(devices, false, false, false, false)
1404 }
1405
1406 pub fn new_native_p2p(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
1407 Self::new_inner(devices, false, true, false, false)
1408 }
1409
1410 pub fn new_native_p2p_device_arithmetic(
1411 devices: &[usize],
1412 ) -> Result<Self, Box<dyn std::error::Error>> {
1413 Self::new_inner(devices, false, true, true, false)
1414 }
1415
1416 pub(crate) fn new_configured(
1417 devices: &[usize],
1418 native_p2p: bool,
1419 ep_device_arithmetic: bool,
1420 bulk_p2p: bool,
1421 ) -> Result<Self, Box<dyn std::error::Error>> {
1422 Self::new_inner(devices, false, native_p2p, ep_device_arithmetic, bulk_p2p)
1423 }
1424
1425 pub fn new_single_rank_oracle(device: usize) -> Result<Self, Box<dyn std::error::Error>> {
1430 Self::new_inner(&[device], true, false, false, false)
1431 }
1432
1433 fn new_inner(
1434 devices: &[usize],
1435 allow_single_rank: bool,
1436 native_p2p: bool,
1437 ep_device_arithmetic: bool,
1438 bulk_p2p: bool,
1439 ) -> Result<Self, Box<dyn std::error::Error>> {
1440 if ep_device_arithmetic && !native_p2p {
1441 return Err("device-resident EP arithmetic requires native P2P".into());
1442 }
1443 if bulk_p2p && !native_p2p {
1444 return Err("bulk TP transport requires native P2P".into());
1445 }
1446 let minimum = if allow_single_rank { 1 } else { 2 };
1447 if !(minimum..=8).contains(&devices.len()) {
1448 return Err(format!(
1449 "TP reference requires {minimum}..=8 devices, got {}",
1450 devices.len()
1451 )
1452 .into());
1453 }
1454 let mut unique = devices.to_vec();
1455 unique.sort_unstable();
1456 unique.dedup();
1457 if unique.len() != devices.len() {
1458 return Err(format!("TP devices must be distinct, got {devices:?}").into());
1459 }
1460 let ranks = devices
1461 .iter()
1462 .map(|&device| Engine::new(device))
1463 .collect::<Result<Vec<_>, _>>()?;
1464 if native_p2p {
1465 configure_native_p2p(&ranks, devices)?;
1466 }
1467 if allow_single_rank {
1468 eprintln!(
1469 "[tp] canonical oracle transport=local device={} performance_claim=false",
1470 devices[0]
1471 );
1472 } else if native_p2p {
1473 if ep_device_arithmetic {
1474 eprintln!(
1475 "[tp] correctness transport=native-p2p devices={devices:?} \
1476 native_p2p=true activation=device-host-exact \
1477 accumulation=device-host-exact output=root-readback \
1478 bulk_p2p={bulk_p2p} performance_claim=false"
1479 );
1480 } else {
1481 eprintln!(
1482 "[tp] correctness transport=native-p2p devices={devices:?} \
1483 native_p2p=true activation=host-canonical bulk_p2p={bulk_p2p} \
1484 performance_claim=false"
1485 );
1486 }
1487 } else {
1488 eprintln!(
1489 "[tp] correctness transport=host-bounce devices={devices:?} \
1490 native_p2p=false performance_claim=false"
1491 );
1492 }
1493 Ok(Self {
1494 devices: devices.to_vec(),
1495 ranks,
1496 native_p2p,
1497 ep_device_arithmetic,
1498 bulk_p2p,
1499 decode_v2: std::sync::Mutex::new(Vec::new()),
1500 })
1501 }
1502
1503 pub fn devices(&self) -> &[usize] {
1504 &self.devices
1505 }
1506
1507 pub fn native_p2p(&self) -> bool {
1508 self.native_p2p
1509 }
1510
1511 pub fn bulk_p2p(&self) -> bool {
1512 self.bulk_p2p
1513 }
1514
1515 pub fn expert_activation_label(&self) -> &'static str {
1516 if self.ep_device_arithmetic {
1517 "device-host-exact"
1518 } else {
1519 "host-canonical"
1520 }
1521 }
1522
1523 pub fn expert_accumulation_label(&self) -> &'static str {
1524 self.expert_activation_label()
1525 }
1526
1527 pub fn expert_output_label(&self) -> &'static str {
1528 if self.ep_device_arithmetic {
1529 "root-readback"
1530 } else {
1531 "host-accumulated"
1532 }
1533 }
1534
1535 pub fn transport_label(&self) -> &'static str {
1536 if self.devices.len() == 1 {
1537 "local"
1538 } else if self.native_p2p {
1539 "native-p2p"
1540 } else {
1541 "host-bounce"
1542 }
1543 }
1544
1545 pub fn device_names(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
1546 self.ranks
1547 .iter()
1548 .map(|rank| rank.ctx().name().map_err(Into::into))
1549 .collect()
1550 }
1551
1552 pub fn rank_engine(&self, rank: usize) -> Option<&Engine> {
1558 self.ranks.get(rank)
1559 }
1560
1561 pub fn allocate_tp_kv_cache(
1562 &self,
1563 kv_dim_k: usize,
1564 kv_dim_v: usize,
1565 capacity: usize,
1566 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1567 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, None)
1568 }
1569
1570 pub fn allocate_tp_swa_kv_cache(
1571 &self,
1572 kv_dim_k: usize,
1573 kv_dim_v: usize,
1574 capacity: usize,
1575 window: usize,
1576 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1577 if window == 0 {
1578 return Err("TP SWA KV window must be nonzero".into());
1579 }
1580 self.allocate_tp_kv_cache_inner(kv_dim_k, kv_dim_v, capacity, Some(window))
1581 }
1582
1583 fn allocate_tp_kv_cache_inner(
1584 &self,
1585 kv_dim_k: usize,
1586 kv_dim_v: usize,
1587 capacity: usize,
1588 window: Option<usize>,
1589 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1590 if capacity == 0 || capacity > i32::MAX as usize {
1591 return Err(
1592 format!("TP KV capacity must be in 1..={}, got {capacity}", i32::MAX).into(),
1593 );
1594 }
1595 let tp = self.ranks.len();
1596 let shape = crate::cache::tp_kv_rank_allocation_shape(kv_dim_k, kv_dim_v, tp)?;
1597 let physical_rows = window
1598 .map(|window| crate::cache::swa_ring_rows(window, capacity))
1599 .unwrap_or(capacity);
1600 let k_plane_bytes = physical_rows
1601 .checked_mul(shape.k_token_bytes)
1602 .and_then(|bytes| bytes.checked_add(8))
1603 .ok_or("TP KV K plane-byte overflow")?;
1604 let v_plane_bytes = physical_rows
1605 .checked_mul(shape.v_token_bytes)
1606 .and_then(|bytes| bytes.checked_add(8))
1607 .ok_or("TP KV V plane-byte overflow")?;
1608 let mut ranks = Vec::with_capacity(tp);
1609 for engine in &self.ranks {
1610 let _main = engine.gpu.enter_main()?;
1611 ranks.push(ResidentTpKvCacheRank::new(
1612 engine.alloc_u8(k_plane_bytes)?,
1613 engine.alloc_u8(v_plane_bytes)?,
1614 engine.htod_i32(&[0])?,
1615 ));
1616 }
1617 Ok(match window {
1618 Some(window) => ResidentTpKvCache::new_swa(
1619 ranks,
1620 shape.kv_dim_k,
1621 shape.kv_dim_v,
1622 shape.k_token_bytes,
1623 shape.v_token_bytes,
1624 capacity,
1625 window,
1626 ),
1627 None => ResidentTpKvCache::new(
1628 ranks,
1629 shape.kv_dim_k,
1630 shape.kv_dim_v,
1631 shape.k_token_bytes,
1632 shape.v_token_bytes,
1633 capacity,
1634 ),
1635 })
1636 }
1637
1638 pub fn grow_tp_kv_cache(
1639 &self,
1640 source: &ResidentTpKvCache,
1641 target_capacity: usize,
1642 rows: usize,
1643 ) -> Result<ResidentTpKvCache, Box<dyn std::error::Error>> {
1644 self.validate_tp_kv_cache(source)?;
1645 let plan = source.prepare_grow(target_capacity, rows)?;
1646 let ranks = self.ranks.len();
1647 let global_k = source
1648 .kv_dim_k()
1649 .checked_mul(ranks)
1650 .ok_or("TP KV grow global K dimension overflow")?;
1651 let global_v = source
1652 .kv_dim_v()
1653 .checked_mul(ranks)
1654 .ok_or("TP KV grow global V dimension overflow")?;
1655 let mut target = match source.ring_window() {
1656 Some(window) => {
1657 self.allocate_tp_swa_kv_cache(global_k, global_v, target_capacity, window)?
1658 }
1659 None => self.allocate_tp_kv_cache(global_k, global_v, target_capacity)?,
1660 };
1661 self.validate_tp_kv_cache(&target)?;
1662
1663 for (rank, engine) in self.ranks.iter().enumerate() {
1664 let _main = engine.gpu.enter_main()?;
1665 let src = source
1666 .rank(rank)
1667 .ok_or_else(|| format!("TP KV grow source has no rank {rank}"))?;
1668 let dst = target
1669 .rank_mut(rank)
1670 .ok_or_else(|| format!("TP KV grow target has no rank {rank}"))?;
1671 if plan.k_bytes() > 0 {
1672 engine.copy_u8_range_into(
1673 dst.k_mut(),
1674 0,
1675 src.k(),
1676 plan.source_row() * source.k_tok_bytes(),
1677 plan.k_bytes(),
1678 )?;
1679 }
1680 if plan.v_bytes() > 0 {
1681 engine.copy_u8_range_into(
1682 dst.v_mut(),
1683 0,
1684 src.v(),
1685 plan.source_row() * source.v_tok_bytes(),
1686 plan.v_bytes(),
1687 )?;
1688 }
1689 }
1690 self.set_tp_kv_len_mirrors(&mut target, plan.rows())?;
1691
1692 for engine in &self.ranks {
1695 let _main = engine.gpu.enter_main()?;
1696 engine.stream().synchronize()?;
1697 }
1698 let physical_copy_rows = plan.copy_rows();
1699 target.publish_grow(plan)?;
1700 eprintln!(
1701 "[step-tp-kv-grow] rows={} source_capacity={} target_capacity={} ranks={} \
1702 physical_copy_rows={} ring_window={:?} copy=rank-local-dtod \
1703 rank_streams_synchronized=true generation_preserved=true",
1704 rows,
1705 source.capacity(),
1706 target_capacity,
1707 ranks,
1708 physical_copy_rows,
1709 source.ring_window(),
1710 );
1711 Ok(target)
1712 }
1713
1714 pub fn hydrate_tp_kv_cache(
1715 &self,
1716 cache: &mut ResidentTpKvCache,
1717 rows: usize,
1718 k_rows: &[u8],
1719 v_rows: &[u8],
1720 ) -> Result<(), Box<dyn std::error::Error>> {
1721 self.hydrate_tp_kv_cache_from(cache, rows, 0, k_rows, v_rows)
1722 }
1723
1724 pub fn hydrate_tp_kv_cache_from(
1725 &self,
1726 cache: &mut ResidentTpKvCache,
1727 logical_len: usize,
1728 resident_start: usize,
1729 k_rows: &[u8],
1730 v_rows: &[u8],
1731 ) -> Result<(), Box<dyn std::error::Error>> {
1732 self.validate_tp_kv_cache(cache)?;
1733 if cache.committed_len() != 0 || cache.staged_len() != 0 {
1734 return Err(format!(
1735 "TP KV hydration requires an empty cache, got committed/staged={}/{}",
1736 cache.committed_len(),
1737 cache.staged_len()
1738 )
1739 .into());
1740 }
1741 if resident_start > logical_len || logical_len > cache.capacity() {
1742 return Err(format!(
1743 "TP KV hydration range [{resident_start},{logical_len}) exceeds capacity {}",
1744 cache.capacity(),
1745 )
1746 .into());
1747 }
1748 let rows = logical_len - resident_start;
1749 if rows > cache.physical_capacity() {
1750 return Err(format!(
1751 "TP KV hydration rows {rows} exceed physical capacity {}",
1752 cache.physical_capacity()
1753 )
1754 .into());
1755 }
1756 for rank in 0..self.ranks.len() {
1757 let k_rank =
1758 cache_rank_rows(k_rows, rows, cache.k_tok_bytes(), self.ranks.len(), rank)?;
1759 let v_rank =
1760 cache_rank_rows(v_rows, rows, cache.v_tok_bytes(), self.ranks.len(), rank)?;
1761 let engine = &self.ranks[rank];
1762 let _main = engine.gpu.enter_main()?;
1763 let rank_cache = cache
1764 .rank_mut(rank)
1765 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1766 engine.htod_u8_into(rank_cache.k_mut(), 0, &k_rank)?;
1767 engine.htod_u8_into(rank_cache.v_mut(), 0, &v_rank)?;
1768 }
1769 cache.publish_hydration(logical_len, resident_start)?;
1770 Ok(())
1771 }
1772
1773 pub fn append_tp_kv_transaction(
1774 &self,
1775 cache: &mut ResidentTpKvCache,
1776 transaction: TpKvTransaction,
1777 k_shards: &[CudaSlice<f32>],
1778 v_shards: &[CudaSlice<f32>],
1779 rows: usize,
1780 ) -> Result<(), Box<dyn std::error::Error>> {
1781 self.append_tp_kv_transaction_inner(cache, transaction, k_shards, v_shards, rows, false)
1782 }
1783
1784 #[allow(clippy::too_many_arguments)]
1789 pub fn append_tp_kv_transaction_inner(
1790 &self,
1791 cache: &mut ResidentTpKvCache,
1792 transaction: TpKvTransaction,
1793 k_shards: &[CudaSlice<f32>],
1794 v_shards: &[CudaSlice<f32>],
1795 rows: usize,
1796 external_rank_appends: bool,
1797 ) -> Result<(), Box<dyn std::error::Error>> {
1798 self.validate_tp_kv_cache(cache)?;
1799 let plan = cache.prepare_append(transaction, rows)?;
1800 let target = plan.target();
1801 let expected_k = rows
1802 .checked_mul(cache.kv_dim_k())
1803 .ok_or("TP KV K append size overflow")?;
1804 let expected_v = rows
1805 .checked_mul(cache.kv_dim_v())
1806 .ok_or("TP KV V append size overflow")?;
1807 if !external_rank_appends
1810 && (k_shards.len() != self.ranks.len() || v_shards.len() != self.ranks.len())
1811 {
1812 return Err(format!(
1813 "TP KV append shard counts k={} v={} != ranks {}",
1814 k_shards.len(),
1815 v_shards.len(),
1816 self.ranks.len()
1817 )
1818 .into());
1819 }
1820 let kv_dim_k = cache.kv_dim_k();
1821 let kv_dim_v = cache.kv_dim_v();
1822 let k_tok_bytes = cache.k_tok_bytes();
1823 let v_tok_bytes = cache.v_tok_bytes();
1824 if let Some(KvRingAppend::Rebase {
1825 src_row,
1826 keep_rows,
1827 new_base,
1828 ..
1829 }) = plan.ring_append()
1830 {
1831 for rank in 0..self.ranks.len() {
1832 let engine = &self.ranks[rank];
1833 let _main = engine.gpu.enter_main()?;
1834 let rank_cache = cache
1835 .rank_mut(rank)
1836 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1837 if keep_rows > 0 {
1838 let k_len = keep_rows
1839 .checked_mul(k_tok_bytes)
1840 .ok_or("TP KV K rebase-byte overflow")?;
1841 let v_len = keep_rows
1842 .checked_mul(v_tok_bytes)
1843 .ok_or("TP KV V rebase-byte overflow")?;
1844 let mut k_tmp = engine.alloc_u8_uninit(k_len)?;
1845 let mut v_tmp = engine.alloc_u8_uninit(v_len)?;
1846 engine.copy_u8_range_into(
1847 &mut k_tmp,
1848 0,
1849 rank_cache.k(),
1850 src_row * k_tok_bytes,
1851 k_len,
1852 )?;
1853 engine.copy_u8_range_into(
1854 &mut v_tmp,
1855 0,
1856 rank_cache.v(),
1857 src_row * v_tok_bytes,
1858 v_len,
1859 )?;
1860 engine.copy_u8_into(rank_cache.k_mut(), 0, &k_tmp, k_len)?;
1861 engine.copy_u8_into(rank_cache.v_mut(), 0, &v_tmp, v_len)?;
1862 }
1863 if rank_cache.base_d().is_some() {
1867 let value = new_base as i32;
1868 let rank_cache = cache
1869 .rank_mut(rank)
1870 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1871 if let Some(base_d) = rank_cache.base_d_mut() {
1872 engine.set_i32_one(base_d, value)?;
1873 }
1874 }
1875 }
1876 }
1877 cache.publish_append_rebase(plan)?;
1878 let write_row = plan.write_row();
1879 for rank in 0..self.ranks.len() {
1880 if external_rank_appends {
1881 break;
1882 }
1883 let engine = &self.ranks[rank];
1884 let _main = engine.gpu.enter_main()?;
1885 if k_shards[rank].len() != expected_k
1886 || v_shards[rank].len() != expected_v
1887 || k_shards[rank].ordinal() != engine.ctx().ordinal()
1888 || v_shards[rank].ordinal() != engine.ctx().ordinal()
1889 {
1890 return Err(format!(
1891 "TP KV rank {rank} shard geometry/device k={}/{} v={}/{} \
1892 != expected {expected_k}/{expected_v} on device {}",
1893 k_shards[rank].len(),
1894 k_shards[rank].ordinal(),
1895 v_shards[rank].len(),
1896 v_shards[rank].ordinal(),
1897 engine.ctx().ordinal(),
1898 )
1899 .into());
1900 }
1901 let rank_cache = cache
1902 .rank_mut(rank)
1903 .ok_or_else(|| format!("TP KV cache has no rank {rank}"))?;
1904 let (rank_k, rank_v) = rank_cache.planes_mut();
1905 engine.append_kv_quantized_rows(
1906 &k_shards[rank],
1907 &v_shards[rank],
1908 rank_k,
1909 rank_v,
1910 write_row,
1911 rows,
1912 kv_dim_k,
1913 kv_dim_v,
1914 k_tok_bytes,
1915 v_tok_bytes,
1916 Engine::kv_fp8_on(),
1917 )?;
1918 }
1919 if !external_rank_appends {
1920 self.set_tp_kv_len_mirrors(cache, target)?;
1923 }
1924 cache.publish_append_plan(plan)?;
1925 Ok(())
1926 }
1927
1928 pub fn commit_tp_kv_transaction(
1929 &self,
1930 cache: &mut ResidentTpKvCache,
1931 transaction: TpKvTransaction,
1932 accepted_rows: usize,
1933 ) -> Result<(), Box<dyn std::error::Error>> {
1934 self.validate_tp_kv_cache(cache)?;
1935 let target = cache.commit_target(transaction, accepted_rows)?;
1936 self.set_tp_kv_len_mirrors(cache, target)?;
1937 cache.publish_finalize(transaction, target)?;
1938 Ok(())
1939 }
1940
1941 pub fn commit_tp_kv_transaction_external(
1947 &self,
1948 cache: &mut ResidentTpKvCache,
1949 transaction: TpKvTransaction,
1950 accepted_rows: usize,
1951 ) -> Result<(), Box<dyn std::error::Error>> {
1952 self.validate_tp_kv_cache(cache)?;
1953 let target = cache.commit_target(transaction, accepted_rows)?;
1954 cache.publish_finalize(transaction, target)?;
1955 Ok(())
1956 }
1957
1958 pub fn rollback_tp_kv_transaction(
1959 &self,
1960 cache: &mut ResidentTpKvCache,
1961 transaction: TpKvTransaction,
1962 ) -> Result<(), Box<dyn std::error::Error>> {
1963 self.validate_tp_kv_cache(cache)?;
1964 cache.validate_transaction(transaction)?;
1965 let target = transaction.base_len();
1966 self.set_tp_kv_len_mirrors(cache, target)?;
1967 cache.publish_finalize(transaction, target)?;
1968 Ok(())
1969 }
1970
1971 pub fn tp_kv_device_lengths(
1972 &self,
1973 cache: &ResidentTpKvCache,
1974 ) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
1975 self.validate_tp_kv_cache(cache)?;
1976 let mut lengths = Vec::with_capacity(self.ranks.len());
1977 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks()) {
1978 let _main = engine.gpu.enter_main()?;
1979 lengths.push(engine.dtoh_i32_one(rank_cache.len_d())?);
1980 }
1981 Ok(lengths)
1982 }
1983
1984 fn set_tp_kv_len_mirrors(
1985 &self,
1986 cache: &mut ResidentTpKvCache,
1987 len: usize,
1988 ) -> Result<(), Box<dyn std::error::Error>> {
1989 let len = i32::try_from(len).map_err(|_| "TP KV length exceeds i32 device mirror")?;
1990 for (engine, rank_cache) in self.ranks.iter().zip(cache.ranks_mut()) {
1991 let _main = engine.gpu.enter_main()?;
1992 engine.set_i32_one(rank_cache.len_d_mut(), len)?;
1993 }
1994 Ok(())
1995 }
1996
1997 fn validate_tp_kv_cache(
1998 &self,
1999 cache: &ResidentTpKvCache,
2000 ) -> Result<(), Box<dyn std::error::Error>> {
2001 if cache.ranks_len() != self.ranks.len() {
2002 return Err(format!(
2003 "TP KV cache ranks {} != runtime ranks {}",
2004 cache.ranks_len(),
2005 self.ranks.len()
2006 )
2007 .into());
2008 }
2009 let expected_k = cache
2010 .physical_capacity()
2011 .checked_mul(cache.k_tok_bytes())
2012 .and_then(|bytes| bytes.checked_add(8))
2013 .ok_or("TP KV K plane validation overflow")?;
2014 let expected_v = cache
2015 .physical_capacity()
2016 .checked_mul(cache.v_tok_bytes())
2017 .and_then(|bytes| bytes.checked_add(8))
2018 .ok_or("TP KV V plane validation overflow")?;
2019 for (rank, (engine, rank_cache)) in self.ranks.iter().zip(cache.ranks()).enumerate() {
2020 let device = engine.ctx().ordinal();
2021 if rank_cache.k().len() != expected_k
2022 || rank_cache.v().len() != expected_v
2023 || rank_cache.len_d().len() != 1
2024 || rank_cache.k().ordinal() != device
2025 || rank_cache.v().ordinal() != device
2026 || rank_cache.len_d().ordinal() != device
2027 {
2028 return Err(format!(
2029 "TP KV rank {rank} residency does not match device {device} or plane geometry"
2030 )
2031 .into());
2032 }
2033 }
2034 Ok(())
2035 }
2036
2037 pub fn full(
2038 &self,
2039 matrix: E4m3BlockMatrix<'_>,
2040 activations: &[f32],
2041 tokens: usize,
2042 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2043 matrix.validate()?;
2044 validate_activations(activations, tokens, matrix.in_features)?;
2045 run_rank(&self.ranks[0], matrix, activations, tokens)
2046 }
2047
2048 pub fn column_parallel(
2052 &self,
2053 matrix: E4m3BlockMatrix<'_>,
2054 activations: &[f32],
2055 tokens: usize,
2056 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2057 matrix.validate()?;
2058 validate_activations(activations, tokens, matrix.in_features)?;
2059 let tp = self.ranks.len();
2060 if matrix.out_features % tp != 0 {
2061 return Err(format!(
2062 "column-parallel out_features {} is not divisible by TP={tp}",
2063 matrix.out_features
2064 )
2065 .into());
2066 }
2067 let local_out = matrix.out_features / tp;
2068 if local_out % FP8_BLOCK != 0 {
2069 return Err(format!(
2070 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
2071 E4M3 scale block"
2072 )
2073 .into());
2074 }
2075
2076 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2077 let mut rank_outputs = Vec::with_capacity(tp);
2078 for (rank_index, rank) in self.ranks.iter().enumerate() {
2079 let shard = column_shard(matrix, tp, rank_index)?;
2080 let output = run_rank(rank, shard, activations, tokens)?;
2081 let row_start = rank_index * local_out;
2082 for token in 0..tokens {
2083 gathered[token * matrix.out_features + row_start
2084 ..token * matrix.out_features + row_start + local_out]
2085 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2086 }
2087 rank_outputs.push(output);
2088 }
2089 Ok(ColumnParallelResult {
2090 gathered,
2091 rank_outputs,
2092 })
2093 }
2094
2095 pub fn upload_column_parallel(
2096 &self,
2097 matrix: E4m3BlockMatrix<'_>,
2098 ) -> Result<ResidentColumnParallel, Box<dyn std::error::Error>> {
2099 matrix.validate()?;
2100 let tp = self.ranks.len();
2101 validate_column_shape(matrix, tp)?;
2102 let mut ranks = Vec::with_capacity(tp);
2103 for (rank_index, engine) in self.ranks.iter().enumerate() {
2104 ranks.push(upload_rank(engine, column_shard(matrix, tp, rank_index)?)?);
2105 }
2106 Ok(ResidentColumnParallel {
2107 ranks,
2108 out_features: matrix.out_features,
2109 in_features: matrix.in_features,
2110 })
2111 }
2112
2113 pub fn column_parallel_resident(
2114 &self,
2115 matrix: &ResidentColumnParallel,
2116 activations: &[f32],
2117 tokens: usize,
2118 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2119 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2120 validate_activations(activations, tokens, matrix.in_features)?;
2121 let local_out = matrix.out_features / self.ranks.len();
2122 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2123 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2124 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2125 let output = run_resident_rank(engine, shard, activations, tokens)?;
2126 let row_start = rank_index * local_out;
2127 for token in 0..tokens {
2128 gathered[token * matrix.out_features + row_start
2129 ..token * matrix.out_features + row_start + local_out]
2130 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
2131 }
2132 rank_outputs.push(output);
2133 }
2134 Ok(ColumnParallelResult {
2135 gathered,
2136 rank_outputs,
2137 })
2138 }
2139
2140 pub fn row_parallel(
2144 &self,
2145 matrix: E4m3BlockMatrix<'_>,
2146 activations: &[f32],
2147 tokens: usize,
2148 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2149 matrix.validate()?;
2150 validate_activations(activations, tokens, matrix.in_features)?;
2151 let tp = self.ranks.len();
2152 if matrix.in_features % tp != 0 {
2153 return Err(format!(
2154 "row-parallel in_features {} is not divisible by TP={tp}",
2155 matrix.in_features
2156 )
2157 .into());
2158 }
2159 let local_in = matrix.in_features / tp;
2160 if local_in % FP8_BLOCK != 0 {
2161 return Err(format!(
2162 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
2163 E4M3 scale block"
2164 )
2165 .into());
2166 }
2167
2168 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2169 let mut rank_partials = Vec::with_capacity(tp);
2170 for (rank_index, rank) in self.ranks.iter().enumerate() {
2171 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2172 let local_activations =
2173 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2174 let shard = E4m3BlockMatrix {
2175 codes: &codes,
2176 scales: &scales,
2177 out_features: matrix.out_features,
2178 in_features: local_in,
2179 };
2180 let partial = run_rank(rank, shard, &local_activations, tokens)?;
2181 for (sum, value) in reduced.iter_mut().zip(&partial) {
2182 *sum += *value;
2183 }
2184 rank_partials.push(partial);
2185 }
2186 Ok(RowParallelResult {
2187 reduced,
2188 rank_partials,
2189 })
2190 }
2191
2192 pub fn upload_row_parallel(
2193 &self,
2194 matrix: E4m3BlockMatrix<'_>,
2195 ) -> Result<ResidentRowParallel, Box<dyn std::error::Error>> {
2196 matrix.validate()?;
2197 let tp = self.ranks.len();
2198 validate_row_shape(matrix, tp)?;
2199 let local_in = matrix.in_features / tp;
2200 let mut ranks = Vec::with_capacity(tp);
2201 for (rank_index, engine) in self.ranks.iter().enumerate() {
2202 let (codes, scales) = row_shard(matrix, tp, rank_index)?;
2203 ranks.push(upload_rank(
2204 engine,
2205 E4m3BlockMatrix {
2206 codes: &codes,
2207 scales: &scales,
2208 out_features: matrix.out_features,
2209 in_features: local_in,
2210 },
2211 )?);
2212 }
2213 Ok(ResidentRowParallel {
2214 ranks,
2215 out_features: matrix.out_features,
2216 in_features: matrix.in_features,
2217 })
2218 }
2219
2220 pub fn row_parallel_resident(
2221 &self,
2222 matrix: &ResidentRowParallel,
2223 activations: &[f32],
2224 tokens: usize,
2225 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2226 validate_resident_ranks(&self.ranks, &matrix.ranks)?;
2227 validate_activations(activations, tokens, matrix.in_features)?;
2228 let tp = self.ranks.len();
2229 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2230 let mut rank_partials = Vec::with_capacity(tp);
2231 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2232 let local_activations =
2233 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
2234 let partial = run_resident_rank(engine, shard, &local_activations, tokens)?;
2235 for (sum, value) in reduced.iter_mut().zip(&partial) {
2236 *sum += *value;
2237 }
2238 rank_partials.push(partial);
2239 }
2240 Ok(RowParallelResult {
2241 reduced,
2242 rank_partials,
2243 })
2244 }
2245
2246 pub fn upload_bf16_column_parallel(
2247 &self,
2248 matrix: Bf16Matrix<'_>,
2249 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2250 self.upload_bf16_column_parallel_inner(matrix, None, false)
2251 }
2252
2253 pub fn upload_step_bf16_column_parallel(
2255 &self,
2256 matrix: Bf16Matrix<'_>,
2257 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2258 self.upload_step_bf16_column_parallel_inner(matrix, false)
2259 }
2260
2261 pub fn upload_step_bf16_column_parallel_f32_mirror(
2266 &self,
2267 matrix: Bf16Matrix<'_>,
2268 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2269 self.upload_step_bf16_column_parallel_inner(matrix, true)
2270 }
2271
2272 fn upload_step_bf16_column_parallel_inner(
2273 &self,
2274 matrix: Bf16Matrix<'_>,
2275 f32_mirror: bool,
2276 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2277 let canonical_chunk_rows =
2278 step_bf16_canonical_chunk_rows(matrix.out_features, self.ranks.len())?;
2279 self.upload_bf16_column_parallel_inner(matrix, Some(canonical_chunk_rows), f32_mirror)
2280 }
2281
2282 fn upload_bf16_column_parallel_inner(
2283 &self,
2284 matrix: Bf16Matrix<'_>,
2285 canonical_chunk_rows: Option<usize>,
2286 f32_mirror: bool,
2287 ) -> Result<ResidentBf16ColumnParallel, Box<dyn std::error::Error>> {
2288 matrix.validate()?;
2289 let tp = self.ranks.len();
2290 if matrix.out_features % tp != 0 {
2291 return Err(format!(
2292 "BF16 column-parallel out_features {} is not divisible by TP={tp}",
2293 matrix.out_features
2294 )
2295 .into());
2296 }
2297 let mut ranks = Vec::with_capacity(tp);
2298 for (rank, engine) in self.ranks.iter().enumerate() {
2299 ranks.push(upload_bf16_rank(
2300 engine,
2301 bf16_column_shard(matrix, tp, rank)?,
2302 f32_mirror,
2303 )?);
2304 }
2305 Ok(ResidentBf16ColumnParallel {
2306 ranks,
2307 out_features: matrix.out_features,
2308 in_features: matrix.in_features,
2309 canonical_chunk_rows,
2310 })
2311 }
2312
2313 pub fn bf16_column_parallel_resident(
2314 &self,
2315 matrix: &ResidentBf16ColumnParallel,
2316 activations: &[f32],
2317 tokens: usize,
2318 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
2319 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2320 validate_activations(activations, tokens, matrix.in_features)?;
2321 let local_out = matrix.out_features / self.ranks.len();
2322 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
2323 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2324 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2325 let output = run_resident_bf16_rank(
2326 engine,
2327 shard,
2328 activations,
2329 tokens,
2330 matrix.canonical_chunk_rows,
2331 )?;
2332 for token in 0..tokens {
2333 let src = &output[token * local_out..(token + 1) * local_out];
2334 let dst_start = token * matrix.out_features + rank * local_out;
2335 gathered[dst_start..dst_start + local_out].copy_from_slice(src);
2336 }
2337 rank_outputs.push(output);
2338 }
2339 Ok(ColumnParallelResult {
2340 gathered,
2341 rank_outputs,
2342 })
2343 }
2344
2345 pub fn bf16_column_parallel_resident_native(
2352 &self,
2353 matrix: &ResidentBf16ColumnParallel,
2354 activations: &[f32],
2355 tokens: usize,
2356 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
2357 let rank_outputs =
2358 self.bf16_column_parallel_resident_device_shards(matrix, activations, tokens)?;
2359 let local_out = matrix.out_features / self.ranks.len();
2360 self.gather_native_column_shards(&rank_outputs, tokens, local_out)
2361 }
2362
2363 pub fn bf16_column_parallel_resident_device_shards(
2370 &self,
2371 matrix: &ResidentBf16ColumnParallel,
2372 activations: &[f32],
2373 tokens: usize,
2374 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2375 if self.ranks.len() > 1 && !self.native_p2p {
2376 return Err("device-resident BF16 column parallelism requires native P2P ranks".into());
2377 }
2378 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2379 validate_activations(activations, tokens, matrix.in_features)?;
2380
2381 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
2382 let root_input = {
2383 let root = &self.ranks[0];
2384 let _main = root.gpu.enter_main()?;
2385 root.htod(activations)?
2386 };
2387 {
2393 let root = &self.ranks[0];
2394 let _main = root.gpu.enter_main()?;
2395 root.stream().synchronize()?;
2396 }
2397 rank_inputs.push(root_input);
2398 for engine in &self.ranks[1..] {
2399 let peer_input = {
2400 let _main = engine.gpu.enter_main()?;
2401 let mut peer_input = engine.uninit(activations.len())?;
2402 engine
2403 .stream()
2404 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
2405 peer_input
2406 };
2407 rank_inputs.push(peer_input);
2408 }
2409
2410 let mut rank_outputs = Vec::with_capacity(self.ranks.len());
2411 for rank in 0..self.ranks.len() {
2412 rank_outputs.push(run_resident_bf16_rank_device(
2413 &self.ranks[rank],
2414 &matrix.ranks[rank],
2415 &rank_inputs[rank],
2416 tokens,
2417 matrix.canonical_chunk_rows,
2418 self.bulk_p2p,
2419 )?);
2420 }
2421 Ok(rank_outputs)
2422 }
2423
2424 pub fn allocate_replicated_device_rows(
2428 &self,
2429 tokens: usize,
2430 width: usize,
2431 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2432 if self.ranks.len() > 1 && !self.native_p2p {
2433 return Err("replicated device rows require native P2P ranks".into());
2434 }
2435 let values = tokens
2436 .checked_mul(width)
2437 .ok_or("replicated device row size overflow")?;
2438 let rank_lengths = vec![values; self.ranks.len()];
2439 replicated_device_row_values(tokens, width, self.ranks.len(), &rank_lengths)?;
2440 let mut ranks = Vec::with_capacity(self.ranks.len());
2441 for engine in &self.ranks {
2442 let _main = engine.gpu.enter_main()?;
2443 ranks.push(engine.uninit(values)?);
2444 }
2445 Ok(ResidentReplicatedDeviceRows {
2446 ranks,
2447 tokens,
2448 width,
2449 })
2450 }
2451
2452 pub fn refresh_replicated_device_rows_from_root(
2454 &self,
2455 rows: &mut ResidentReplicatedDeviceRows,
2456 source: &CudaSlice<f32>,
2457 ) -> Result<(), Box<dyn std::error::Error>> {
2458 if self.ranks.len() > 1 && !self.native_p2p {
2459 return Err("replicated device rows require native P2P ranks".into());
2460 }
2461 validate_replicated_device_rows(&self.ranks, rows)?;
2462 let root = self
2463 .ranks
2464 .first()
2465 .ok_or("replicated rows have no root rank")?;
2466 let values = replicated_device_row_source_values(
2467 rows.tokens,
2468 rows.width,
2469 source.len(),
2470 source.ordinal(),
2471 root.ctx().ordinal(),
2472 )?;
2473 let (root_rows, peer_rows) = rows
2474 .ranks
2475 .split_first_mut()
2476 .ok_or("replicated rows have no root allocation")?;
2477 {
2478 let _main = root.gpu.enter_main()?;
2479 let mut destination = root_rows.slice_mut(0..values);
2480 root.stream()
2481 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
2482 root.stream().synchronize()?;
2483 }
2484 for (engine, peer_rows) in self.ranks.iter().skip(1).zip(peer_rows) {
2485 let _main = engine.gpu.enter_main()?;
2486 let mut destination = peer_rows.slice_mut(0..values);
2487 engine
2488 .stream()
2489 .memcpy_dtod(&root_rows.slice(0..values), &mut destination)?;
2490 }
2491 Ok(())
2492 }
2493
2494 pub fn upload_replicated_device_rows(
2496 &self,
2497 rows: &[f32],
2498 tokens: usize,
2499 width: usize,
2500 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2501 if self.ranks.len() > 1 && !self.native_p2p {
2502 return Err("replicated device rows require native P2P ranks".into());
2503 }
2504 validate_activations(rows, tokens, width)?;
2505 let root = self
2506 .ranks
2507 .first()
2508 .ok_or("replicated rows have no root rank")?;
2509 let root_rows = {
2510 let _main = root.gpu.enter_main()?;
2511 root.htod(rows)?
2512 };
2513 {
2514 let _main = root.gpu.enter_main()?;
2515 root.stream().synchronize()?;
2516 }
2517 let mut ranks = Vec::with_capacity(self.ranks.len());
2518 ranks.push(root_rows);
2519 for engine in self.ranks.iter().skip(1) {
2520 let _main = engine.gpu.enter_main()?;
2521 let mut peer_rows = engine.uninit(rows.len())?;
2522 engine.stream().memcpy_dtod(&ranks[0], &mut peer_rows)?;
2523 ranks.push(peer_rows);
2524 }
2525 Ok(ResidentReplicatedDeviceRows {
2526 ranks,
2527 tokens,
2528 width,
2529 })
2530 }
2531
2532 pub fn bf16_column_parallel_resident_replicated_device_shards(
2534 &self,
2535 matrix: &ResidentBf16ColumnParallel,
2536 activations: &ResidentReplicatedDeviceRows,
2537 ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
2538 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2539 validate_replicated_device_rows(&self.ranks, activations)?;
2540 if activations.width != matrix.in_features {
2541 return Err(format!(
2542 "replicated BF16 column input width {} != matrix width {}",
2543 activations.width, matrix.in_features
2544 )
2545 .into());
2546 }
2547 let mut outputs = Vec::with_capacity(self.ranks.len());
2548 for rank in 0..self.ranks.len() {
2549 outputs.push(run_resident_bf16_rank_device(
2550 &self.ranks[rank],
2551 &matrix.ranks[rank],
2552 &activations.ranks[rank],
2553 activations.tokens,
2554 matrix.canonical_chunk_rows,
2555 self.bulk_p2p,
2556 )?);
2557 }
2558 Ok(outputs)
2559 }
2560
2561 #[allow(clippy::too_many_arguments)]
2563 pub fn upload_sigmoid_topk_router(
2564 &self,
2565 weight: Bf16Matrix<'_>,
2566 correction_bias: &[f32],
2567 active: Option<&[bool]>,
2568 experts_per_token: usize,
2569 scaling_factor: f32,
2570 route_norm: bool,
2571 ) -> Result<ResidentSigmoidTopKRouter, Box<dyn std::error::Error>> {
2572 weight.validate()?;
2573 if correction_bias.len() != weight.out_features
2574 || experts_per_token == 0
2575 || experts_per_token > weight.out_features
2576 || !correction_bias.iter().all(|value| value.is_finite())
2577 || !scaling_factor.is_finite()
2578 || scaling_factor <= 0.0
2579 {
2580 return Err(format!(
2581 "sigmoid router geometry weight={}x{} bias={} top_k={} scale={scaling_factor}",
2582 weight.out_features,
2583 weight.in_features,
2584 correction_bias.len(),
2585 experts_per_token,
2586 )
2587 .into());
2588 }
2589 let active_row = active
2590 .map(|mask| {
2591 if mask.len() != weight.out_features {
2592 return Err(format!(
2593 "sigmoid router active mask {} != experts {}",
2594 mask.len(),
2595 weight.out_features
2596 ));
2597 }
2598 Ok(mask
2599 .iter()
2600 .map(|&enabled| u8::from(enabled))
2601 .collect::<Vec<_>>())
2602 })
2603 .transpose()?
2604 .unwrap_or_else(|| vec![1; weight.out_features]);
2605 let active_count = active_row.iter().filter(|&&enabled| enabled != 0).count();
2606 crate::sigrouter_contract::validate_active_count(experts_per_token, active_count)?;
2607
2608 let root = self
2609 .ranks
2610 .first()
2611 .ok_or("sigmoid router runtime has no root rank")?;
2612 let _main = root.gpu.enter_main()?;
2613 let bf16 = root.htod_bytes(weight.bytes)?;
2614 let weight_f32 = root.bf16_to_f32(
2615 &bf16.slice(0..bf16.len()),
2616 weight.out_features * weight.in_features,
2617 )?;
2618 Ok(ResidentSigmoidTopKRouter {
2619 weight: weight_f32,
2620 correction_bias: root.htod(correction_bias)?,
2621 active: root.htod_bytes(&active_row)?,
2622 root_device: root.ctx().ordinal(),
2623 input_width: weight.in_features,
2624 expert_count: weight.out_features,
2625 experts_per_token,
2626 active_count,
2627 scaling_factor,
2628 route_norm,
2629 })
2630 }
2631
2632 pub fn sigmoid_topk_replicated_device_rows_host(
2637 &self,
2638 router: &ResidentSigmoidTopKRouter,
2639 input: &ResidentReplicatedDeviceRows,
2640 ) -> Result<SigmoidTopKHostOutput, Box<dyn std::error::Error>> {
2641 validate_replicated_device_rows(&self.ranks, input)?;
2642 if input.width != router.input_width {
2643 return Err(format!(
2644 "sigmoid router input width {} != resident width {}",
2645 input.width, router.input_width
2646 )
2647 .into());
2648 }
2649 let root = self
2650 .ranks
2651 .first()
2652 .ok_or("sigmoid router runtime has no root rank")?;
2653 let _main = root.gpu.enter_main()?;
2654 if root.ctx().ordinal() != router.root_device
2655 || router.weight.ordinal() != router.root_device
2656 || router.correction_bias.ordinal() != router.root_device
2657 || router.active.ordinal() != router.root_device
2658 {
2659 return Err("sigmoid router root residency changed".into());
2660 }
2661 let logits = root.router_gemv(
2662 &router.weight,
2663 &input.ranks[0],
2664 router.input_width,
2665 router.expert_count,
2666 input.tokens,
2667 )?;
2668 let (selected, weights) = root.moe_router_sigmoid_topk_host(
2669 &logits,
2670 input.tokens,
2671 router.expert_count,
2672 router.experts_per_token,
2673 router.active_count,
2674 &router.correction_bias,
2675 &router.active,
2676 router.scaling_factor,
2677 router.route_norm,
2678 )?;
2679 Ok(SigmoidTopKHostOutput {
2680 logits: root.dtoh(&logits)?,
2681 selected,
2682 weights,
2683 })
2684 }
2685
2686 pub fn upload_replicated_bf16_swiglu(
2688 &self,
2689 gate: Bf16Matrix<'_>,
2690 up: Bf16Matrix<'_>,
2691 down: Bf16Matrix<'_>,
2692 ) -> Result<ResidentReplicatedBf16SwiGlu, Box<dyn std::error::Error>> {
2693 gate.validate()?;
2694 up.validate()?;
2695 down.validate()?;
2696 if gate.in_features != up.in_features
2697 || gate.out_features != up.out_features
2698 || down.in_features != gate.out_features
2699 || down.out_features != gate.in_features
2700 {
2701 return Err(format!(
2702 "replicated BF16 SwiGLU geometry gate={}x{} up={}x{} down={}x{}",
2703 gate.out_features,
2704 gate.in_features,
2705 up.out_features,
2706 up.in_features,
2707 down.out_features,
2708 down.in_features,
2709 )
2710 .into());
2711 }
2712 let mut gate_ranks = Vec::with_capacity(self.ranks.len());
2713 let mut up_ranks = Vec::with_capacity(self.ranks.len());
2714 let mut down_ranks = Vec::with_capacity(self.ranks.len());
2715 for engine in &self.ranks {
2716 gate_ranks.push(upload_bf16_rank(engine, gate, false)?);
2717 up_ranks.push(upload_bf16_rank(engine, up, false)?);
2718 down_ranks.push(upload_bf16_rank(engine, down, false)?);
2719 }
2720 Ok(ResidentReplicatedBf16SwiGlu {
2721 gate: gate_ranks,
2722 up: up_ranks,
2723 down: down_ranks,
2724 input_width: gate.in_features,
2725 intermediate_width: gate.out_features,
2726 })
2727 }
2728
2729 pub fn replicated_bf16_swiglu_resident_device(
2731 &self,
2732 mlp: &ResidentReplicatedBf16SwiGlu,
2733 input: &ResidentReplicatedDeviceRows,
2734 activation_limit: Option<f32>,
2735 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2736 validate_step_expert_activation_limit(activation_limit)?;
2737 validate_replicated_device_rows(&self.ranks, input)?;
2738 validate_resident_bf16_ranks(&self.ranks, &mlp.gate)?;
2739 validate_resident_bf16_ranks(&self.ranks, &mlp.up)?;
2740 validate_resident_bf16_ranks(&self.ranks, &mlp.down)?;
2741 if input.width != mlp.input_width
2742 || mlp.gate.len() != self.ranks.len()
2743 || mlp.up.len() != self.ranks.len()
2744 || mlp.down.len() != self.ranks.len()
2745 {
2746 return Err("replicated BF16 SwiGLU residency or input width changed".into());
2747 }
2748
2749 let mut outputs = Vec::with_capacity(self.ranks.len());
2750 for rank in 0..self.ranks.len() {
2751 let engine = &self.ranks[rank];
2752 let gate = run_resident_bf16_rank_device(
2753 engine,
2754 &mlp.gate[rank],
2755 &input.ranks[rank],
2756 input.tokens,
2757 None,
2758 self.bulk_p2p,
2759 )?;
2760 let up = run_resident_bf16_rank_device(
2761 engine,
2762 &mlp.up[rank],
2763 &input.ranks[rank],
2764 input.tokens,
2765 None,
2766 self.bulk_p2p,
2767 )?;
2768 let _main = engine.gpu.enter_main()?;
2769 let values = input
2770 .tokens
2771 .checked_mul(mlp.intermediate_width)
2772 .ok_or("replicated BF16 SwiGLU activation size overflow")?;
2773 let mut activation = engine.uninit(values)?;
2774 if let Some(limit) = activation_limit {
2775 engine.silu_clamped_mul_host_expf(&gate, &up, limit, &mut activation, values)?;
2776 } else {
2777 engine.silu_mul_host_expf(&gate, &up, &mut activation, values)?;
2778 }
2779 outputs.push(run_resident_bf16_rank_device(
2780 engine,
2781 &mlp.down[rank],
2782 &activation,
2783 input.tokens,
2784 None,
2785 self.bulk_p2p,
2786 )?);
2787 }
2788 Ok(ResidentReplicatedDeviceRows {
2789 ranks: outputs,
2790 tokens: input.tokens,
2791 width: mlp.input_width,
2792 })
2793 }
2794
2795 pub fn rms_norm_replicated_device_rows(
2797 &self,
2798 input: &ResidentReplicatedDeviceRows,
2799 weight: &[f32],
2800 eps: f32,
2801 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
2802 validate_replicated_device_rows(&self.ranks, input)?;
2803 if weight.len() != input.width || !eps.is_finite() || eps <= 0.0 {
2804 return Err(format!(
2805 "replicated RMS norm weight/eps {}/{} != width {}",
2806 weight.len(),
2807 eps,
2808 input.width
2809 )
2810 .into());
2811 }
2812 let mut ranks = Vec::with_capacity(self.ranks.len());
2813 for (rank, engine) in self.ranks.iter().enumerate() {
2814 let _main = engine.gpu.enter_main()?;
2815 let weight = engine.htod(weight)?;
2816 let mut output = engine.uninit(input.tokens * input.width)?;
2817 engine.rms_norm(
2818 &input.ranks[rank],
2819 &weight,
2820 &mut output,
2821 input.width,
2822 input.tokens,
2823 eps,
2824 )?;
2825 ranks.push(output);
2826 }
2827 Ok(ResidentReplicatedDeviceRows {
2828 ranks,
2829 tokens: input.tokens,
2830 width: input.width,
2831 })
2832 }
2833
2834 pub fn add_rms_norm_replicated_device_rows(
2836 &self,
2837 input: &ResidentReplicatedDeviceRows,
2838 update: &ResidentReplicatedDeviceRows,
2839 weight: &[f32],
2840 eps: f32,
2841 ) -> Result<
2842 (ResidentReplicatedDeviceRows, ResidentReplicatedDeviceRows),
2843 Box<dyn std::error::Error>,
2844 > {
2845 validate_replicated_device_rows(&self.ranks, input)?;
2846 validate_replicated_device_rows(&self.ranks, update)?;
2847 if input.tokens != update.tokens
2848 || input.width != update.width
2849 || weight.len() != input.width
2850 || !eps.is_finite()
2851 || eps <= 0.0
2852 {
2853 return Err(format!(
2854 "replicated add/RMS geometry input={}x{} update={}x{} weight={} eps={eps}",
2855 input.tokens,
2856 input.width,
2857 update.tokens,
2858 update.width,
2859 weight.len(),
2860 )
2861 .into());
2862 }
2863 let values = input.tokens * input.width;
2864 let mut residual_ranks = Vec::with_capacity(self.ranks.len());
2865 let mut normalized_ranks = Vec::with_capacity(self.ranks.len());
2866 for (rank, engine) in self.ranks.iter().enumerate() {
2867 let _main = engine.gpu.enter_main()?;
2868 let weight = engine.htod(weight)?;
2869 let mut residual = engine.uninit(values)?;
2870 let mut normalized = engine.uninit(values)?;
2871 engine.add_rms_norm(
2872 &input.ranks[rank],
2873 &update.ranks[rank],
2874 &weight,
2875 &mut residual,
2876 &mut normalized,
2877 input.width,
2878 input.tokens,
2879 eps,
2880 )?;
2881 residual_ranks.push(residual);
2882 normalized_ranks.push(normalized);
2883 }
2884 Ok((
2885 ResidentReplicatedDeviceRows {
2886 ranks: residual_ranks,
2887 tokens: input.tokens,
2888 width: input.width,
2889 },
2890 ResidentReplicatedDeviceRows {
2891 ranks: normalized_ranks,
2892 tokens: input.tokens,
2893 width: input.width,
2894 },
2895 ))
2896 }
2897
2898 pub fn collect_replicated_device_rows(
2899 &self,
2900 rows: &ResidentReplicatedDeviceRows,
2901 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
2902 validate_replicated_device_rows(&self.ranks, rows)?;
2903 let mut outputs = Vec::with_capacity(self.ranks.len());
2904 for (rank, engine) in self.ranks.iter().enumerate() {
2905 let _main = engine.gpu.enter_main()?;
2906 outputs.push(engine.dtoh(&rows.ranks[rank])?);
2907 }
2908 Ok(outputs)
2909 }
2910
2911 pub fn upload_bf16_row_parallel(
2912 &self,
2913 matrix: Bf16Matrix<'_>,
2914 ) -> Result<ResidentBf16RowParallel, Box<dyn std::error::Error>> {
2915 matrix.validate()?;
2916 let tp = self.ranks.len();
2917 if matrix.in_features % tp != 0 {
2918 return Err(format!(
2919 "BF16 row-parallel in_features {} is not divisible by TP={tp}",
2920 matrix.in_features
2921 )
2922 .into());
2923 }
2924 let mut ranks = Vec::with_capacity(tp);
2925 for (rank, engine) in self.ranks.iter().enumerate() {
2926 let shard = bf16_row_shard(matrix, tp, rank)?;
2927 ranks.push(upload_bf16_rank(
2928 engine,
2929 Bf16Matrix {
2930 bytes: &shard,
2931 out_features: matrix.out_features,
2932 in_features: matrix.in_features / tp,
2933 },
2934 false,
2935 )?);
2936 }
2937 Ok(ResidentBf16RowParallel {
2938 ranks,
2939 out_features: matrix.out_features,
2940 in_features: matrix.in_features,
2941 })
2942 }
2943
2944 pub fn bf16_row_parallel_resident(
2945 &self,
2946 matrix: &ResidentBf16RowParallel,
2947 activations: &[f32],
2948 tokens: usize,
2949 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
2950 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
2951 validate_activations(activations, tokens, matrix.in_features)?;
2952 let tp = self.ranks.len();
2953 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
2954 let mut rank_partials = Vec::with_capacity(tp);
2955 for (rank, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
2956 let local_activations =
2957 activation_shard(activations, tokens, matrix.in_features, tp, rank);
2958 let partial = run_resident_bf16_rank(engine, shard, &local_activations, tokens, None)?;
2959 for (sum, value) in reduced.iter_mut().zip(&partial) {
2960 *sum += value;
2961 }
2962 rank_partials.push(partial);
2963 }
2964 Ok(RowParallelResult {
2965 reduced,
2966 rank_partials,
2967 })
2968 }
2969
2970 pub fn upload_step_bf16_row_parallel(
2972 &self,
2973 matrix: Bf16Matrix<'_>,
2974 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
2975 self.upload_step_bf16_row_parallel_inner(matrix, false)
2976 }
2977
2978 pub fn upload_step_bf16_row_parallel_f32_mirror(
2979 &self,
2980 matrix: Bf16Matrix<'_>,
2981 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
2982 self.upload_step_bf16_row_parallel_inner(matrix, true)
2983 }
2984
2985 fn upload_step_bf16_row_parallel_inner(
2986 &self,
2987 matrix: Bf16Matrix<'_>,
2988 f32_mirror: bool,
2989 ) -> Result<ResidentStepBf16RowParallel, Box<dyn std::error::Error>> {
2990 matrix.validate()?;
2991 let tp = self.ranks.len();
2992 let canonical_chunk_cols = step_bf16_canonical_chunk_cols(matrix.in_features, tp)?;
2993 let local_in = matrix.in_features / tp;
2994 let blocks_per_rank = local_in / canonical_chunk_cols;
2995 let mut ranks = Vec::with_capacity(tp);
2996 for (rank, engine) in self.ranks.iter().enumerate() {
2997 let mut blocks = Vec::with_capacity(blocks_per_rank);
2998 for block in 0..blocks_per_rank {
2999 let global_block = rank * blocks_per_rank + block;
3000 let col_start = global_block * canonical_chunk_cols;
3001 let bytes = bf16_row_block(matrix, col_start, canonical_chunk_cols)?;
3002 blocks.push(upload_bf16_rank(
3003 engine,
3004 Bf16Matrix {
3005 bytes: &bytes,
3006 out_features: matrix.out_features,
3007 in_features: canonical_chunk_cols,
3008 },
3009 f32_mirror,
3010 )?);
3011 }
3012 ranks.push(blocks);
3013 }
3014 Ok(ResidentStepBf16RowParallel {
3015 ranks,
3016 out_features: matrix.out_features,
3017 in_features: matrix.in_features,
3018 canonical_chunk_cols,
3019 })
3020 }
3021
3022 pub fn step_bf16_row_parallel_resident(
3027 &self,
3028 matrix: &ResidentStepBf16RowParallel,
3029 activations: &[f32],
3030 tokens: usize,
3031 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3032 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3033 validate_activations(activations, tokens, matrix.in_features)?;
3034 let root = &self.ranks[0];
3035 let output_len = tokens
3036 .checked_mul(matrix.out_features)
3037 .ok_or("Step BF16 row output size overflow")?;
3038 let mut reduced = {
3039 let _main = root.gpu.enter_main()?;
3040 root.htod(&vec![0.0f32; output_len])?
3041 };
3042 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3043 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3044 for (block, resident) in blocks.iter().enumerate() {
3045 let global_block = rank * blocks_per_rank + block;
3046 let input = activation_shard(
3047 activations,
3048 tokens,
3049 matrix.in_features,
3050 PRODUCT_MAX_CARDS,
3051 global_block,
3052 );
3053 let partial =
3054 run_resident_bf16_rank(&self.ranks[rank], resident, &input, tokens, None)?;
3055 let next = {
3056 let _main = root.gpu.enter_main()?;
3057 let partial = root.htod(&partial)?;
3058 let mut next = root.uninit(output_len)?;
3059 root.add(&reduced, &partial, &mut next, output_len)?;
3060 next
3061 };
3062 reduced = next;
3063 }
3064 }
3065 let _main = root.gpu.enter_main()?;
3066 root.dtoh(&reduced)
3067 }
3068
3069 pub fn step_bf16_row_parallel_resident_native(
3075 &self,
3076 matrix: &ResidentStepBf16RowParallel,
3077 activations: &[f32],
3078 tokens: usize,
3079 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3080 if self.ranks.len() > 1 && !self.native_p2p {
3081 return Err("native Step BF16 row parallelism requires P2P ranks".into());
3082 }
3083 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3084 validate_activations(activations, tokens, matrix.in_features)?;
3085 let root = &self.ranks[0];
3086 let root_input = {
3087 let _main = root.gpu.enter_main()?;
3088 root.htod(activations)?
3089 };
3090 let output_len = tokens
3091 .checked_mul(matrix.out_features)
3092 .ok_or("native Step BF16 row output size overflow")?;
3093 let mut reduced = {
3094 let _main = root.gpu.enter_main()?;
3095 root.htod(&vec![0.0f32; output_len])?
3096 };
3097 {
3100 let _main = root.gpu.enter_main()?;
3101 root.stream().synchronize()?;
3102 }
3103 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3104 let mut block_input_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3105 let mut root_packed_keepalive = Vec::with_capacity(PRODUCT_MAX_CARDS);
3106 let mut remote_partial_keepalive = Vec::new();
3107 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3108 for (block, resident) in blocks.iter().enumerate() {
3109 let global_block = rank * blocks_per_rank + block;
3110 let col_start = global_block * matrix.canonical_chunk_cols;
3111 let block_len = tokens
3112 .checked_mul(matrix.canonical_chunk_cols)
3113 .ok_or("native Step BF16 row block size overflow")?;
3114 let block_input = if self.bulk_p2p {
3115 let root_packed = {
3116 let _main = root.gpu.enter_main()?;
3117 let mut root_packed = root.uninit(block_len)?;
3118 root.copy_rows_strided(
3119 &root_input,
3120 &mut root_packed,
3121 matrix.canonical_chunk_cols,
3122 tokens,
3123 matrix.in_features,
3124 col_start,
3125 )?;
3126 root_packed
3127 };
3128 if rank == 0 {
3129 root_packed
3130 } else {
3131 {
3134 let _main = root.gpu.enter_main()?;
3135 root.stream().synchronize()?;
3136 }
3137 let engine = &self.ranks[rank];
3138 let _main = engine.gpu.enter_main()?;
3139 let mut block_input = engine.uninit(block_len)?;
3140 engine
3141 .stream()
3142 .memcpy_dtod(&root_packed, &mut block_input)?;
3143 root_packed_keepalive.push(root_packed);
3144 block_input
3145 }
3146 } else {
3147 let engine = &self.ranks[rank];
3148 let _main = engine.gpu.enter_main()?;
3149 let mut block_input = engine.uninit(block_len)?;
3150 for token in 0..tokens {
3151 let source_start = token * matrix.in_features + col_start;
3152 let source = root_input
3153 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3154 let destination_start = token * matrix.canonical_chunk_cols;
3155 let mut destination = block_input.slice_mut(
3156 destination_start..destination_start + matrix.canonical_chunk_cols,
3157 );
3158 engine.stream().memcpy_dtod(&source, &mut destination)?;
3159 }
3160 block_input
3161 };
3162 let partial = run_resident_bf16_rank_device(
3163 &self.ranks[rank],
3164 resident,
3165 &block_input,
3166 tokens,
3167 None,
3168 self.bulk_p2p,
3169 )?;
3170 block_input_keepalive.push(block_input);
3171 let root_partial = if rank == 0 {
3172 partial
3173 } else {
3174 {
3177 let engine = &self.ranks[rank];
3178 let _main = engine.gpu.enter_main()?;
3179 engine.stream().synchronize()?;
3180 }
3181 let _main = root.gpu.enter_main()?;
3182 let mut peer_partial = root.uninit(output_len)?;
3183 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3184 remote_partial_keepalive.push(partial);
3185 peer_partial
3186 };
3187 let next = {
3188 let _main = root.gpu.enter_main()?;
3189 let mut next = root.uninit(output_len)?;
3190 root.add(&reduced, &root_partial, &mut next, output_len)?;
3191 next
3192 };
3193 reduced = next;
3194 }
3195 }
3196 let output = {
3197 let _main = root.gpu.enter_main()?;
3198 root.dtoh(&reduced)?
3199 };
3200 drop(remote_partial_keepalive);
3201 drop(root_packed_keepalive);
3202 drop(block_input_keepalive);
3203 Ok(output)
3204 }
3205
3206 pub fn step_bf16_row_parallel_resident_root_device(
3209 &self,
3210 matrix: &ResidentStepBf16RowParallel,
3211 rank_activations: &[CudaSlice<f32>],
3212 tokens: usize,
3213 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3214 if self.ranks.len() > 1 && !self.native_p2p {
3215 return Err(
3216 "device-resident Step BF16 row parallelism requires native P2P ranks".into(),
3217 );
3218 }
3219 validate_step_bf16_row_residency(&self.ranks, matrix)?;
3220 let local_width = matrix.in_features / self.ranks.len();
3221 let shard_len = tokens
3222 .checked_mul(local_width)
3223 .ok_or("device Step BF16 row shard size overflow")?;
3224 if tokens == 0
3225 || rank_activations.len() != self.ranks.len()
3226 || rank_activations
3227 .iter()
3228 .zip(&self.ranks)
3229 .any(|(rows, engine)| {
3230 rows.len() != shard_len || rows.ordinal() != engine.ctx().ordinal()
3231 })
3232 {
3233 return Err("device Step BF16 row activation shard geometry changed".into());
3234 }
3235
3236 let blocks_per_rank = PRODUCT_MAX_CARDS / self.ranks.len();
3237 let mut block_inputs = Vec::with_capacity(self.ranks.len());
3238 let mut partials = Vec::with_capacity(self.ranks.len());
3239 for (rank, blocks) in matrix.ranks.iter().enumerate() {
3240 if blocks.len() != blocks_per_rank {
3241 return Err(format!(
3242 "device Step BF16 row rank {rank} blocks {} != {blocks_per_rank}",
3243 blocks.len()
3244 )
3245 .into());
3246 }
3247 let engine = &self.ranks[rank];
3248 let _main = engine.gpu.enter_main()?;
3249 let mut rank_inputs = Vec::with_capacity(blocks_per_rank);
3250 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
3251 for (block, resident) in blocks.iter().enumerate() {
3252 let block_len = tokens
3253 .checked_mul(matrix.canonical_chunk_cols)
3254 .ok_or("device Step BF16 row block size overflow")?;
3255 let mut block_input = engine.uninit(block_len)?;
3256 let local_col_start = block * matrix.canonical_chunk_cols;
3257 if self.bulk_p2p {
3258 engine.copy_rows_strided(
3259 &rank_activations[rank],
3260 &mut block_input,
3261 matrix.canonical_chunk_cols,
3262 tokens,
3263 local_width,
3264 local_col_start,
3265 )?;
3266 } else {
3267 for token in 0..tokens {
3268 let source_start = token * local_width + local_col_start;
3269 let source = rank_activations[rank]
3270 .slice(source_start..source_start + matrix.canonical_chunk_cols);
3271 let destination_start = token * matrix.canonical_chunk_cols;
3272 let mut destination = block_input.slice_mut(
3273 destination_start..destination_start + matrix.canonical_chunk_cols,
3274 );
3275 engine.stream().memcpy_dtod(&source, &mut destination)?;
3276 }
3277 }
3278 let partial = run_resident_bf16_rank_device(
3279 engine,
3280 resident,
3281 &block_input,
3282 tokens,
3283 None,
3284 self.bulk_p2p,
3285 )?;
3286 rank_inputs.push(block_input);
3287 rank_partials.push(partial);
3288 }
3289 block_inputs.push(rank_inputs);
3290 partials.push(rank_partials);
3291 }
3292 for engine in self.ranks.iter().skip(1) {
3293 let _main = engine.gpu.enter_main()?;
3294 engine.stream().synchronize()?;
3295 }
3296
3297 let output_len = tokens
3298 .checked_mul(matrix.out_features)
3299 .ok_or("device Step BF16 row output size overflow")?;
3300 let root = &self.ranks[0];
3301 let _main = root.gpu.enter_main()?;
3302 let mut reduced = root.htod(&vec![0.0f32; output_len])?;
3303 let mut remote_partials = Vec::new();
3304 for (rank, rank_partials) in partials.into_iter().enumerate() {
3305 for partial in rank_partials {
3306 let root_partial = if rank == 0 {
3307 partial
3308 } else {
3309 let mut peer_partial = root.uninit(output_len)?;
3310 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
3311 remote_partials.push(partial);
3312 peer_partial
3313 };
3314 let mut next = root.uninit(output_len)?;
3315 root.add(&reduced, &root_partial, &mut next, output_len)?;
3316 reduced = next;
3317 }
3318 }
3319 root.stream().synchronize()?;
3320 drop(remote_partials);
3321 drop(block_inputs);
3322 Ok(reduced)
3323 }
3324
3325 pub fn step_bf16_row_parallel_resident_replicated_device(
3327 &self,
3328 matrix: &ResidentStepBf16RowParallel,
3329 rank_activations: &[CudaSlice<f32>],
3330 tokens: usize,
3331 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
3332 let reduced =
3333 self.step_bf16_row_parallel_resident_root_device(matrix, rank_activations, tokens)?;
3334 let output_len = tokens
3335 .checked_mul(matrix.out_features)
3336 .ok_or("device Step BF16 row output size overflow")?;
3337 let mut ranks = Vec::with_capacity(self.ranks.len());
3338 ranks.push(reduced);
3339 for engine in self.ranks.iter().skip(1) {
3340 let _main = engine.gpu.enter_main()?;
3341 let mut peer_output = engine.uninit(output_len)?;
3342 engine.stream().memcpy_dtod(&ranks[0], &mut peer_output)?;
3343 ranks.push(peer_output);
3344 }
3345 Ok(ResidentReplicatedDeviceRows {
3346 ranks,
3347 tokens,
3348 width: matrix.out_features,
3349 })
3350 }
3351
3352 pub fn upload_expert(
3353 &self,
3354 gate: E4m3BlockMatrix<'_>,
3355 up: E4m3BlockMatrix<'_>,
3356 down: E4m3BlockMatrix<'_>,
3357 ) -> Result<ResidentTpExpert, Box<dyn std::error::Error>> {
3358 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3359 return Err("TP expert gate/up dimensions differ".into());
3360 }
3361 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3362 return Err(format!(
3363 "TP expert down {}x{} does not invert gate/up {}x{}",
3364 down.out_features, down.in_features, gate.out_features, gate.in_features
3365 )
3366 .into());
3367 }
3368 Ok(ResidentTpExpert {
3369 gate: self.upload_column_parallel(gate)?,
3370 up: self.upload_column_parallel(up)?,
3371 down: self.upload_row_parallel(down)?,
3372 input_width: gate.in_features,
3373 expert_width: gate.out_features,
3374 })
3375 }
3376
3377 pub fn run_expert(
3378 &self,
3379 expert: &ResidentTpExpert,
3380 input: &[f32],
3381 tokens: usize,
3382 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
3383 validate_activations(input, tokens, expert.input_width)?;
3384 let gate = self.column_parallel_resident(&expert.gate, input, tokens)?;
3385 let up = self.column_parallel_resident(&expert.up, input, tokens)?;
3386 let activated: Vec<f32> = gate
3387 .gathered
3388 .iter()
3389 .zip(&up.gathered)
3390 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
3391 .collect();
3392 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
3393 Ok(self
3394 .row_parallel_resident(&expert.down, &activated, tokens)?
3395 .reduced)
3396 }
3397
3398 pub fn upload_expert_parallel(
3399 &self,
3400 gate: E4m3ExpertBank<'_>,
3401 up: E4m3ExpertBank<'_>,
3402 down: E4m3ExpertBank<'_>,
3403 ) -> Result<ResidentExpertParallel, Box<dyn std::error::Error>> {
3404 gate.validate()?;
3405 up.validate()?;
3406 down.validate()?;
3407 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
3408 return Err("EP gate/up/down expert counts differ".into());
3409 }
3410 if gate.in_features != up.in_features || gate.out_features != up.out_features {
3411 return Err("EP gate/up dimensions differ".into());
3412 }
3413 if down.in_features != gate.out_features || down.out_features != gate.in_features {
3414 return Err(format!(
3415 "EP down {}x{} does not invert gate/up {}x{}",
3416 down.out_features, down.in_features, gate.out_features, gate.in_features
3417 )
3418 .into());
3419 }
3420 if gate.expert_count % self.ranks.len() != 0 {
3421 return Err(format!(
3422 "EP expert count {} is not divisible by {} ranks",
3423 gate.expert_count,
3424 self.ranks.len()
3425 )
3426 .into());
3427 }
3428
3429 let per_rank = gate.expert_count / self.ranks.len();
3430 let mut ranks = Vec::with_capacity(self.ranks.len());
3431 for (rank, engine) in self.ranks.iter().enumerate() {
3432 let expert_range = rank * per_rank..(rank + 1) * per_rank;
3433 ranks.push(ResidentEpRank {
3434 gate: upload_expert_bank_rank(engine, gate, expert_range.clone())?,
3435 up: upload_expert_bank_rank(engine, up, expert_range.clone())?,
3436 down: upload_expert_bank_rank(engine, down, expert_range)?,
3437 });
3438 }
3439 Ok(ResidentExpertParallel {
3440 ranks,
3441 expert_count: gate.expert_count,
3442 input_width: gate.in_features,
3443 expert_width: gate.out_features,
3444 })
3445 }
3446
3447 #[allow(clippy::too_many_arguments)]
3453 pub fn prepare_step_grouped_fp8_gate(
3454 &self,
3455 gate: E4m3ExpertBank<'_>,
3456 up: E4m3ExpertBank<'_>,
3457 down: E4m3ExpertBank<'_>,
3458 input: &[f32],
3459 tokens: usize,
3460 selected: &[usize],
3461 activation_limit: Option<f32>,
3462 ) -> Result<PreparedStepGroupedFp8Gate, Box<dyn std::error::Error>> {
3463 gate.validate()?;
3464 up.validate()?;
3465 down.validate()?;
3466 validate_step_expert_activation_limit(activation_limit)?;
3467 if gate.expert_count != STEP_GROUPED_FP8_EXPERTS
3468 || up.expert_count != STEP_GROUPED_FP8_EXPERTS
3469 || down.expert_count != STEP_GROUPED_FP8_EXPERTS
3470 {
3471 return Err(format!(
3472 "official Step grouped FP8 gate requires {STEP_GROUPED_FP8_EXPERTS} experts, \
3473 got gate/up/down={}/{}/{}",
3474 gate.expert_count, up.expert_count, down.expert_count,
3475 )
3476 .into());
3477 }
3478 if gate.in_features != up.in_features
3479 || gate.out_features != STEP_GROUPED_FP8_WIDTH
3480 || up.out_features != STEP_GROUPED_FP8_WIDTH
3481 || down.in_features != STEP_GROUPED_FP8_WIDTH
3482 || down.out_features != gate.in_features
3483 {
3484 return Err(format!(
3485 "official Step grouped FP8 geometry gate={}x{} up={}x{} down={}x{}",
3486 gate.out_features,
3487 gate.in_features,
3488 up.out_features,
3489 up.in_features,
3490 down.out_features,
3491 down.in_features,
3492 )
3493 .into());
3494 }
3495 validate_activations(input, tokens, gate.in_features)?;
3496 let pairs = tokens
3497 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3498 .ok_or("official Step grouped FP8 route count overflow")?;
3499 if selected.len() != pairs {
3500 return Err(format!(
3501 "official Step grouped FP8 routes {} != {tokens}x{STEP_GROUPED_FP8_TOP_K} \
3502 ({pairs})",
3503 selected.len()
3504 )
3505 .into());
3506 }
3507 for (token, routes) in selected.chunks_exact(STEP_GROUPED_FP8_TOP_K).enumerate() {
3508 let mut unique = routes.to_vec();
3509 unique.sort_unstable();
3510 unique.dedup();
3511 if unique.len() != STEP_GROUPED_FP8_TOP_K {
3512 return Err(format!(
3513 "official Step grouped FP8 token {token} routes are not top-8 unique: \
3514 {routes:?}"
3515 )
3516 .into());
3517 }
3518 }
3519
3520 let engine = self
3521 .ranks
3522 .first()
3523 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3524 let _main = engine.gpu.enter_main()?;
3525 let expert_range = 0..STEP_GROUPED_FP8_EXPERTS;
3526 let gate = upload_expert_bank_rank(engine, gate, expert_range.clone())?;
3527 let up = upload_expert_bank_rank(engine, up, expert_range.clone())?;
3528 let down = upload_expert_bank_rank(engine, down, expert_range)?;
3529 let input = engine.htod(input)?;
3530 let route_csr = ExpertCsr::from_token_routes(
3531 STEP_GROUPED_FP8_EXPERTS,
3532 tokens,
3533 STEP_GROUPED_FP8_TOP_K,
3534 selected,
3535 )?
3536 .upload(engine)?;
3537 let pair_rows = (0..pairs).collect::<Vec<_>>();
3538 let down_csr =
3539 ExpertCsr::from_pair_rows(STEP_GROUPED_FP8_EXPERTS, pairs, selected, &pair_rows)?
3540 .upload(engine)?;
3541 let gate_workspace =
3542 Fp8GroupedWorkspace::new(engine, gate.in_features, gate.out_features, tokens, pairs)?;
3543 let up_workspace =
3544 Fp8GroupedWorkspace::new(engine, up.in_features, up.out_features, tokens, pairs)?;
3545 let down_workspace =
3546 Fp8GroupedWorkspace::new(engine, down.in_features, down.out_features, pairs, pairs)?;
3547 let activation = engine.uninit(pairs * STEP_GROUPED_FP8_WIDTH)?;
3548 Ok(PreparedStepGroupedFp8Gate {
3549 device: engine.ctx().ordinal(),
3550 gate,
3551 up,
3552 down,
3553 input,
3554 route_csr,
3555 down_csr,
3556 gate_workspace,
3557 up_workspace,
3558 down_workspace,
3559 activation,
3560 activation_limit,
3561 tokens,
3562 pairs,
3563 })
3564 }
3565
3566 pub fn run_step_grouped_fp8_gate(
3568 &self,
3569 plan: &mut PreparedStepGroupedFp8Gate,
3570 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
3571 let engine = self
3572 .ranks
3573 .first()
3574 .ok_or("official Step grouped FP8 gate has no rank-zero engine")?;
3575 if engine.ctx().ordinal() != plan.device {
3576 return Err(format!(
3577 "official Step grouped FP8 plan device {} != rank-zero device {}",
3578 plan.device,
3579 engine.ctx().ordinal()
3580 )
3581 .into());
3582 }
3583 let _main = engine.gpu.enter_main()?;
3584
3585 plan.gate_workspace.quantize(engine, &plan.input)?;
3586 plan.gate_workspace.project(
3587 engine,
3588 &plan.gate.codes,
3589 &plan.gate.scales,
3590 &plan.route_csr,
3591 plan.gate.code_stride,
3592 plan.gate.scale_stride,
3593 1.0,
3594 )?;
3595 plan.up_workspace.quantize(engine, &plan.input)?;
3596 plan.up_workspace.project(
3597 engine,
3598 &plan.up.codes,
3599 &plan.up.scales,
3600 &plan.route_csr,
3601 plan.up.code_stride,
3602 plan.up.scale_stride,
3603 1.0,
3604 )?;
3605 if let Some(limit) = plan.activation_limit {
3606 engine.silu_clamped_mul_host_expf(
3607 plan.gate_workspace.output(),
3608 plan.up_workspace.output(),
3609 limit,
3610 &mut plan.activation,
3611 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3612 )?;
3613 } else {
3614 engine.silu_mul_host_expf(
3615 plan.gate_workspace.output(),
3616 plan.up_workspace.output(),
3617 &mut plan.activation,
3618 plan.pairs * STEP_GROUPED_FP8_WIDTH,
3619 )?;
3620 }
3621 plan.down_workspace.quantize(engine, &plan.activation)?;
3622 plan.down_workspace.project(
3623 engine,
3624 &plan.down.codes,
3625 &plan.down.scales,
3626 &plan.down_csr,
3627 plan.down.code_stride,
3628 plan.down.scale_stride,
3629 1.0,
3630 )?;
3631
3632 Ok(StepGroupedFp8ProjectionOutput {
3633 gate: engine.dtoh(plan.gate_workspace.output())?,
3634 up: engine.dtoh(plan.up_workspace.output())?,
3635 down: engine.dtoh(plan.down_workspace.output())?,
3636 })
3637 }
3638
3639 pub fn prepare_step_grouped_expert_parallel_gate(
3640 &self,
3641 experts: &ResidentExpertParallel,
3642 input: &[f32],
3643 tokens: usize,
3644 selected: &[usize],
3645 activation_limit: Option<f32>,
3646 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3647 self.prepare_step_grouped_expert_parallel_gate_with_capacity(
3648 experts,
3649 input,
3650 tokens,
3651 selected,
3652 activation_limit,
3653 tokens,
3654 )
3655 }
3656
3657 #[allow(clippy::too_many_arguments)]
3658 pub fn prepare_step_grouped_expert_parallel_gate_with_capacity(
3659 &self,
3660 experts: &ResidentExpertParallel,
3661 input: &[f32],
3662 tokens: usize,
3663 selected: &[usize],
3664 activation_limit: Option<f32>,
3665 max_tokens: usize,
3666 ) -> Result<PreparedStepGroupedExpertParallelGate, Box<dyn std::error::Error>> {
3667 if !self.native_p2p || !self.ep_device_arithmetic {
3668 return Err(
3669 "Step owner-grouped FP8 requires native P2P and device-resident arithmetic".into(),
3670 );
3671 }
3672 validate_step_expert_activation_limit(activation_limit)?;
3673 validate_ep_residency(&self.ranks, experts)?;
3674 validate_activations(input, tokens, experts.input_width)?;
3675 if max_tokens < tokens || max_tokens > i32::MAX as usize {
3676 return Err(format!(
3677 "official Step owner-grouped FP8 tokens {tokens} exceed capacity {max_tokens}"
3678 )
3679 .into());
3680 }
3681 if experts.expert_count != STEP_GROUPED_FP8_EXPERTS
3682 || experts.expert_width != STEP_GROUPED_FP8_WIDTH
3683 {
3684 return Err(format!(
3685 "official Step owner-grouped FP8 requires {} experts at width {}, got {} at {}",
3686 STEP_GROUPED_FP8_EXPERTS,
3687 STEP_GROUPED_FP8_WIDTH,
3688 experts.expert_count,
3689 experts.expert_width,
3690 )
3691 .into());
3692 }
3693 validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3694 let max_pairs = max_tokens
3695 .checked_mul(STEP_GROUPED_FP8_TOP_K)
3696 .ok_or("official Step owner-grouped FP8 capacity route count overflow")?;
3697 let input_capacity = max_tokens
3698 .checked_mul(experts.input_width)
3699 .ok_or("official Step owner-grouped FP8 input capacity overflow")?;
3700
3701 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
3702 for engine in &self.ranks {
3703 let _main = engine.gpu.enter_main()?;
3704 rank_inputs.push(engine.uninit(input_capacity)?);
3705 }
3706
3707 let mut owners = Vec::with_capacity(self.ranks.len());
3708 for (owner_rank, rank) in experts.ranks.iter().enumerate() {
3709 if rank.gate.expert_range != rank.up.expert_range
3710 || rank.gate.expert_range != rank.down.expert_range
3711 {
3712 return Err(format!(
3713 "owner-grouped FP8 rank {} gate/up/down expert ranges differ",
3714 owner_rank
3715 )
3716 .into());
3717 }
3718 let local_experts = rank.gate.expert_range.len();
3719 let engine = &self.ranks[owner_rank];
3720 let _main = engine.gpu.enter_main()?;
3721 let route_csr =
3722 DeviceExpertCsr::with_capacity(engine, local_experts, max_tokens, max_pairs)?;
3723 let down_csr =
3724 DeviceExpertCsr::with_capacity(engine, local_experts, max_pairs, max_pairs)?;
3725 let gate_workspace = Fp8GroupedWorkspace::new(
3726 engine,
3727 experts.input_width,
3728 experts.expert_width,
3729 max_tokens,
3730 max_pairs,
3731 )?;
3732 let up_workspace = Fp8GroupedWorkspace::new(
3733 engine,
3734 experts.input_width,
3735 experts.expert_width,
3736 max_tokens,
3737 max_pairs,
3738 )?;
3739 let down_workspace = Fp8GroupedWorkspace::new(
3740 engine,
3741 experts.expert_width,
3742 experts.input_width,
3743 max_pairs,
3744 max_pairs,
3745 )?;
3746 let activation = engine.uninit(
3747 max_pairs
3748 .checked_mul(experts.expert_width)
3749 .ok_or("official Step owner-grouped FP8 activation capacity overflow")?,
3750 )?;
3751 owners.push(PreparedStepGroupedExpertOwner {
3752 rank: owner_rank,
3753 global_pairs: Vec::new(),
3754 route_csr,
3755 down_csr,
3756 gate_workspace,
3757 up_workspace,
3758 down_workspace,
3759 activation,
3760 });
3761 }
3762
3763 let mut plan = PreparedStepGroupedExpertParallelGate {
3764 rank_inputs,
3765 owners,
3766 activation_limit,
3767 tokens: 0,
3768 pairs: 0,
3769 max_tokens,
3770 max_pairs,
3771 input_width: experts.input_width,
3772 expert_width: experts.expert_width,
3773 generation: 0,
3774 executed_generation: None,
3775 ready: false,
3776 };
3777 self.refresh_step_grouped_expert_parallel_gate(
3778 experts, &mut plan, input, tokens, selected,
3779 )?;
3780 Ok(plan)
3781 }
3782
3783 fn prepare_step_grouped_expert_parallel_refresh(
3784 &self,
3785 experts: &ResidentExpertParallel,
3786 plan: &PreparedStepGroupedExpertParallelGate,
3787 tokens: usize,
3788 selected: &[usize],
3789 ) -> Result<(usize, u64, Vec<Option<StepGroupedExpertOwnerSchedule>>), Box<dyn std::error::Error>>
3790 {
3791 validate_ep_residency(&self.ranks, experts)?;
3792 if plan.rank_inputs.len() != self.ranks.len()
3793 || plan.owners.len() != self.ranks.len()
3794 || plan.input_width != experts.input_width
3795 || plan.expert_width != experts.expert_width
3796 || tokens > plan.max_tokens
3797 {
3798 return Err(format!(
3799 "Step owner-grouped FP8 refresh geometry changed ranks={}/{} owners={}/{} \
3800 input={}/{} expert={}/{} tokens={}/{}",
3801 plan.rank_inputs.len(),
3802 self.ranks.len(),
3803 plan.owners.len(),
3804 self.ranks.len(),
3805 plan.input_width,
3806 experts.input_width,
3807 plan.expert_width,
3808 experts.expert_width,
3809 tokens,
3810 plan.max_tokens,
3811 )
3812 .into());
3813 }
3814 let pairs = validate_step_grouped_owner_routes(experts.expert_count, tokens, selected)?;
3815 if pairs > plan.max_pairs {
3816 return Err(format!(
3817 "Step owner-grouped FP8 route count {pairs} exceeds capacity {}",
3818 plan.max_pairs
3819 )
3820 .into());
3821 }
3822 let next_generation = plan
3823 .generation
3824 .checked_add(1)
3825 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
3826 let owner_routes = partition_expert_owner_routes(
3827 experts.expert_count,
3828 self.ranks.len(),
3829 tokens,
3830 STEP_GROUPED_FP8_TOP_K,
3831 selected,
3832 )?;
3833 let mut schedules = Vec::with_capacity(self.ranks.len());
3834 for routes in owner_routes {
3835 if routes.selected.is_empty() {
3836 schedules.push(None);
3837 continue;
3838 }
3839 let local_experts = experts.ranks[routes.rank].gate.expert_range.len();
3840 let local_pairs = routes.selected.len();
3841 let route_csr = ExpertCsr::from_pair_rows(
3842 local_experts,
3843 tokens,
3844 &routes.selected,
3845 &routes.token_rows,
3846 )?;
3847 let down_rows = (0..local_pairs).collect::<Vec<_>>();
3848 let down_csr = ExpertCsr::from_pair_rows(
3849 local_experts,
3850 local_pairs,
3851 &routes.selected,
3852 &down_rows,
3853 )?;
3854 schedules.push(Some(StepGroupedExpertOwnerSchedule {
3855 global_pairs: routes.global_pairs,
3856 route_csr,
3857 down_csr,
3858 }));
3859 }
3860 Ok((pairs, next_generation, schedules))
3861 }
3862
3863 fn commit_step_grouped_expert_parallel_refresh(
3864 &self,
3865 plan: &mut PreparedStepGroupedExpertParallelGate,
3866 tokens: usize,
3867 pairs: usize,
3868 next_generation: u64,
3869 schedules: Vec<Option<StepGroupedExpertOwnerSchedule>>,
3870 ) -> Result<(), Box<dyn std::error::Error>> {
3871 for (owner, schedule) in plan.owners.iter_mut().zip(schedules) {
3872 let engine = &self.ranks[owner.rank];
3873 let _main = engine.gpu.enter_main()?;
3874 if let Some(schedule) = schedule {
3875 owner.route_csr.refresh(engine, &schedule.route_csr)?;
3876 owner.down_csr.refresh(engine, &schedule.down_csr)?;
3877 owner.global_pairs = schedule.global_pairs;
3878 } else {
3879 owner.route_csr.clear();
3880 owner.down_csr.clear();
3881 owner.global_pairs.clear();
3882 }
3883 }
3884 plan.tokens = tokens;
3885 plan.pairs = pairs;
3886 plan.generation = next_generation;
3887 plan.ready = true;
3888 Ok(())
3889 }
3890
3891 pub fn refresh_step_grouped_expert_parallel_gate(
3892 &self,
3893 experts: &ResidentExpertParallel,
3894 plan: &mut PreparedStepGroupedExpertParallelGate,
3895 input: &[f32],
3896 tokens: usize,
3897 selected: &[usize],
3898 ) -> Result<(), Box<dyn std::error::Error>> {
3899 validate_activations(input, tokens, experts.input_width)?;
3900 let (pairs, next_generation, schedules) =
3901 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
3902
3903 plan.ready = false;
3904 plan.executed_generation = None;
3905 {
3906 let root = &self.ranks[0];
3907 let _main = root.gpu.enter_main()?;
3908 let mut destination = plan.rank_inputs[0].slice_mut(0..input.len());
3909 root.stream().memcpy_htod(input, &mut destination)?;
3910 root.stream().synchronize()?;
3911 }
3912 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
3913 let root_input = &root_inputs[0];
3914 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
3915 let engine = &self.ranks[rank + 1];
3916 let _main = engine.gpu.enter_main()?;
3917 let mut destination = peer_input.slice_mut(0..input.len());
3918 engine
3919 .stream()
3920 .memcpy_dtod(&root_input.slice(0..input.len()), &mut destination)?;
3921 }
3922 self.commit_step_grouped_expert_parallel_refresh(
3923 plan,
3924 tokens,
3925 pairs,
3926 next_generation,
3927 schedules,
3928 )
3929 }
3930
3931 pub fn refresh_step_grouped_expert_parallel_gate_from_root_device(
3936 &self,
3937 experts: &ResidentExpertParallel,
3938 plan: &mut PreparedStepGroupedExpertParallelGate,
3939 input: &CudaSlice<f32>,
3940 tokens: usize,
3941 selected: &[usize],
3942 ) -> Result<(), Box<dyn std::error::Error>> {
3943 let input_values = tokens
3944 .checked_mul(experts.input_width)
3945 .ok_or("Step owner-grouped FP8 input size overflow")?;
3946 let root = self
3947 .ranks
3948 .first()
3949 .ok_or("Step owner-grouped FP8 runtime has no root rank")?;
3950 if input.len() < input_values || input.ordinal() != root.ctx().ordinal() {
3951 return Err(format!(
3952 "Step owner-grouped FP8 root input len/device {}/{} does not cover {} values on \
3953 device {}",
3954 input.len(),
3955 input.ordinal(),
3956 input_values,
3957 root.ctx().ordinal(),
3958 )
3959 .into());
3960 }
3961 let (pairs, next_generation, schedules) =
3962 self.prepare_step_grouped_expert_parallel_refresh(experts, plan, tokens, selected)?;
3963
3964 plan.ready = false;
3965 plan.executed_generation = None;
3966 {
3967 let _main = root.gpu.enter_main()?;
3968 let mut destination = plan.rank_inputs[0].slice_mut(0..input_values);
3969 root.stream()
3970 .memcpy_dtod(&input.slice(0..input_values), &mut destination)?;
3971 root.stream().synchronize()?;
3972 }
3973 let (root_inputs, peer_inputs) = plan.rank_inputs.split_at_mut(1);
3974 let root_input = &root_inputs[0];
3975 for (rank, peer_input) in peer_inputs.iter_mut().enumerate() {
3976 let engine = &self.ranks[rank + 1];
3977 let _main = engine.gpu.enter_main()?;
3978 let mut destination = peer_input.slice_mut(0..input_values);
3979 engine
3980 .stream()
3981 .memcpy_dtod(&root_input.slice(0..input_values), &mut destination)?;
3982 }
3983 self.commit_step_grouped_expert_parallel_refresh(
3984 plan,
3985 tokens,
3986 pairs,
3987 next_generation,
3988 schedules,
3989 )
3990 }
3991
3992 pub fn refresh_step_grouped_expert_parallel_inputs_from_replicated(
3997 &self,
3998 experts: &ResidentExpertParallel,
3999 plan: &mut PreparedStepGroupedExpertParallelGate,
4000 input: &ResidentReplicatedDeviceRows,
4001 ) -> Result<(), Box<dyn std::error::Error>> {
4002 validate_ep_residency(&self.ranks, experts)?;
4003 validate_replicated_device_rows(&self.ranks, input)?;
4004 if !plan.ready
4005 || input.tokens != plan.tokens
4006 || input.width != plan.input_width
4007 || input.tokens > plan.max_tokens
4008 || plan.rank_inputs.len() != self.ranks.len()
4009 || plan.owners.len() != self.ranks.len()
4010 || plan.input_width != experts.input_width
4011 || plan.expert_width != experts.expert_width
4012 {
4013 return Err("Step owner-grouped replicated input geometry changed".into());
4014 }
4015 let values = input
4016 .tokens
4017 .checked_mul(input.width)
4018 .ok_or("Step owner-grouped replicated input size overflow")?;
4019 let next_generation = plan
4020 .generation
4021 .checked_add(1)
4022 .ok_or("Step owner-grouped FP8 plan generation overflow")?;
4023 plan.ready = false;
4024 plan.executed_generation = None;
4025 for (rank, engine) in self.ranks.iter().enumerate() {
4026 let _main = engine.gpu.enter_main()?;
4027 let mut destination = plan.rank_inputs[rank].slice_mut(0..values);
4028 engine
4029 .stream()
4030 .memcpy_dtod(&input.ranks[rank], &mut destination)?;
4031 }
4032 plan.generation = next_generation;
4033 plan.ready = true;
4034 Ok(())
4035 }
4036
4037 pub fn execute_step_grouped_expert_parallel_gate(
4038 &self,
4039 experts: &ResidentExpertParallel,
4040 plan: &mut PreparedStepGroupedExpertParallelGate,
4041 ) -> Result<(), Box<dyn std::error::Error>> {
4042 validate_ep_residency(&self.ranks, experts)?;
4043 if !plan.ready
4044 || plan.rank_inputs.len() != self.ranks.len()
4045 || plan.owners.len() != self.ranks.len()
4046 || plan.input_width != experts.input_width
4047 || plan.expert_width != experts.expert_width
4048 {
4049 return Err("Step owner-grouped FP8 plan is not ready or its geometry changed".into());
4050 }
4051 plan.executed_generation = None;
4052
4053 for owner in &mut plan.owners {
4054 if owner.global_pairs.is_empty() {
4055 continue;
4056 }
4057 let engine = &self.ranks[owner.rank];
4058 let bank = &experts.ranks[owner.rank];
4059 let _main = engine.gpu.enter_main()?;
4060 let local_pairs = owner.global_pairs.len();
4061 owner.gate_workspace.quantize_for_shape(
4062 engine,
4063 &plan.rank_inputs[owner.rank],
4064 plan.tokens,
4065 local_pairs,
4066 )?;
4067 owner.gate_workspace.project(
4068 engine,
4069 &bank.gate.codes,
4070 &bank.gate.scales,
4071 &owner.route_csr,
4072 bank.gate.code_stride,
4073 bank.gate.scale_stride,
4074 1.0,
4075 )?;
4076 owner.up_workspace.quantize_for_shape(
4077 engine,
4078 &plan.rank_inputs[owner.rank],
4079 plan.tokens,
4080 local_pairs,
4081 )?;
4082 owner.up_workspace.project(
4083 engine,
4084 &bank.up.codes,
4085 &bank.up.scales,
4086 &owner.route_csr,
4087 bank.up.code_stride,
4088 bank.up.scale_stride,
4089 1.0,
4090 )?;
4091 }
4092 for owner in &mut plan.owners {
4093 if owner.global_pairs.is_empty() {
4094 continue;
4095 }
4096 let engine = &self.ranks[owner.rank];
4097 let _main = engine.gpu.enter_main()?;
4098 let values = owner.global_pairs.len() * plan.expert_width;
4099 if let Some(limit) = plan.activation_limit {
4100 engine.silu_clamped_mul_host_expf(
4101 owner.gate_workspace.output(),
4102 owner.up_workspace.output(),
4103 limit,
4104 &mut owner.activation,
4105 values,
4106 )?;
4107 } else {
4108 engine.silu_mul_host_expf(
4109 owner.gate_workspace.output(),
4110 owner.up_workspace.output(),
4111 &mut owner.activation,
4112 values,
4113 )?;
4114 }
4115 }
4116 for owner in &mut plan.owners {
4117 if owner.global_pairs.is_empty() {
4118 continue;
4119 }
4120 let engine = &self.ranks[owner.rank];
4121 let bank = &experts.ranks[owner.rank];
4122 let _main = engine.gpu.enter_main()?;
4123 let local_pairs = owner.global_pairs.len();
4124 owner.down_workspace.quantize_for_shape(
4125 engine,
4126 &owner.activation,
4127 local_pairs,
4128 local_pairs,
4129 )?;
4130 owner.down_workspace.project(
4131 engine,
4132 &bank.down.codes,
4133 &bank.down.scales,
4134 &owner.down_csr,
4135 bank.down.code_stride,
4136 bank.down.scale_stride,
4137 1.0,
4138 )?;
4139 }
4140 plan.executed_generation = Some(plan.generation);
4141 Ok(())
4142 }
4143
4144 pub fn collect_step_grouped_expert_parallel_gate(
4145 &self,
4146 plan: &PreparedStepGroupedExpertParallelGate,
4147 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4148 if !plan.ready || plan.executed_generation != Some(plan.generation) {
4149 return Err("Step owner-grouped FP8 projection is stale or has not executed".into());
4150 }
4151 let mut gate = vec![0.0f32; plan.pairs * plan.expert_width];
4152 let mut up = vec![0.0f32; plan.pairs * plan.expert_width];
4153 let mut down = vec![0.0f32; plan.pairs * plan.input_width];
4154 for owner in &plan.owners {
4155 if owner.global_pairs.is_empty() {
4156 continue;
4157 }
4158 let engine = &self.ranks[owner.rank];
4159 let _main = engine.gpu.enter_main()?;
4160 let owner_gate = engine.dtoh_view(
4161 &owner
4162 .gate_workspace
4163 .output()
4164 .slice(0..owner.gate_workspace.output_len()),
4165 )?;
4166 let owner_up = engine.dtoh_view(
4167 &owner
4168 .up_workspace
4169 .output()
4170 .slice(0..owner.up_workspace.output_len()),
4171 )?;
4172 let owner_down = engine.dtoh_view(
4173 &owner
4174 .down_workspace
4175 .output()
4176 .slice(0..owner.down_workspace.output_len()),
4177 )?;
4178 for (local_pair, &global_pair) in owner.global_pairs.iter().enumerate() {
4179 let local_expert = local_pair * plan.expert_width;
4180 let global_expert = global_pair * plan.expert_width;
4181 gate[global_expert..global_expert + plan.expert_width]
4182 .copy_from_slice(&owner_gate[local_expert..local_expert + plan.expert_width]);
4183 up[global_expert..global_expert + plan.expert_width]
4184 .copy_from_slice(&owner_up[local_expert..local_expert + plan.expert_width]);
4185
4186 let local_hidden = local_pair * plan.input_width;
4187 let global_hidden = global_pair * plan.input_width;
4188 down[global_hidden..global_hidden + plan.input_width]
4189 .copy_from_slice(&owner_down[local_hidden..local_hidden + plan.input_width]);
4190 }
4191 }
4192 Ok(StepGroupedFp8ProjectionOutput { gate, up, down })
4193 }
4194
4195 pub fn run_step_grouped_expert_parallel_gate(
4196 &self,
4197 experts: &ResidentExpertParallel,
4198 plan: &mut PreparedStepGroupedExpertParallelGate,
4199 ) -> Result<StepGroupedFp8ProjectionOutput, Box<dyn std::error::Error>> {
4200 self.execute_step_grouped_expert_parallel_gate(experts, plan)?;
4201 self.collect_step_grouped_expert_parallel_gate(plan)
4202 }
4203
4204 pub fn prepare_step_grouped_expert_parallel_combine(
4205 &self,
4206 plan: &PreparedStepGroupedExpertParallelGate,
4207 route_weights: &[f32],
4208 ) -> Result<PreparedPeerWeightedRouteCombine, Box<dyn std::error::Error>> {
4209 if !self.native_p2p || !self.ep_device_arithmetic || !plan.ready {
4210 return Err(
4211 "Step owner-grouped combine requires a ready native-P2P device plan".into(),
4212 );
4213 }
4214 let owner_pairs = plan
4215 .owners
4216 .iter()
4217 .map(|owner| owner.global_pairs.as_slice())
4218 .collect::<Vec<_>>();
4219 let shape = validate_weighted_route_combine(
4220 plan.input_width,
4221 STEP_GROUPED_FP8_TOP_K,
4222 plan.max_tokens,
4223 plan.tokens,
4224 &owner_pairs,
4225 route_weights,
4226 )?;
4227 if shape.max_pairs != plan.max_pairs {
4228 return Err(format!(
4229 "Step owner-grouped combine capacity {} != projection capacity {}",
4230 shape.max_pairs, plan.max_pairs
4231 )
4232 .into());
4233 }
4234 let root = self
4235 .ranks
4236 .first()
4237 .ok_or("Step owner-grouped combine has no root rank")?;
4238 let slot_values = shape
4239 .max_pairs
4240 .checked_mul(plan.input_width)
4241 .ok_or("Step owner-grouped combine slot capacity overflow")?;
4242 let output_values = plan
4243 .max_tokens
4244 .checked_mul(plan.input_width)
4245 .ok_or("Step owner-grouped combine output capacity overflow")?;
4246 let (root_device, owners, peer_staging, slots, weights, output) = {
4247 let _main = root.gpu.enter_main()?;
4248 let mut owners = Vec::with_capacity(plan.owners.len());
4249 for _ in &plan.owners {
4250 owners.push(PreparedPeerWeightedRouteOwner {
4251 token_rows: root.htod_i32(&vec![0; shape.max_pairs])?,
4252 slots: root.htod_i32(&vec![0; shape.max_pairs])?,
4253 weights: root.htod(&vec![0.0; shape.max_pairs])?,
4254 active_pairs: 0,
4255 });
4256 }
4257 (
4258 root.ctx().ordinal(),
4259 owners,
4260 root.uninit(slot_values)?,
4261 root.uninit(slot_values)?,
4262 root.uninit(shape.max_pairs)?,
4263 root.uninit(output_values)?,
4264 )
4265 };
4266 let mut peer_devices = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4267 let mut peer_outputs = Vec::with_capacity(self.ranks.len().saturating_sub(1));
4268 for engine in self.ranks.iter().skip(1) {
4269 let _main = engine.gpu.enter_main()?;
4270 peer_devices.push(engine.ctx().ordinal());
4271 peer_outputs.push(engine.uninit(output_values)?);
4272 }
4273 let mut combine = PreparedPeerWeightedRouteCombine {
4274 root_device,
4275 owners,
4276 peer_staging,
4277 slots,
4278 weights,
4279 output,
4280 peer_devices,
4281 peer_outputs,
4282 width: plan.input_width,
4283 experts_per_token: STEP_GROUPED_FP8_TOP_K,
4284 max_tokens: plan.max_tokens,
4285 max_pairs: shape.max_pairs,
4286 tokens: 0,
4287 pairs: 0,
4288 projection_generation: 0,
4289 output_generation: None,
4290 broadcast_generation: None,
4291 ready: false,
4292 };
4293 self.refresh_step_grouped_expert_parallel_combine(plan, &mut combine, route_weights)?;
4294 Ok(combine)
4295 }
4296
4297 pub fn refresh_step_grouped_expert_parallel_combine(
4298 &self,
4299 plan: &PreparedStepGroupedExpertParallelGate,
4300 combine: &mut PreparedPeerWeightedRouteCombine,
4301 route_weights: &[f32],
4302 ) -> Result<(), Box<dyn std::error::Error>> {
4303 let output_capacity = combine
4304 .max_tokens
4305 .checked_mul(combine.width)
4306 .ok_or("Step owner-grouped combine output capacity overflow")?;
4307 if !plan.ready
4308 || combine.owners.len() != plan.owners.len()
4309 || combine.peer_devices.len() + 1 != self.ranks.len()
4310 || combine.peer_outputs.len() + 1 != self.ranks.len()
4311 || combine.width != plan.input_width
4312 || combine.experts_per_token != STEP_GROUPED_FP8_TOP_K
4313 || combine.max_tokens != plan.max_tokens
4314 || combine.max_pairs != plan.max_pairs
4315 || combine.output.len() < output_capacity
4316 || combine
4317 .peer_outputs
4318 .iter()
4319 .any(|output| output.len() < output_capacity)
4320 {
4321 return Err("Step owner-grouped combine/projection geometry changed".into());
4322 }
4323 if self
4324 .ranks
4325 .iter()
4326 .skip(1)
4327 .zip(&combine.peer_devices)
4328 .any(|(engine, &device)| engine.ctx().ordinal() != device)
4329 {
4330 return Err("Step owner-grouped combine peer devices changed".into());
4331 }
4332 let owner_pairs = plan
4333 .owners
4334 .iter()
4335 .map(|owner| owner.global_pairs.as_slice())
4336 .collect::<Vec<_>>();
4337 let shape = validate_weighted_route_combine(
4338 combine.width,
4339 combine.experts_per_token,
4340 combine.max_tokens,
4341 plan.tokens,
4342 &owner_pairs,
4343 route_weights,
4344 )?;
4345 if shape.max_pairs != combine.max_pairs {
4346 return Err("Step owner-grouped combine capacity changed during refresh".into());
4347 }
4348 let metadata = owner_pairs
4349 .iter()
4350 .map(|pairs| {
4351 let token_rows = pairs
4352 .iter()
4353 .map(|&pair| (pair / combine.experts_per_token) as i32)
4354 .collect::<Vec<_>>();
4355 let slots = pairs
4356 .iter()
4357 .map(|&pair| (pair % combine.experts_per_token) as i32)
4358 .collect::<Vec<_>>();
4359 let weights = pairs
4360 .iter()
4361 .map(|&pair| route_weights[pair])
4362 .collect::<Vec<_>>();
4363 (token_rows, slots, weights)
4364 })
4365 .collect::<Vec<_>>();
4366
4367 combine.ready = false;
4368 combine.output_generation = None;
4369 combine.broadcast_generation = None;
4370 let root = self
4371 .ranks
4372 .first()
4373 .ok_or("Step owner-grouped combine has no root rank")?;
4374 let _main = root.gpu.enter_main()?;
4375 if root.ctx().ordinal() != combine.root_device {
4376 return Err(format!(
4377 "Step owner-grouped combine root device changed {} != {}",
4378 root.ctx().ordinal(),
4379 combine.root_device
4380 )
4381 .into());
4382 }
4383 for (owner, (token_rows, slots, weights)) in combine.owners.iter_mut().zip(metadata) {
4384 if token_rows.is_empty() {
4385 owner.active_pairs = 0;
4386 continue;
4387 }
4388 root.htod_i32_into(&mut owner.token_rows, &token_rows)?;
4389 root.htod_i32_into(&mut owner.slots, &slots)?;
4390 let mut weight_prefix = owner.weights.slice_mut(0..weights.len());
4391 root.stream().memcpy_htod(&weights, &mut weight_prefix)?;
4392 owner.active_pairs = token_rows.len();
4393 }
4394 combine.tokens = plan.tokens;
4395 combine.pairs = shape.pairs;
4396 combine.projection_generation = plan.generation;
4397 combine.ready = true;
4398 Ok(())
4399 }
4400
4401 pub fn execute_step_grouped_expert_parallel_combine(
4402 &self,
4403 plan: &PreparedStepGroupedExpertParallelGate,
4404 combine: &mut PreparedPeerWeightedRouteCombine,
4405 ) -> Result<(), Box<dyn std::error::Error>> {
4406 if !plan.ready
4407 || plan.executed_generation != Some(plan.generation)
4408 || !combine.ready
4409 || combine.tokens != plan.tokens
4410 || combine.pairs != plan.pairs
4411 || combine.width != plan.input_width
4412 || combine.owners.len() != plan.owners.len()
4413 || combine.projection_generation != plan.generation
4414 {
4415 return Err("Step owner-grouped combine is stale or its geometry changed".into());
4416 }
4417 combine.output_generation = None;
4418 combine.broadcast_generation = None;
4419 for owner in &plan.owners {
4420 if owner.rank == 0 || owner.global_pairs.is_empty() {
4421 continue;
4422 }
4423 let engine = &self.ranks[owner.rank];
4424 let _main = engine.gpu.enter_main()?;
4425 engine.stream().synchronize()?;
4426 }
4427 let root = self
4428 .ranks
4429 .first()
4430 .ok_or("Step owner-grouped combine has no root rank")?;
4431 let _main = root.gpu.enter_main()?;
4432 if root.ctx().ordinal() != combine.root_device {
4433 return Err("Step owner-grouped combine is not resident on the root device".into());
4434 }
4435 for (index, owner) in plan.owners.iter().enumerate() {
4436 let metadata = &combine.owners[index];
4437 if owner.global_pairs.len() != metadata.active_pairs {
4438 return Err(format!(
4439 "Step owner-grouped combine owner {index} rows {} != metadata {}",
4440 owner.global_pairs.len(),
4441 metadata.active_pairs
4442 )
4443 .into());
4444 }
4445 if metadata.active_pairs == 0 {
4446 continue;
4447 }
4448 let values = metadata
4449 .active_pairs
4450 .checked_mul(combine.width)
4451 .ok_or("Step owner-grouped combine peer value count overflow")?;
4452 if owner.rank == 0 {
4453 root.scatter_slot(
4454 owner.down_workspace.output(),
4455 &metadata.token_rows,
4456 &metadata.slots,
4457 &metadata.weights,
4458 &mut combine.slots,
4459 &mut combine.weights,
4460 combine.width,
4461 combine.experts_per_token,
4462 metadata.active_pairs,
4463 )?;
4464 } else {
4465 let source = owner.down_workspace.output().slice(0..values);
4466 let mut destination = combine.peer_staging.slice_mut(0..values);
4467 root.stream().memcpy_dtod(&source, &mut destination)?;
4468 root.scatter_slot(
4469 &combine.peer_staging,
4470 &metadata.token_rows,
4471 &metadata.slots,
4472 &metadata.weights,
4473 &mut combine.slots,
4474 &mut combine.weights,
4475 combine.width,
4476 combine.experts_per_token,
4477 metadata.active_pairs,
4478 )?;
4479 }
4480 }
4481 root.reduce_slots_host(
4482 &combine.slots,
4483 &combine.weights,
4484 &mut combine.output,
4485 combine.width,
4486 combine.experts_per_token,
4487 combine.tokens,
4488 )?;
4489 combine.output_generation = Some(plan.generation);
4490 Ok(())
4491 }
4492
4493 pub fn collect_step_grouped_expert_parallel_combine(
4494 &self,
4495 plan: &PreparedStepGroupedExpertParallelGate,
4496 combine: &PreparedPeerWeightedRouteCombine,
4497 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4498 if !plan.ready
4499 || combine.output_generation != Some(plan.generation)
4500 || combine.projection_generation != plan.generation
4501 {
4502 return Err("Step owner-grouped combine output is stale or has not executed".into());
4503 }
4504 let root = self
4505 .ranks
4506 .first()
4507 .ok_or("Step owner-grouped combine has no root rank")?;
4508 let _main = root.gpu.enter_main()?;
4509 if root.ctx().ordinal() != combine.root_device {
4510 return Err("Step owner-grouped combine is not resident on the root device".into());
4511 }
4512 root.dtoh_view(&combine.output.slice(0..combine.tokens * combine.width))
4513 }
4514
4515 pub fn copy_step_grouped_expert_parallel_combine_root(
4520 &self,
4521 plan: &PreparedStepGroupedExpertParallelGate,
4522 combine: &PreparedPeerWeightedRouteCombine,
4523 destination: &Engine,
4524 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4525 if !plan.ready
4526 || combine.output_generation != Some(plan.generation)
4527 || combine.projection_generation != plan.generation
4528 {
4529 return Err("Step owner-grouped combine output is stale or has not executed".into());
4530 }
4531 let root = self
4532 .ranks
4533 .first()
4534 .ok_or("Step owner-grouped combine has no root rank")?;
4535 if root.ctx().ordinal() != combine.root_device
4536 || destination.ctx().ordinal() != combine.root_device
4537 {
4538 return Err(format!(
4539 "Step owner-grouped combine root/destination devices {}/{} != {}",
4540 root.ctx().ordinal(),
4541 destination.ctx().ordinal(),
4542 combine.root_device,
4543 )
4544 .into());
4545 }
4546 let values = combine
4547 .tokens
4548 .checked_mul(combine.width)
4549 .ok_or("Step owner-grouped combine copy size overflow")?;
4550 {
4551 let _main = root.gpu.enter_main()?;
4552 root.stream().synchronize()?;
4553 }
4554 let _main = destination.gpu.enter_main()?;
4555 let mut output = destination.uninit(values)?;
4556 destination
4557 .stream()
4558 .memcpy_dtod(&combine.output.slice(0..values), &mut output)?;
4559 Ok(output)
4560 }
4561
4562 pub fn broadcast_step_grouped_expert_parallel_combine(
4563 &self,
4564 plan: &PreparedStepGroupedExpertParallelGate,
4565 combine: &mut PreparedPeerWeightedRouteCombine,
4566 ) -> Result<(), Box<dyn std::error::Error>> {
4567 if !plan.ready
4568 || combine.output_generation != Some(plan.generation)
4569 || combine.projection_generation != plan.generation
4570 || combine.peer_devices.len() + 1 != self.ranks.len()
4571 || combine.peer_outputs.len() + 1 != self.ranks.len()
4572 {
4573 return Err("Step owner-grouped combine output cannot be broadcast".into());
4574 }
4575 combine.broadcast_generation = None;
4576 let values = combine
4577 .tokens
4578 .checked_mul(combine.width)
4579 .ok_or("Step owner-grouped combine broadcast size overflow")?;
4580 {
4581 let root = self
4582 .ranks
4583 .first()
4584 .ok_or("Step owner-grouped combine has no root rank")?;
4585 let _main = root.gpu.enter_main()?;
4586 if root.ctx().ordinal() != combine.root_device {
4587 return Err("Step owner-grouped combine root device changed".into());
4588 }
4589 root.stream().synchronize()?;
4590 }
4591 let source = &combine.output;
4592 for (index, destination_buffer) in combine.peer_outputs.iter_mut().enumerate() {
4593 let engine = &self.ranks[index + 1];
4594 let _main = engine.gpu.enter_main()?;
4595 if engine.ctx().ordinal() != combine.peer_devices[index] {
4596 return Err(format!(
4597 "Step owner-grouped combine peer {} device changed",
4598 index + 1
4599 )
4600 .into());
4601 }
4602 let mut destination = destination_buffer.slice_mut(0..values);
4603 engine
4604 .stream()
4605 .memcpy_dtod(&source.slice(0..values), &mut destination)?;
4606 }
4607 combine.broadcast_generation = Some(plan.generation);
4608 Ok(())
4609 }
4610
4611 pub fn collect_step_grouped_expert_parallel_broadcast(
4612 &self,
4613 plan: &PreparedStepGroupedExpertParallelGate,
4614 combine: &PreparedPeerWeightedRouteCombine,
4615 ) -> Result<Vec<Vec<f32>>, Box<dyn std::error::Error>> {
4616 if !plan.ready
4617 || combine.output_generation != Some(plan.generation)
4618 || combine.broadcast_generation != Some(plan.generation)
4619 || combine.peer_outputs.len() + 1 != self.ranks.len()
4620 {
4621 return Err("Step owner-grouped combine broadcast is stale or incomplete".into());
4622 }
4623 let values = combine
4624 .tokens
4625 .checked_mul(combine.width)
4626 .ok_or("Step owner-grouped combine collection size overflow")?;
4627 let mut outputs = Vec::with_capacity(self.ranks.len());
4628 {
4629 let root = &self.ranks[0];
4630 let _main = root.gpu.enter_main()?;
4631 outputs.push(root.dtoh_view(&combine.output.slice(0..values))?);
4632 }
4633 for (index, output) in combine.peer_outputs.iter().enumerate() {
4634 let engine = &self.ranks[index + 1];
4635 let _main = engine.gpu.enter_main()?;
4636 outputs.push(engine.dtoh_view(&output.slice(0..values))?);
4637 }
4638 Ok(outputs)
4639 }
4640
4641 pub fn finish_step_grouped_expert_parallel_layer(
4643 &self,
4644 plan: &PreparedStepGroupedExpertParallelGate,
4645 combine: &PreparedPeerWeightedRouteCombine,
4646 shared: &ResidentReplicatedDeviceRows,
4647 residual: &ResidentReplicatedDeviceRows,
4648 ) -> Result<ResidentReplicatedDeviceRows, Box<dyn std::error::Error>> {
4649 validate_replicated_device_rows(&self.ranks, shared)?;
4650 validate_replicated_device_rows(&self.ranks, residual)?;
4651 if !plan.ready
4652 || plan.executed_generation != Some(plan.generation)
4653 || combine.output_generation != Some(plan.generation)
4654 || combine.broadcast_generation != Some(plan.generation)
4655 || combine.projection_generation != plan.generation
4656 || combine.peer_outputs.len() + 1 != self.ranks.len()
4657 || shared.tokens != combine.tokens
4658 || residual.tokens != combine.tokens
4659 || shared.width != combine.width
4660 || residual.width != combine.width
4661 {
4662 return Err("Step full-layer finish inputs are stale or their geometry changed".into());
4663 }
4664 let values = combine
4665 .tokens
4666 .checked_mul(combine.width)
4667 .ok_or("Step full-layer output size overflow")?;
4668 let mut ranks = Vec::with_capacity(self.ranks.len());
4669 for rank in 0..self.ranks.len() {
4670 let engine = &self.ranks[rank];
4671 let _main = engine.gpu.enter_main()?;
4672 let routed = if rank == 0 {
4673 &combine.output
4674 } else {
4675 &combine.peer_outputs[rank - 1]
4676 };
4677 let mut ffn = engine.uninit(values)?;
4678 engine.add(routed, &shared.ranks[rank], &mut ffn, values)?;
4679 let mut output = engine.uninit(values)?;
4680 engine.add(&residual.ranks[rank], &ffn, &mut output, values)?;
4681 ranks.push(output);
4682 }
4683 Ok(ResidentReplicatedDeviceRows {
4684 ranks,
4685 tokens: combine.tokens,
4686 width: combine.width,
4687 })
4688 }
4689
4690 pub fn run_step_grouped_expert_parallel_combine(
4691 &self,
4692 plan: &PreparedStepGroupedExpertParallelGate,
4693 combine: &mut PreparedPeerWeightedRouteCombine,
4694 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4695 self.execute_step_grouped_expert_parallel_combine(plan, combine)?;
4696 self.collect_step_grouped_expert_parallel_combine(plan, combine)
4697 }
4698
4699 pub fn upload_tensor_parallel(
4700 &self,
4701 gate: E4m3ExpertBank<'_>,
4702 up: E4m3ExpertBank<'_>,
4703 down: E4m3ExpertBank<'_>,
4704 ) -> Result<ResidentTensorParallel, Box<dyn std::error::Error>> {
4705 gate.validate()?;
4706 up.validate()?;
4707 down.validate()?;
4708 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
4709 return Err("TP gate/up/down expert counts differ".into());
4710 }
4711 if gate.in_features != up.in_features || gate.out_features != up.out_features {
4712 return Err("TP gate/up dimensions differ".into());
4713 }
4714 if down.in_features != gate.out_features || down.out_features != gate.in_features {
4715 return Err(format!(
4716 "TP down {}x{} does not invert gate/up {}x{}",
4717 down.out_features, down.in_features, gate.out_features, gate.in_features
4718 )
4719 .into());
4720 }
4721 let tp = self.ranks.len();
4722 validate_column_bank_shape(gate, tp)?;
4723 validate_column_bank_shape(up, tp)?;
4724 validate_row_bank_shape(down, tp)?;
4725
4726 let mut gate_ranks = Vec::with_capacity(tp);
4727 let mut up_ranks = Vec::with_capacity(tp);
4728 let mut down_ranks = Vec::with_capacity(tp);
4729 for (rank, engine) in self.ranks.iter().enumerate() {
4730 gate_ranks.push(upload_column_bank_rank(engine, gate, tp, rank)?);
4731 up_ranks.push(upload_column_bank_rank(engine, up, tp, rank)?);
4732 down_ranks.push(upload_row_bank_rank(engine, down, tp, rank)?);
4733 }
4734 Ok(ResidentTensorParallel {
4735 bank: ResidentTpExpertBank {
4736 gate: gate_ranks,
4737 up: up_ranks,
4738 down: down_ranks,
4739 expert_count: gate.expert_count,
4740 input_width: gate.in_features,
4741 expert_width: gate.out_features,
4742 },
4743 })
4744 }
4745
4746 pub fn run_tensor_parallel_routes(
4747 &self,
4748 experts: &ResidentTensorParallel,
4749 input: &[f32],
4750 tokens: usize,
4751 selected: &[usize],
4752 route_weights: &[f32],
4753 experts_per_token: usize,
4754 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4755 validate_tp_bank_residency(&self.ranks, &experts.bank)?;
4756 validate_activations(input, tokens, experts.bank.input_width)?;
4757 let pairs = tokens
4758 .checked_mul(experts_per_token)
4759 .ok_or("TP route count overflow")?;
4760 if selected.len() != pairs || route_weights.len() != pairs {
4761 return Err(format!(
4762 "TP routes selected={} weights={} != tokens {tokens} x experts/token \
4763 {experts_per_token} ({pairs})",
4764 selected.len(),
4765 route_weights.len(),
4766 )
4767 .into());
4768 }
4769 if !route_weights.iter().all(|weight| weight.is_finite()) {
4770 return Err("TP route weights contain a non-finite value".into());
4771 }
4772
4773 let mut output = vec![0.0f32; tokens * experts.bank.input_width];
4774 for token in 0..tokens {
4775 let input_row =
4776 &input[token * experts.bank.input_width..(token + 1) * experts.bank.input_width];
4777 for slot in 0..experts_per_token {
4778 let pair = token * experts_per_token + slot;
4779 let expert = selected[pair];
4780 if expert >= experts.bank.expert_count {
4781 return Err(format!(
4782 "TP selected expert {expert} outside 0..{}",
4783 experts.bank.expert_count
4784 )
4785 .into());
4786 }
4787 let down = if self.native_p2p {
4788 self.run_tensor_parallel_expert_native(&experts.bank, expert, input_row)?
4789 } else {
4790 let gate =
4791 self.run_column_bank_expert(&experts.bank.gate, expert, input_row)?;
4792 let up = self.run_column_bank_expert(&experts.bank.up, expert, input_row)?;
4793 let activated: Vec<f32> = gate
4794 .iter()
4795 .zip(&up)
4796 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4797 .collect();
4798 debug_assert_eq!(activated.len(), experts.bank.expert_width);
4799 self.run_row_bank_expert(&experts.bank.down, expert, &activated)?
4800 };
4801 let weight = route_weights[pair];
4802 for (sum, value) in output
4803 [token * experts.bank.input_width..(token + 1) * experts.bank.input_width]
4804 .iter_mut()
4805 .zip(down)
4806 {
4807 *sum += weight * value;
4808 }
4809 }
4810 }
4811 Ok(output)
4812 }
4813
4814 fn run_column_bank_expert(
4815 &self,
4816 ranks: &[ResidentE4m3ExpertBankRank],
4817 expert: usize,
4818 input: &[f32],
4819 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4820 let local_out = ranks
4821 .first()
4822 .ok_or("TP column bank has no ranks")?
4823 .out_features;
4824 let mut gathered = vec![0.0f32; local_out * ranks.len()];
4825 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
4826 let shard = run_resident_bank_expert(engine, bank, expert, input, 1)?;
4827 gathered[rank * local_out..(rank + 1) * local_out].copy_from_slice(&shard);
4828 }
4829 Ok(gathered)
4830 }
4831
4832 fn run_row_bank_expert(
4833 &self,
4834 ranks: &[ResidentE4m3ExpertBankRank],
4835 expert: usize,
4836 input: &[f32],
4837 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4838 let local_in = ranks.first().ok_or("TP row bank has no ranks")?.in_features;
4839 if input.len() != local_in * ranks.len() {
4840 return Err(format!(
4841 "TP row input {} != {} ranks x {local_in}",
4842 input.len(),
4843 ranks.len()
4844 )
4845 .into());
4846 }
4847 let out_features = ranks[0].out_features;
4848 let mut reduced = vec![0.0f32; out_features];
4849 for (rank, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
4850 let blocks = bank
4851 .k_blocks
4852 .ok_or("TP row bank is not packed in native K-block order")?;
4853 if blocks * FP8_BLOCK != local_in {
4854 return Err(format!(
4855 "TP row bank has {blocks} blocks but local input width is {local_in}"
4856 )
4857 .into());
4858 }
4859 for block in 0..blocks {
4860 let global_start = rank * local_in + block * FP8_BLOCK;
4861 let partial = run_resident_bank_expert_block(
4862 engine,
4863 bank,
4864 expert,
4865 block,
4866 &input[global_start..global_start + FP8_BLOCK],
4867 )?;
4868 for (sum, value) in reduced.iter_mut().zip(partial) {
4869 *sum += value;
4870 }
4871 }
4872 }
4873 Ok(reduced)
4874 }
4875
4876 fn run_tensor_parallel_expert_native(
4877 &self,
4878 bank: &ResidentTpExpertBank,
4879 expert: usize,
4880 input: &[f32],
4881 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4882 if !self.native_p2p || self.ranks.len() < 2 {
4883 return Err("native TP expert execution requires at least two P2P ranks".into());
4884 }
4885 let local_out = bank
4886 .gate
4887 .first()
4888 .ok_or("native TP gate bank has no ranks")?
4889 .out_features;
4890 if local_out * self.ranks.len() != bank.expert_width {
4891 return Err(format!(
4892 "native TP gate shards {}x{local_out} != expert width {}",
4893 self.ranks.len(),
4894 bank.expert_width
4895 )
4896 .into());
4897 }
4898
4899 let mut rank_inputs = Vec::with_capacity(self.ranks.len());
4902 let root_input = {
4903 let root = &self.ranks[0];
4904 let _main = root.gpu.enter_main()?;
4905 root.htod(input)?
4906 };
4907 rank_inputs.push(root_input);
4908 for engine in &self.ranks[1..] {
4909 let peer_input = {
4910 let _main = engine.gpu.enter_main()?;
4911 let mut peer_input = engine.uninit(input.len())?;
4912 engine
4913 .stream()
4914 .memcpy_dtod(&rank_inputs[0], &mut peer_input)?;
4915 peer_input
4916 };
4917 rank_inputs.push(peer_input);
4918 }
4919
4920 let mut gate_shards = Vec::with_capacity(self.ranks.len());
4921 let mut up_shards = Vec::with_capacity(self.ranks.len());
4922 for rank in 0..self.ranks.len() {
4923 gate_shards.push(run_resident_bank_expert_device(
4924 &self.ranks[rank],
4925 &bank.gate[rank],
4926 expert,
4927 &rank_inputs[rank],
4928 1,
4929 )?);
4930 up_shards.push(run_resident_bank_expert_device(
4931 &self.ranks[rank],
4932 &bank.up[rank],
4933 expert,
4934 &rank_inputs[rank],
4935 1,
4936 )?);
4937 }
4938
4939 let gate = self.gather_native_column_shards(&gate_shards, 1, local_out)?;
4943 let up = self.gather_native_column_shards(&up_shards, 1, local_out)?;
4944 let activated = gate
4945 .iter()
4946 .zip(&up)
4947 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
4948 .collect::<Vec<_>>();
4949 debug_assert_eq!(activated.len(), bank.expert_width);
4950
4951 let root_activated = {
4952 let root = &self.ranks[0];
4953 let _main = root.gpu.enter_main()?;
4954 root.htod(&activated)?
4955 };
4956 let mut rank_activated = Vec::with_capacity(self.ranks.len());
4957 for (rank, engine) in self.ranks.iter().enumerate() {
4958 let start = rank * local_out;
4959 let source = root_activated.slice(start..start + local_out);
4960 let local = {
4961 let _main = engine.gpu.enter_main()?;
4962 let mut local = engine.uninit(local_out)?;
4963 engine.stream().memcpy_dtod(&source, &mut local)?;
4964 local
4965 };
4966 rank_activated.push(local);
4967 }
4968
4969 let out_features = bank
4970 .down
4971 .first()
4972 .ok_or("native TP down bank has no ranks")?
4973 .out_features;
4974 let mut reduced = {
4975 let root = &self.ranks[0];
4976 let _main = root.gpu.enter_main()?;
4977 root.htod(&vec![0.0f32; out_features])?
4978 };
4979 let mut remote_partial_keepalive = Vec::new();
4980 for rank in 0..self.ranks.len() {
4981 let down = &bank.down[rank];
4982 let blocks = down
4983 .k_blocks
4984 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
4985 if blocks * FP8_BLOCK != local_out {
4986 return Err(format!(
4987 "native TP rank {rank} has {blocks} blocks but local activation width is \
4988 {local_out}"
4989 )
4990 .into());
4991 }
4992 for block in 0..blocks {
4993 let start = block * FP8_BLOCK;
4994 let input_block = rank_activated[rank].slice(start..start + FP8_BLOCK);
4995 let partial = run_resident_bank_expert_block_device(
4996 &self.ranks[rank],
4997 down,
4998 expert,
4999 block,
5000 &input_block,
5001 )?;
5002 let root_partial = if rank == 0 {
5003 partial
5004 } else {
5005 let root = &self.ranks[0];
5006 let _main = root.gpu.enter_main()?;
5007 let mut peer_partial = root.uninit(out_features)?;
5008 root.stream().memcpy_dtod(&partial, &mut peer_partial)?;
5009 remote_partial_keepalive.push(partial);
5010 peer_partial
5011 };
5012 let next = {
5013 let root = &self.ranks[0];
5014 let _main = root.gpu.enter_main()?;
5015 let mut next = root.uninit(out_features)?;
5016 root.add(&reduced, &root_partial, &mut next, out_features)?;
5017 next
5018 };
5019 reduced = next;
5020 }
5021 }
5022 let output = {
5023 let root = &self.ranks[0];
5024 let _main = root.gpu.enter_main()?;
5025 root.dtoh(&reduced)?
5026 };
5027 drop(remote_partial_keepalive);
5028 Ok(output)
5029 }
5030
5031 pub fn gather_native_column_shards_device(
5033 &self,
5034 shards: &[CudaSlice<f32>],
5035 tokens: usize,
5036 local_out: usize,
5037 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5038 let shard_len = tokens
5039 .checked_mul(local_out)
5040 .ok_or("native TP gather shard size overflow")?;
5041 if shards.len() != self.ranks.len() || shards.iter().any(|shard| shard.len() != shard_len) {
5042 return Err("native TP gather shard geometry mismatch".into());
5043 }
5044 for engine in &self.ranks[1..] {
5048 let _main = engine.gpu.enter_main()?;
5049 engine.stream().synchronize()?;
5050 }
5051 let root = &self.ranks[0];
5052 let _main = root.gpu.enter_main()?;
5053 let global_out = shards
5054 .len()
5055 .checked_mul(local_out)
5056 .ok_or("native TP gather output width overflow")?;
5057 let gathered_len = tokens
5058 .checked_mul(global_out)
5059 .ok_or("native TP gather output size overflow")?;
5060 let mut gathered = root.uninit(gathered_len)?;
5061 if self.bulk_p2p {
5062 root.place_rows_strided(&shards[0], &mut gathered, local_out, tokens, global_out, 0)?;
5063 if shards.len() > 1 {
5064 let mut staging = root.uninit(shard_len)?;
5065 for (rank, shard) in shards.iter().enumerate().skip(1) {
5066 root.stream().memcpy_dtod(shard, &mut staging)?;
5067 root.place_rows_strided(
5068 &staging,
5069 &mut gathered,
5070 local_out,
5071 tokens,
5072 global_out,
5073 rank * local_out,
5074 )?;
5075 }
5076 }
5077 } else {
5078 for token in 0..tokens {
5079 for (rank, shard) in shards.iter().enumerate() {
5080 let source = shard.slice(token * local_out..(token + 1) * local_out);
5081 let start = token * global_out + rank * local_out;
5082 let mut destination = gathered.slice_mut(start..start + local_out);
5083 root.stream().memcpy_dtod(&source, &mut destination)?;
5084 }
5085 }
5086 }
5087 Ok(gathered)
5088 }
5089
5090 pub fn gather_native_column_shards(
5091 &self,
5092 shards: &[CudaSlice<f32>],
5093 tokens: usize,
5094 local_out: usize,
5095 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
5096 let gathered = self.gather_native_column_shards_device(shards, tokens, local_out)?;
5097 let root = &self.ranks[0];
5098 let _main = root.gpu.enter_main()?;
5099 root.dtoh(&gathered)
5100 }
5101
5102 pub(crate) fn decode_v2_workspace(&self) -> &std::sync::Mutex<Vec<StepTpDecodeV2Ws>> {
5103 &self.decode_v2
5104 }
5105
5106 pub(crate) fn decode_v2_ensure(
5115 &self,
5116 e: &Engine,
5117 q_m: &ResidentBf16ColumnParallel,
5118 k_m: &ResidentBf16ColumnParallel,
5119 v_m: &ResidentBf16ColumnParallel,
5120 o_m: &ResidentStepBf16RowParallel,
5121 heads: usize,
5122 ) -> Result<usize, Box<dyn std::error::Error>> {
5123 if self.ranks.len() > 1 && !self.native_p2p {
5124 return Err("step TP decode v2 requires native P2P ranks".into());
5125 }
5126 let ranks = self.ranks.len();
5127 let fused_door = step_tp_qkv_fused_enabled()?;
5131 let arm_ok = |weight: &ResidentBf16Weight| match weight {
5132 ResidentBf16Weight::F32(_) => true,
5133 ResidentBf16Weight::Bf16(_) => fused_door,
5134 };
5135 for matrix in [q_m, k_m, v_m] {
5136 validate_resident_bf16_ranks(&self.ranks, &matrix.ranks)?;
5137 if matrix.out_features % ranks != 0 || matrix.in_features != q_m.in_features {
5138 return Err("step TP decode v2 QKV geometry mismatch".into());
5139 }
5140 for rank in &matrix.ranks {
5141 if !arm_ok(&rank.weight) {
5142 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5143 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5144 .into());
5145 }
5146 }
5147 }
5148 validate_step_bf16_row_residency(&self.ranks, o_m)?;
5149 for blocks in &o_m.ranks {
5150 for block in blocks {
5151 if !arm_ok(&block.weight) {
5152 return Err("step TP decode v2 requires MEMRA_STEP_TP_F32_MIRROR=1 or \
5153 MEMRA_STEP_TP_QKV_FUSED=1 (bf16-resident fused kernels)"
5154 .into());
5155 }
5156 }
5157 }
5158 if v_m.out_features != k_m.out_features
5159 || o_m.in_features != q_m.out_features
5160 || heads == 0
5161 || heads % ranks != 0
5162 {
5163 return Err("step TP decode v2 K/V/O geometry mismatch".into());
5164 }
5165 let local_q_dim = q_m.out_features / ranks;
5166 let local_kv_dim = k_m.out_features / ranks;
5167 let o_out = o_m.out_features;
5168 let o_block_cols = o_m.canonical_chunk_cols;
5169 let blocks_per_rank = o_m.ranks.first().map(Vec::len).unwrap_or(0);
5170 if blocks_per_rank == 0
5171 || o_m
5172 .ranks
5173 .iter()
5174 .any(|blocks| blocks.len() != blocks_per_rank)
5175 || blocks_per_rank * o_block_cols * ranks != o_m.in_features
5176 {
5177 return Err("step TP decode v2 O canonical block grid mismatch".into());
5178 }
5179
5180 let mut guard = self
5181 .decode_v2
5182 .lock()
5183 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5184 if let Some(index) = guard.iter().position(|ws| {
5185 ws.local_q_dim == local_q_dim
5186 && ws.local_kv_dim == local_kv_dim
5187 && ws.heads == heads
5188 && ws.o_out == o_out
5189 && ws.o_block_cols == o_block_cols
5190 && ws.blocks_per_rank == blocks_per_rank
5191 && ws.e_device == e.ctx().ordinal()
5192 && ws.q.len() == ranks
5193 }) {
5194 return Ok(index);
5195 }
5196
5197 let mut q_raw = Vec::with_capacity(ranks);
5198 let mut k_raw = Vec::with_capacity(ranks);
5199 let mut v_raw = Vec::with_capacity(ranks);
5200 let mut q = Vec::with_capacity(ranks);
5201 let mut k = Vec::with_capacity(ranks);
5202 let mut pos = Vec::with_capacity(ranks);
5203 let mut gate = Vec::with_capacity(ranks);
5204 let mut attn_out = Vec::with_capacity(ranks);
5205 let mut gated = Vec::with_capacity(ranks);
5206 let mut fuse_ctr = Vec::with_capacity(ranks);
5207 let mut o_partials = Vec::with_capacity(ranks);
5208 let mut ev_rank = Vec::with_capacity(ranks);
5209 let direct_join = oproj_direct_on();
5210 for (rank, engine) in self.ranks.iter().enumerate() {
5211 let _main = engine.gpu.enter_main()?;
5212 q_raw.push(engine.uninit(local_q_dim)?);
5213 k_raw.push(engine.uninit(local_kv_dim)?);
5214 v_raw.push(engine.uninit(local_kv_dim)?);
5215 q.push(engine.uninit(local_q_dim)?);
5216 k.push(engine.uninit(local_kv_dim)?);
5217 pos.push(engine.htod_i32(&[0])?);
5218 fuse_ctr.push(engine.stream().clone_htod(&[0u32])?);
5219 gate.push(engine.uninit(heads / ranks)?);
5220 attn_out.push(engine.uninit(local_q_dim)?);
5221 gated.push(engine.uninit(local_q_dim)?);
5222 let mut rank_partials = Vec::with_capacity(blocks_per_rank);
5223 for _ in 0..blocks_per_rank {
5224 if direct_join && rank != 0 {
5227 let root = &self.ranks[0];
5228 let _root_main = root.gpu.enter_main()?;
5229 rank_partials.push(root.uninit(o_out)?);
5230 } else {
5231 rank_partials.push(engine.uninit(o_out)?);
5232 }
5233 }
5234 o_partials.push(rank_partials);
5235 ev_rank.push(engine.ctx().new_event(None)?);
5236 }
5237 let root = &self.ranks[0];
5238 let (peer_partial, reduce_a, reduce_b, zeros, k_shadow, v_shadow, ev_refresh, ev_oproj) = {
5239 let _main = root.gpu.enter_main()?;
5240 (
5241 root.uninit(o_out)?,
5242 root.uninit(o_out)?,
5243 root.uninit(o_out)?,
5244 root.htod(&vec![0.0f32; o_out])?,
5245 root.uninit(ranks * local_kv_dim)?,
5246 root.uninit(ranks * local_kv_dim)?,
5247 root.ctx().new_event(None)?,
5248 root.ctx().new_event(None)?,
5249 )
5250 };
5251 let (gate_e, ev_entry) = {
5252 let _main = e.gpu.enter_main()?;
5253 (e.uninit(heads)?, e.ctx().new_event(None)?)
5254 };
5255 let raw_attn_in = Vec::new();
5256 let raw_pos = Vec::new();
5257 guard.push(StepTpDecodeV2Ws {
5258 tcol_q: Vec::new(),
5259 tcol_k: Vec::new(),
5260 tcol_v: Vec::new(),
5261 tcol_g: Vec::new(),
5262 tcol_in: Vec::new(),
5263 tcol_cap: 0,
5264 fa2_q: Vec::new(),
5265 fa2_gate: Vec::new(),
5266 fa2_gated: Vec::new(),
5267 fa2_cap: 0,
5268 tcol_gated: Vec::new(),
5269 tcol_opart: Vec::new(),
5270 tcol_opeer: None,
5271 tcol_omix: None,
5272 tcol_ocap: 0,
5273 q_raw,
5274 k_raw,
5275 v_raw,
5276 q,
5277 k,
5278 pos,
5279 fuse_ctr,
5280 gate,
5281 attn_out,
5282 gated,
5283 o_partials,
5284 ev_rank,
5285 peer_partial,
5286 reduce_a,
5287 reduce_b,
5288 zeros,
5289 k_shadow,
5290 v_shadow,
5291 ev_refresh,
5292 ev_oproj,
5293 gate_e,
5294 attn_in: Vec::new(),
5295 h_stage: None,
5296 pos_stage: None,
5297 raw_h_stage: 0,
5298 raw_pos_stage: 0,
5299 raw_attn_in,
5300 raw_pos,
5301 raw_o_partial1: 0,
5302 raw_peer_partial: 0,
5303 raw_k1: 0,
5304 raw_v1: 0,
5305 raw_k_shadow: 0,
5306 raw_v_shadow: 0,
5307 raw_mixed_stage_e: 0,
5308 raw_reduce_a: 0,
5309 raw_shadow_stage_e: (0, 0),
5310 ev_entry,
5311 e_device: e.ctx().ordinal(),
5312 local_q_dim,
5313 local_kv_dim,
5314 heads,
5315 o_out,
5316 o_block_cols,
5317 blocks_per_rank,
5318 });
5319 eprintln!(
5320 "[step-tp-decode-v2] workspace ranks={ranks} local_q={local_q_dim} \
5321 local_kv={local_kv_dim} heads={heads} o_blocks={blocks_per_rank}x{o_block_cols} \
5322 residency=persistent ordering=evented performance_claim=false"
5323 );
5324 Ok(guard.len() - 1)
5325 }
5326
5327 #[allow(clippy::too_many_arguments)]
5335 #[allow(clippy::too_many_arguments)]
5340 pub fn decode_v2_input_qkv_tcol(
5341 &self,
5342 ws_index: usize,
5343 e: &Engine,
5344 h_t: &CudaSlice<f32>,
5345 t: usize,
5346 q_m: &ResidentBf16ColumnParallel,
5347 k_m: &ResidentBf16ColumnParallel,
5348 v_m: &ResidentBf16ColumnParallel,
5349 gate_shards: Option<StepTpGateShards<'_>>,
5350 ) -> Result<(), Box<dyn std::error::Error>> {
5351 let ranks = self.ranks.len();
5352 let mut guard = self
5353 .decode_v2
5354 .lock()
5355 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5356 let ws = guard
5357 .get_mut(ws_index)
5358 .ok_or("step TP decode v2 workspace index out of range")?;
5359 let in_f = q_m.in_features;
5360 if h_t.len() < t * in_f || t == 0 || t > 8 {
5361 return Err("decode_v2_input_qkv_tcol geometry".into());
5362 }
5363 if ws.tcol_cap < t || ws.tcol_q.len() != ranks {
5365 ws.tcol_q.clear();
5366 ws.tcol_k.clear();
5367 ws.tcol_v.clear();
5368 ws.tcol_g.clear();
5369 ws.tcol_in.clear();
5370 for engine in &self.ranks {
5371 let _m = engine.gpu.enter_main()?;
5372 ws.tcol_q.push(engine.uninit(8 * ws.local_q_dim)?);
5373 ws.tcol_k.push(engine.uninit(8 * ws.local_kv_dim)?);
5374 ws.tcol_v.push(engine.uninit(8 * ws.local_kv_dim)?);
5375 ws.tcol_g
5376 .push(engine.uninit(8 * (ws.heads / ranks).max(1))?);
5377 ws.tcol_in.push(engine.uninit(8 * in_f)?);
5378 }
5379 ws.tcol_cap = 8;
5380 }
5381 use cudarc::driver::DevicePtr;
5383 let raw_src = {
5384 let _main = e.gpu.enter_main()?;
5385 let stream = e.stream();
5386 let (p, _g) = h_t.device_ptr(&stream);
5387 ws.ev_entry.record(&stream)?;
5388 p as u64
5389 };
5390 for rank in 0..ranks {
5391 let engine = &self.ranks[rank];
5392 let _main = engine.gpu.enter_main()?;
5393 engine.stream().wait(&ws.ev_entry)?;
5394 let raw_dst = {
5395 let stream = engine.stream();
5396 let (p, _g) = ws.tcol_in[rank].device_ptr(&stream);
5397 p as u64
5398 };
5399 raw_copy_bytes(raw_dst, raw_src, t * in_f * 4, engine)?;
5400 let out_g = match &gate_shards {
5401 Some(_) => ws.heads / ranks,
5402 None => 0,
5403 };
5404 match (
5405 &q_m.ranks[rank].weight,
5406 &k_m.ranks[rank].weight,
5407 &v_m.ranks[rank].weight,
5408 ) {
5409 (
5410 ResidentBf16Weight::Bf16(wq),
5411 ResidentBf16Weight::Bf16(wk),
5412 ResidentBf16Weight::Bf16(wv),
5413 ) => {
5414 let wg = match &gate_shards {
5415 Some(StepTpGateShards::Bf16(shards)) => &shards[rank],
5416 Some(StepTpGateShards::F32(_)) => {
5417 return Err(
5418 "tcol verify: gate shard class does not match bf16 QKV".into()
5419 );
5420 }
5421 None => wq,
5422 };
5423 let StepTpDecodeV2Ws {
5424 tcol_q,
5425 tcol_k,
5426 tcol_v,
5427 tcol_g,
5428 tcol_in,
5429 local_q_dim,
5430 local_kv_dim,
5431 ..
5432 } = &mut *ws;
5433 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5436 let refk = *REFK
5437 .get_or_init(|| std::env::var("MEMRA_TCOL_REFKERN").as_deref() == Ok("1"));
5438 if refk {
5439 let lq = *local_q_dim;
5440 let lkv = *local_kv_dim;
5441 let mut hrow = engine.uninit(in_f)?;
5442 let mut qr = engine.uninit(lq)?;
5443 let mut kr = engine.uninit(lkv)?;
5444 let mut vr = engine.uninit(lkv)?;
5445 let mut gr = engine.uninit(out_g.max(1))?;
5446 for c in 0..t {
5447 {
5448 let mut dst = hrow.slice_mut(0..in_f);
5449 engine.stream().memcpy_dtod(
5450 &tcol_in[rank].slice(c * in_f..(c + 1) * in_f),
5451 &mut dst,
5452 )?;
5453 }
5454 engine.matvec_bf16_qkvg_into(
5455 wq, wk, wv, wg, &hrow, &mut qr, &mut kr, &mut vr, &mut gr, in_f,
5456 lq, lkv, out_g,
5457 )?;
5458 let stream = engine.stream();
5459 {
5460 let mut dst = tcol_q[rank].slice_mut(c * lq..(c + 1) * lq);
5461 stream.memcpy_dtod(&qr.slice(0..lq), &mut dst)?;
5462 }
5463 {
5464 let mut dst = tcol_k[rank].slice_mut(c * lkv..(c + 1) * lkv);
5465 stream.memcpy_dtod(&kr.slice(0..lkv), &mut dst)?;
5466 }
5467 {
5468 let mut dst = tcol_v[rank].slice_mut(c * lkv..(c + 1) * lkv);
5469 stream.memcpy_dtod(&vr.slice(0..lkv), &mut dst)?;
5470 }
5471 if out_g > 0 {
5472 let mut dst = tcol_g[rank].slice_mut(c * out_g..(c + 1) * out_g);
5473 stream.memcpy_dtod(&gr.slice(0..out_g), &mut dst)?;
5474 }
5475 }
5476 } else {
5477 engine.matvec_bf16_qkvg_tcol_into(
5478 wq,
5479 wk,
5480 wv,
5481 wg,
5482 &tcol_in[rank],
5483 &mut tcol_q[rank],
5484 &mut tcol_k[rank],
5485 &mut tcol_v[rank],
5486 &mut tcol_g[rank],
5487 in_f,
5488 *local_q_dim,
5489 *local_kv_dim,
5490 out_g,
5491 t,
5492 )?;
5493 }
5494 }
5495 _ => return Err("tcol verify requires bf16-resident fused QKV".into()),
5496 }
5497 }
5498 Ok(())
5499 }
5500
5501 pub(crate) fn decode_v2_oproj_tcol_eligible(
5505 &self,
5506 ws: &StepTpDecodeV2Ws,
5507 o_m: &ResidentStepBf16RowParallel,
5508 ) -> bool {
5509 self.ranks.len() == 2
5510 && ws.blocks_per_rank == 4
5511 && step_tp_qkv_fused_enabled().unwrap_or(false)
5512 && no_local_shadow_on()
5513 && std::env::var("MEMRA_B4_X2").as_deref() != Ok("1")
5514 && o_m
5515 .ranks
5516 .iter()
5517 .flatten()
5518 .all(|block| matches!(block.weight, ResidentBf16Weight::Bf16(_)))
5519 }
5520
5521 pub(crate) fn decode_v2_stash_fa2(
5526 &self,
5527 ws: &mut StepTpDecodeV2Ws,
5528 e: &Engine,
5529 col: usize,
5530 ) -> Result<(), Box<dyn std::error::Error>> {
5531 let ranks = self.ranks.len();
5532 if col >= 2 {
5533 return Err("decode_v2_stash_fa2 column out of range".into());
5534 }
5535 let lq = ws.local_q_dim;
5536 let lg = (ws.heads / ranks).max(1);
5537 if ws.fa2_cap == 0 || ws.fa2_q.len() != ranks {
5538 ws.fa2_q.clear();
5539 ws.fa2_gate.clear();
5540 ws.fa2_gated.clear();
5541 for engine in &self.ranks {
5542 let _m = engine.gpu.enter_main()?;
5543 ws.fa2_q.push(engine.uninit(2 * lq)?);
5544 ws.fa2_gate.push(engine.uninit(2 * lg)?);
5545 ws.fa2_gated.push(engine.uninit(2 * lq)?);
5546 }
5547 ws.fa2_cap = 2;
5548 }
5549 for rank in 0..ranks {
5550 let engine = &self.ranks[rank];
5551 let _main = engine.gpu.enter_main()?;
5552 {
5553 let mut dst = ws.fa2_q[rank].slice_mut(col * lq..(col + 1) * lq);
5554 engine
5555 .stream()
5556 .memcpy_dtod(&ws.q[rank].slice(0..lq), &mut dst)?;
5557 }
5558 {
5559 let mut dst = ws.fa2_gate[rank].slice_mut(col * lg..(col + 1) * lg);
5560 engine
5561 .stream()
5562 .memcpy_dtod(&ws.gate[rank].slice(0..lg), &mut dst)?;
5563 }
5564 ws.ev_rank[rank].record(&engine.stream())?;
5565 }
5566 {
5567 let _main = e.gpu.enter_main()?;
5568 for ev in ws.ev_rank.iter() {
5569 e.stream().wait(ev)?;
5570 }
5571 }
5572 Ok(())
5573 }
5574
5575 pub(crate) fn decode_v2_stash_gated(
5580 &self,
5581 ws: &mut StepTpDecodeV2Ws,
5582 e: &Engine,
5583 col: usize,
5584 ) -> Result<(), Box<dyn std::error::Error>> {
5585 let ranks = self.ranks.len();
5586 if col >= 8 {
5587 return Err("decode_v2_stash_gated column out of range".into());
5588 }
5589 let lq = ws.local_q_dim;
5590 if ws.tcol_ocap == 0 || ws.tcol_gated.len() != ranks {
5591 ws.tcol_gated.clear();
5592 ws.tcol_opart.clear();
5593 for engine in &self.ranks {
5594 let _m = engine.gpu.enter_main()?;
5595 ws.tcol_gated.push(engine.uninit(8 * lq)?);
5596 ws.tcol_opart.push(engine.uninit(8 * ws.o_out)?);
5597 }
5598 let root = &self.ranks[0];
5599 let _m = root.gpu.enter_main()?;
5600 ws.tcol_opeer = Some(root.uninit(8 * ws.o_out)?);
5601 ws.tcol_omix = Some(root.uninit(8 * ws.o_out)?);
5602 ws.tcol_ocap = 8;
5603 }
5604 for rank in 0..ranks {
5605 let engine = &self.ranks[rank];
5606 let _main = engine.gpu.enter_main()?;
5607 let mut dst = ws.tcol_gated[rank].slice_mut(col * lq..(col + 1) * lq);
5608 engine
5609 .stream()
5610 .memcpy_dtod(&ws.gated[rank].slice(0..lq), &mut dst)?;
5611 ws.ev_rank[rank].record(&engine.stream())?;
5615 }
5616 {
5617 let _main = e.gpu.enter_main()?;
5618 for ev in ws.ev_rank.iter() {
5619 e.stream().wait(ev)?;
5620 }
5621 }
5622 Ok(())
5623 }
5624
5625 pub(crate) fn decode_v2_oproj_tcol(
5631 &self,
5632 ws_index: usize,
5633 e: &Engine,
5634 o_m: &ResidentStepBf16RowParallel,
5635 t: usize,
5636 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5637 let ranks = self.ranks.len();
5638 let mut guard = self
5639 .decode_v2
5640 .lock()
5641 .map_err(|_| "step TP decode v2 workspace lock is poisoned")?;
5642 let ws = guard
5643 .get_mut(ws_index)
5644 .ok_or("step TP decode v2 workspace index out of range")?;
5645 if ranks != 2 || ws.blocks_per_rank != 4 || t == 0 || t > 8 || ws.tcol_ocap < t {
5646 return Err("decode_v2_oproj_tcol geometry".into());
5647 }
5648 for rank in 0..ranks {
5649 let engine = &self.ranks[rank];
5650 let _main = engine.gpu.enter_main()?;
5651 let mut weights = Vec::with_capacity(4);
5652 for block in 0..4 {
5653 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
5654 return Err("tcol o_proj requires bf16-resident O blocks".into());
5655 };
5656 weights.push(weight);
5657 }
5658 {
5659 let StepTpDecodeV2Ws {
5660 tcol_gated,
5661 tcol_opart,
5662 local_q_dim,
5663 o_block_cols,
5664 o_out,
5665 ..
5666 } = &mut *ws;
5667 static REFK: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5670 let refk = *REFK
5671 .get_or_init(|| std::env::var("MEMRA_TCOL_OPROJ_REF").as_deref() == Ok("1"));
5672 if refk {
5673 let lq = *local_q_dim;
5674 let mut xr = engine.uninit(lq)?;
5675 let mut yr = engine.uninit(*o_out)?;
5676 for c in 0..t {
5677 {
5678 let mut dst = xr.slice_mut(0..lq);
5679 engine.stream().memcpy_dtod(
5680 &tcol_gated[rank].slice(c * lq..(c + 1) * lq),
5681 &mut dst,
5682 )?;
5683 }
5684 engine.matvec_bf16_b4_into(
5685 [weights[0], weights[1], weights[2], weights[3]],
5686 &xr,
5687 &mut yr,
5688 *o_block_cols,
5689 *o_out,
5690 )?;
5691 let mut dst = tcol_opart[rank].slice_mut(c * *o_out..(c + 1) * *o_out);
5692 engine
5693 .stream()
5694 .memcpy_dtod(&yr.slice(0..*o_out), &mut dst)?;
5695 }
5696 } else {
5697 engine.matvec_bf16_b4_tcol_into(
5698 [weights[0], weights[1], weights[2], weights[3]],
5699 &tcol_gated[rank],
5700 &mut tcol_opart[rank],
5701 *o_block_cols,
5702 *o_out,
5703 t,
5704 )?;
5705 }
5706 }
5707 if rank != 0 {
5708 ws.ev_rank[rank].record(&engine.stream())?;
5709 }
5710 }
5711 let root = &self.ranks[0];
5712 {
5713 let _main = root.gpu.enter_main()?;
5714 for ev in ws.ev_rank.iter().skip(1) {
5715 root.stream().wait(ev)?;
5716 }
5717 {
5718 let StepTpDecodeV2Ws {
5719 tcol_opart,
5720 tcol_opeer,
5721 tcol_omix,
5722 o_out,
5723 ..
5724 } = &mut *ws;
5725 let opeer = tcol_opeer.as_mut().ok_or("tcol o_proj slabs not armed")?;
5726 let omix = tcol_omix.as_mut().ok_or("tcol o_proj slabs not armed")?;
5727 {
5728 let mut dst = opeer.slice_mut(0..t * *o_out);
5729 root.stream()
5730 .memcpy_dtod(&tcol_opart[1].slice(0..t * *o_out), &mut dst)?;
5731 }
5732 root.add(&tcol_opart[0], opeer, omix, t * *o_out)?;
5735 }
5736 ws.ev_oproj.record(&root.stream())?;
5737 }
5738 let _main = e.gpu.enter_main()?;
5739 e.stream().wait(&ws.ev_oproj)?;
5740 let mut out = e.uninit(t * ws.o_out)?;
5741 let omix = ws.tcol_omix.as_ref().ok_or("tcol o_proj slabs not armed")?;
5742 e.stream().memcpy_dtod(
5743 &omix.slice(0..t * ws.o_out),
5744 &mut out.slice_mut(0..t * ws.o_out),
5745 )?;
5746 Ok(out)
5747 }
5748
5749 pub(crate) fn decode_v2_input_qkv(
5750 &self,
5751 ws: &mut StepTpDecodeV2Ws,
5752 e: &Engine,
5753 h: &CudaSlice<f32>,
5754 pos_d: &CudaSlice<i32>,
5755 gate_raw: Option<&CudaSlice<f32>>,
5756 gate_shards: Option<StepTpGateShards<'_>>,
5757 decode_input: &mut ResidentReplicatedDeviceRows,
5758 q_m: &ResidentBf16ColumnParallel,
5759 k_m: &ResidentBf16ColumnParallel,
5760 v_m: &ResidentBf16ColumnParallel,
5761 q_norm: &[CudaSlice<f32>],
5762 k_norm: &[CudaSlice<f32>],
5763 head_dim: usize,
5764 n_rot: usize,
5765 rope_base: f32,
5766 rope_freqs: &[Option<&CudaSlice<f32>>],
5767 rms_eps: f32,
5768 defer_norm_rope: bool,
5769 tcol_col: Option<usize>,
5770 ) -> Result<(), Box<dyn std::error::Error>> {
5771 let ranks = self.ranks.len();
5772 validate_replicated_device_rows(&self.ranks, decode_input)?;
5773 if decode_input.tokens != 1
5774 || decode_input.width != q_m.in_features
5775 || pos_d.len() != 1
5776 || gate_raw.is_some_and(|gate| gate.len() != ws.heads)
5777 || gate_raw.is_none() != gate_shards.is_some()
5778 || gate_shards.as_ref().is_some_and(|shards| match shards {
5779 StepTpGateShards::F32(shards) => shards.len() != ranks,
5780 StepTpGateShards::Bf16(shards) => shards.len() != ranks,
5781 })
5782 || q_norm.len() != ranks
5783 || k_norm.len() != ranks
5784 || rope_freqs.len() != ranks
5785 || e.ctx().ordinal() != ws.e_device
5786 {
5787 return Err("step TP decode v2 input geometry mismatch".into());
5788 }
5789
5790 let qkv_fused = step_tp_qkv_fused_enabled()?;
5791 if gate_shards.is_some() && !qkv_fused {
5792 return Err("step TP decode v2 gate shards require MEMRA_STEP_TP_QKV_FUSED=1".into());
5793 }
5794 let values = decode_input.width;
5795 if h.len() != values {
5796 return Err(format!(
5797 "step TP decode v2 hidden width {} != replicated width {values}",
5798 h.len()
5799 )
5800 .into());
5801 }
5802
5803 if qkv_fused {
5804 if ws.h_stage.is_none() {
5808 use cudarc::driver::DevicePtr;
5809 let _main = e.gpu.enter_main()?;
5810 let h_stage = e.uninit(values)?;
5811 let pos_stage = e.htod_i32(&[0])?;
5812 {
5813 let stream = e.stream();
5814 let (hp, _g0) = h_stage.device_ptr(&stream);
5815 let (pp, _g1) = pos_stage.device_ptr(&stream);
5816 ws.raw_h_stage = hp as u64;
5817 ws.raw_pos_stage = pp as u64;
5818 }
5819 ws.h_stage = Some(h_stage);
5820 ws.pos_stage = Some(pos_stage);
5821 for rank in 0..ranks {
5822 use cudarc::driver::DevicePtr;
5823 let engine = &self.ranks[rank];
5824 let _rmain = engine.gpu.enter_main()?;
5825 let attn_in = engine.uninit(values)?;
5826 let (dp, pp) = {
5827 let stream = engine.stream();
5828 let (dp, _g2) = attn_in.device_ptr(&stream);
5829 let (pp, _g3) = ws.pos[rank].device_ptr(&stream);
5830 (dp as u64, pp as u64)
5831 };
5832 ws.raw_attn_in.push(dp);
5833 ws.raw_pos.push(pp);
5834 ws.attn_in.push(attn_in);
5835 }
5836 {
5837 use cudarc::driver::DevicePtr;
5838 let root = &self.ranks[0];
5839 let _rmain = root.gpu.enter_main()?;
5840 let stream = root.stream();
5841 let (a, _g) = ws.peer_partial.device_ptr(&stream);
5842 let (b, _g) = ws.k_shadow.device_ptr(&stream);
5843 let (c, _g) = ws.v_shadow.device_ptr(&stream);
5844 ws.raw_peer_partial = a as u64;
5845 ws.raw_k_shadow = b as u64;
5846 ws.raw_v_shadow = c as u64;
5847 }
5848 {
5849 use cudarc::driver::DevicePtr;
5850 let rank1 = &self.ranks[1];
5851 let _rmain = rank1.gpu.enter_main()?;
5852 let stream = rank1.stream();
5853 let (a, _g) = ws.o_partials[1][0].device_ptr(&stream);
5854 let (b, _g) = ws.k[1].device_ptr(&stream);
5855 let (c, _g) = ws.v_raw[1].device_ptr(&stream);
5856 ws.raw_o_partial1 = a as u64;
5857 ws.raw_k1 = b as u64;
5858 ws.raw_v1 = c as u64;
5859 }
5860 }
5861 {
5862 let _main = e.gpu.enter_main()?;
5863 {
5864 let h_stage = ws.h_stage.as_mut().expect("stage armed above");
5867 let mut dst = h_stage.slice_mut(0..values);
5868 e.stream().memcpy_dtod(&h.slice(0..values), &mut dst)?;
5869 }
5870 {
5871 let pos_stage = ws.pos_stage.as_mut().expect("stage armed above");
5872 let mut dst = pos_stage.slice_mut(0..1);
5873 e.stream().memcpy_dtod(&pos_d.slice(0..1), &mut dst)?;
5874 }
5875 ws.ev_entry.record(&e.stream())?;
5876 }
5877 for rank in 0..ranks {
5878 let engine = &self.ranks[rank];
5879 let _main = engine.gpu.enter_main()?;
5880 engine.stream().wait(&ws.ev_entry)?;
5881 }
5882 } else {
5883 {
5885 let _main = e.gpu.enter_main()?;
5886 if let Some(gate_raw) = gate_raw {
5887 let mut gate_dst = ws.gate_e.slice_mut(0..ws.heads);
5888 e.stream()
5889 .memcpy_dtod(&gate_raw.slice(0..ws.heads), &mut gate_dst)?;
5890 }
5891 ws.ev_entry.record(&e.stream())?;
5892 }
5893 {
5894 let root = &self.ranks[0];
5895 let _main = root.gpu.enter_main()?;
5896 root.stream().wait(&ws.ev_entry)?;
5897 let mut destination = decode_input.ranks[0].slice_mut(0..values);
5898 root.stream()
5899 .memcpy_dtod(&h.slice(0..values), &mut destination)?;
5900 ws.ev_refresh.record(&root.stream())?;
5901 }
5902 for rank in 1..ranks {
5903 let engine = &self.ranks[rank];
5904 let _main = engine.gpu.enter_main()?;
5905 engine.stream().wait(&ws.ev_refresh)?;
5906 let (root_rows, peer_rows) = decode_input.ranks.split_at_mut(rank);
5907 let mut destination = peer_rows[0].slice_mut(0..values);
5908 engine
5909 .stream()
5910 .memcpy_dtod(&root_rows[0].slice(0..values), &mut destination)?;
5911 }
5912 }
5913 for rank in 0..ranks {
5914 self.decode_v2_input_qkv_rank(
5915 ws,
5916 pos_d,
5917 decode_input,
5918 q_m,
5919 k_m,
5920 v_m,
5921 q_norm,
5922 k_norm,
5923 head_dim,
5924 n_rot,
5925 rope_base,
5926 rope_freqs,
5927 rms_eps,
5928 gate_shards.as_ref(),
5929 qkv_fused,
5930 defer_norm_rope,
5931 rank,
5932 tcol_col,
5933 )?;
5934 }
5935 Ok(())
5936 }
5937
5938 #[allow(clippy::too_many_arguments)]
5941 pub(crate) fn decode_v2_input_qkv_rank(
5942 &self,
5943 ws: &mut StepTpDecodeV2Ws,
5944 pos_d: &CudaSlice<i32>,
5945 decode_input: &mut ResidentReplicatedDeviceRows,
5946 q_m: &ResidentBf16ColumnParallel,
5947 k_m: &ResidentBf16ColumnParallel,
5948 v_m: &ResidentBf16ColumnParallel,
5949 q_norm: &[CudaSlice<f32>],
5950 k_norm: &[CudaSlice<f32>],
5951 head_dim: usize,
5952 n_rot: usize,
5953 rope_base: f32,
5954 rope_freqs: &[Option<&CudaSlice<f32>>],
5955 rms_eps: f32,
5956 gate_shards: Option<&StepTpGateShards<'_>>,
5957 qkv_fused: bool,
5958 defer_norm_rope: bool,
5959 rank: usize,
5960 tcol_col: Option<usize>,
5961 ) -> Result<(), Box<dyn std::error::Error>> {
5962 let ranks = self.ranks.len();
5963 let local_heads = ws.local_q_dim / head_dim;
5964 let local_kv_heads = ws.local_kv_dim / head_dim;
5965 let engine = &self.ranks[rank];
5966 let _main = engine.gpu.enter_main()?;
5967 let ws_e_device = ws.e_device;
5968 if qkv_fused && tcol_col.is_some() {
5973 let c = tcol_col.expect("checked");
5974 if ws.tcol_cap == 0 || ws.tcol_q.len() != ranks {
5975 return Err("tcol select without precompute".into());
5976 }
5977 if engine.ctx().ordinal() != ws_e_device {
5981 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
5982 }
5983 let StepTpDecodeV2Ws {
5984 tcol_q,
5985 tcol_k,
5986 tcol_v,
5987 tcol_g,
5988 q_raw,
5989 k_raw,
5990 v_raw,
5991 gate,
5992 local_q_dim,
5993 local_kv_dim,
5994 heads,
5995 ..
5996 } = &mut *ws;
5997 let lg = *heads / ranks;
5998 let stream = engine.stream();
5999 {
6000 let mut dst = q_raw[rank].slice_mut(0..*local_q_dim);
6001 stream.memcpy_dtod(
6002 &tcol_q[rank].slice(c * *local_q_dim..(c + 1) * *local_q_dim),
6003 &mut dst,
6004 )?;
6005 }
6006 {
6007 let mut dst = k_raw[rank].slice_mut(0..*local_kv_dim);
6008 stream.memcpy_dtod(
6009 &tcol_k[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6010 &mut dst,
6011 )?;
6012 }
6013 {
6014 let mut dst = v_raw[rank].slice_mut(0..*local_kv_dim);
6015 stream.memcpy_dtod(
6016 &tcol_v[rank].slice(c * *local_kv_dim..(c + 1) * *local_kv_dim),
6017 &mut dst,
6018 )?;
6019 }
6020 if lg > 0 {
6021 let mut dst = gate[rank].slice_mut(0..lg);
6022 stream.memcpy_dtod(&tcol_g[rank].slice(c * lg..(c + 1) * lg), &mut dst)?;
6023 }
6024 if !defer_norm_rope {
6025 } else {
6029 return Ok(());
6030 }
6031 }
6032 if qkv_fused {
6033 let same_dev = engine.ctx().ordinal() == ws.e_device;
6038 if !same_dev {
6039 raw_copy_bytes(
6040 ws.raw_attn_in[rank],
6041 ws.raw_h_stage,
6042 q_m.in_features * 4,
6043 engine,
6044 )?;
6045 raw_copy_bytes(ws.raw_pos[rank], ws.raw_pos_stage, 4, engine)?;
6046 }
6047 let StepTpDecodeV2Ws {
6048 q_raw,
6049 k_raw,
6050 v_raw,
6051 gate,
6052 gate_e,
6053 attn_in,
6054 h_stage,
6055 heads,
6056 local_q_dim,
6057 local_kv_dim,
6058 ..
6059 } = &mut *ws;
6060 let input_ref: &CudaSlice<f32> = if same_dev {
6061 h_stage
6062 .as_ref()
6063 .ok_or("step TP decode v2 stage not armed")?
6064 } else {
6065 &attn_in[rank]
6066 };
6067 match (
6068 &q_m.ranks[rank].weight,
6069 &k_m.ranks[rank].weight,
6070 &v_m.ranks[rank].weight,
6071 ) {
6072 (
6073 ResidentBf16Weight::F32(wq),
6074 ResidentBf16Weight::F32(wk),
6075 ResidentBf16Weight::F32(wv),
6076 ) => {
6077 let (wg, out_g) = match &gate_shards {
6078 Some(StepTpGateShards::F32(shards)) => (&shards[rank], *heads / ranks),
6079 Some(StepTpGateShards::Bf16(_)) => {
6080 return Err("step TP decode v2 gate shard class does not \
6081 match the F32 projections"
6082 .into());
6083 }
6084 None => (&*gate_e, 0),
6086 };
6087 engine.matvec_f32_qkv_into(
6088 wq,
6089 wk,
6090 wv,
6091 wg,
6092 input_ref,
6093 &mut q_raw[rank],
6094 &mut k_raw[rank],
6095 &mut v_raw[rank],
6096 &mut gate[rank],
6097 q_m.in_features,
6098 *local_q_dim,
6099 *local_kv_dim,
6100 out_g,
6101 )?;
6102 }
6103 (
6104 ResidentBf16Weight::Bf16(wq),
6105 ResidentBf16Weight::Bf16(wk),
6106 ResidentBf16Weight::Bf16(wv),
6107 ) => {
6108 let (wg, out_g) = match &gate_shards {
6109 Some(StepTpGateShards::Bf16(shards)) => (&shards[rank], *heads / ranks),
6110 Some(StepTpGateShards::F32(_)) => {
6111 return Err("step TP decode v2 gate shard class does not \
6112 match the bf16 projections"
6113 .into());
6114 }
6115 None => (wq, 0),
6116 };
6117 engine.matvec_bf16_qkvg_into(
6118 wq,
6119 wk,
6120 wv,
6121 wg,
6122 input_ref,
6123 &mut q_raw[rank],
6124 &mut k_raw[rank],
6125 &mut v_raw[rank],
6126 &mut gate[rank],
6127 q_m.in_features,
6128 *local_q_dim,
6129 *local_kv_dim,
6130 out_g,
6131 )?;
6132 }
6133 _ => {
6134 return Err("step TP decode v2 QKV projections mix residency classes".into());
6135 }
6136 }
6137 } else {
6138 for (matrix, local_out, raw) in [
6139 (q_m, ws.local_q_dim, &mut ws.q_raw),
6140 (k_m, ws.local_kv_dim, &mut ws.k_raw),
6141 (v_m, ws.local_kv_dim, &mut ws.v_raw),
6142 ] {
6143 let ResidentBf16Weight::F32(values_w) = &matrix.ranks[rank].weight else {
6144 return Err("step TP decode v2 lost its F32 projection residency".into());
6145 };
6146 let chunk_rows = matrix.canonical_chunk_rows.unwrap_or(local_out);
6147 engine.linear_f32_resident_canonical_rows_t1_into(
6148 &decode_input.ranks[rank],
6149 values_w,
6150 &mut raw[rank],
6151 matrix.in_features,
6152 local_out,
6153 chunk_rows,
6154 )?;
6155 }
6156 }
6157 if qkv_fused && defer_norm_rope {
6158 } else if qkv_fused {
6160 let StepTpDecodeV2Ws {
6163 q_raw,
6164 k_raw,
6165 q,
6166 k,
6167 pos,
6168 pos_stage,
6169 ..
6170 } = &mut *ws;
6171 let same_dev = engine.ctx().ordinal() == ws_e_device;
6172 let pos_ref: &CudaSlice<i32> = if same_dev {
6173 pos_stage
6174 .as_ref()
6175 .ok_or("step TP decode v2 pos stage not armed")?
6176 } else {
6177 &pos[rank]
6178 };
6179 engine.qk_norm_rope_into(
6180 &q_raw[rank],
6181 &k_raw[rank],
6182 &q_norm[rank],
6183 &k_norm[rank],
6184 &mut q[rank],
6185 &mut k[rank],
6186 pos_ref,
6187 head_dim,
6188 n_rot,
6189 local_heads,
6190 local_kv_heads,
6191 rms_eps,
6192 rope_base,
6193 1.0,
6194 rope_freqs[rank],
6195 )?;
6196 } else {
6197 engine.rms_norm(
6198 &ws.q_raw[rank],
6199 &q_norm[rank],
6200 &mut ws.q[rank],
6201 head_dim,
6202 local_heads,
6203 rms_eps,
6204 )?;
6205 engine.rms_norm(
6206 &ws.k_raw[rank],
6207 &k_norm[rank],
6208 &mut ws.k[rank],
6209 head_dim,
6210 local_kv_heads,
6211 rms_eps,
6212 )?;
6213 {
6214 let mut pos_dst = ws.pos[rank].slice_mut(0..1);
6215 engine
6216 .stream()
6217 .memcpy_dtod(&pos_d.slice(0..1), &mut pos_dst)?;
6218 }
6219 engine.rope_neox2(
6220 &mut ws.q[rank],
6221 &mut ws.k[rank],
6222 &ws.pos[rank],
6223 head_dim,
6224 n_rot,
6225 local_heads,
6226 local_kv_heads,
6227 1,
6228 rope_base,
6229 1.0,
6230 rope_freqs[rank],
6231 )?;
6232 }
6233 if gate_shards.is_none() {
6234 let gate_start = rank * (ws.heads / ranks);
6235 let mut gate_dst = ws.gate[rank].slice_mut(0..ws.heads / ranks);
6236 engine.stream().memcpy_dtod(
6237 &ws.gate_e.slice(gate_start..gate_start + ws.heads / ranks),
6238 &mut gate_dst,
6239 )?;
6240 }
6241 Ok(())
6242 }
6243
6244 pub(crate) fn decode_v2_finish_rank_partial(
6248 &self,
6249 ws: &mut StepTpDecodeV2Ws,
6250 o_m: &ResidentStepBf16RowParallel,
6251 o_fused: bool,
6252 rank: usize,
6253 ) -> Result<(), Box<dyn std::error::Error>> {
6254 let engine = &self.ranks[rank];
6255 let _main = engine.gpu.enter_main()?;
6256 if o_fused {
6257 let StepTpDecodeV2Ws {
6258 gated,
6259 o_partials,
6260 o_block_cols,
6261 o_out,
6262 ..
6263 } = &mut *ws;
6264 let all_f32 = o_m.ranks[rank]
6265 .iter()
6266 .all(|block| matches!(block.weight, ResidentBf16Weight::F32(_)));
6267 if all_f32 {
6268 let mut weights = Vec::with_capacity(4);
6269 for block in 0..4 {
6270 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6271 unreachable!("all_f32 checked above");
6272 };
6273 weights.push(weight);
6274 }
6275 engine.matvec_f32_b4_into(
6276 [weights[0], weights[1], weights[2], weights[3]],
6277 &gated[rank],
6278 &mut o_partials[rank][0],
6279 *o_block_cols,
6280 *o_out,
6281 )?;
6282 } else {
6283 let mut weights = Vec::with_capacity(4);
6284 for block in 0..4 {
6285 let ResidentBf16Weight::Bf16(weight) = &o_m.ranks[rank][block].weight else {
6286 return Err("step TP decode v2 O projections mix residency classes".into());
6287 };
6288 weights.push(weight);
6289 }
6290 engine.matvec_bf16_b4_into(
6291 [weights[0], weights[1], weights[2], weights[3]],
6292 &gated[rank],
6293 &mut o_partials[rank][0],
6294 *o_block_cols,
6295 *o_out,
6296 )?;
6297 }
6298 } else {
6299 for block in 0..ws.blocks_per_rank {
6300 let ResidentBf16Weight::F32(weight) = &o_m.ranks[rank][block].weight else {
6301 return Err("step TP decode v2 lost its F32 O residency".into());
6302 };
6303 let x =
6304 ws.gated[rank].slice(block * ws.o_block_cols..(block + 1) * ws.o_block_cols);
6305 let w = weight.slice(0..weight.len());
6306 let mut y = ws.o_partials[rank][block].slice_mut(0..ws.o_out);
6307 engine.linear_t1_into(&x, &w, &mut y, ws.o_block_cols, ws.o_out)?;
6308 }
6309 }
6310 Ok(())
6311 }
6312
6313 pub(crate) fn decode_v2_finish(
6321 &self,
6322 ws: &mut StepTpDecodeV2Ws,
6323 e: &Engine,
6324 o_m: &ResidentStepBf16RowParallel,
6325 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6326 let ranks = self.ranks.len();
6327 if e.ctx().ordinal() != ws.e_device {
6328 return Err("step TP decode v2 finish engine changed".into());
6329 }
6330 let o_fused = step_tp_qkv_fused_enabled()? && ws.blocks_per_rank == 4 && ranks == 2;
6335
6336 for rank in 0..ranks {
6339 self.decode_v2_finish_rank_partial(ws, o_m, o_fused, rank)?;
6340 if rank == 0 {
6341 continue;
6344 }
6345 let engine = &self.ranks[rank];
6346 let _main = engine.gpu.enter_main()?;
6347 ws.ev_rank[rank].record(&engine.stream())?;
6348 }
6349
6350 let root = &self.ranks[0];
6352 #[allow(unused_assignments)]
6353 let mut final_in_a = false;
6354 {
6355 let _main = root.gpu.enter_main()?;
6356 for ev in ws.ev_rank.iter().skip(1) {
6357 root.stream().wait(ev)?;
6358 }
6359 if o_fused && oproj_direct_on() && ranks == 2 && no_local_shadow_on() {
6360 ws.ev_oproj.record(&root.stream())?;
6366 let _main = e.gpu.enter_main()?;
6367 e.stream().wait(&ws.ev_oproj)?;
6368 let mut output = e.uninit(ws.o_out)?;
6369 if oproj_tail_on() && oproj_tail_eligible() {
6370 use cudarc::driver::DevicePtr;
6373 let stream = e.stream();
6374 let (p0, _g0) = ws.o_partials[0][0].device_ptr(&stream);
6375 let (p1, _g1) = ws.o_partials[1][0].device_ptr(&stream);
6376 set_oproj_tail((p0 as u64, p1 as u64));
6377 return Ok(output);
6378 }
6379 e.add(
6380 &ws.o_partials[0][0],
6381 &ws.o_partials[1][0],
6382 &mut output,
6383 ws.o_out,
6384 )?;
6385 return Ok(output);
6386 }
6387 if o_fused {
6388 self.decode_v2_finish_root_fused(ws)?;
6389 ws.ev_oproj.record(&root.stream())?;
6390 let _main = e.gpu.enter_main()?;
6391 e.stream().wait(&ws.ev_oproj)?;
6392 let mut output = e.uninit(ws.o_out)?;
6393 e.stream().memcpy_dtod(
6394 &ws.reduce_a.slice(0..ws.o_out),
6395 &mut output.slice_mut(0..ws.o_out),
6396 )?;
6397 return Ok(output);
6398 }
6399 let mut first = true;
6400 let mut current_is_a = false;
6401 for rank in 0..ranks {
6402 for block in 0..ws.blocks_per_rank {
6403 let use_peer = rank != 0;
6404 if use_peer {
6405 root.stream()
6406 .memcpy_dtod(&ws.o_partials[rank][block], &mut ws.peer_partial)?;
6407 }
6408 match (first, current_is_a, use_peer) {
6410 (true, _, true) => {
6411 root.add(&ws.zeros, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6412 }
6413 (true, _, false) => root.add(
6414 &ws.zeros,
6415 &ws.o_partials[0][block],
6416 &mut ws.reduce_a,
6417 ws.o_out,
6418 )?,
6419 (false, true, true) => {
6420 root.add(&ws.reduce_a, &ws.peer_partial, &mut ws.reduce_b, ws.o_out)?
6421 }
6422 (false, true, false) => root.add(
6423 &ws.reduce_a,
6424 &ws.o_partials[0][block],
6425 &mut ws.reduce_b,
6426 ws.o_out,
6427 )?,
6428 (false, false, true) => {
6429 root.add(&ws.reduce_b, &ws.peer_partial, &mut ws.reduce_a, ws.o_out)?
6430 }
6431 (false, false, false) => root.add(
6432 &ws.reduce_b,
6433 &ws.o_partials[0][block],
6434 &mut ws.reduce_a,
6435 ws.o_out,
6436 )?,
6437 }
6438 current_is_a = first || !current_is_a;
6439 first = false;
6440 }
6441 }
6442 final_in_a = current_is_a;
6443
6444 for rank in 0..ranks {
6445 let start = rank * ws.local_kv_dim;
6446 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
6447 root.stream().memcpy_dtod(&ws.k[rank], &mut k_dst)?;
6448 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
6449 root.stream().memcpy_dtod(&ws.v_raw[rank], &mut v_dst)?;
6450 }
6451 ws.ev_oproj.record(&root.stream())?;
6452 }
6453
6454 let _main = e.gpu.enter_main()?;
6458 e.stream().wait(&ws.ev_oproj)?;
6459 let mut output = e.uninit(ws.o_out)?;
6460 let source = if final_in_a {
6461 &ws.reduce_a
6462 } else {
6463 &ws.reduce_b
6464 };
6465 e.stream().memcpy_dtod(
6466 &source.slice(0..ws.o_out),
6467 &mut output.slice_mut(0..ws.o_out),
6468 )?;
6469 Ok(output)
6470 }
6471
6472 pub fn run_routed_experts(
6473 &self,
6474 experts: &ResidentExpertParallel,
6475 input: &[f32],
6476 tokens: usize,
6477 selected: &[usize],
6478 route_weights: &[f32],
6479 experts_per_token: usize,
6480 activation_limit: Option<f32>,
6481 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6482 validate_step_expert_activation_limit(activation_limit)?;
6483 validate_ep_residency(&self.ranks, experts)?;
6484 validate_activations(input, tokens, experts.input_width)?;
6485 let pairs = tokens
6486 .checked_mul(experts_per_token)
6487 .ok_or("EP route count overflow")?;
6488 if selected.len() != pairs || route_weights.len() != pairs {
6489 return Err(format!(
6490 "EP routes selected={} weights={} != tokens {tokens} x experts/token \
6491 {experts_per_token} ({pairs})",
6492 selected.len(),
6493 route_weights.len(),
6494 )
6495 .into());
6496 }
6497 if !route_weights.iter().all(|weight| weight.is_finite()) {
6498 return Err("EP route weights contain a non-finite value".into());
6499 }
6500 if self.native_p2p {
6501 return self.run_routed_experts_native(
6502 experts,
6503 input,
6504 tokens,
6505 selected,
6506 route_weights,
6507 experts_per_token,
6508 activation_limit,
6509 );
6510 }
6511
6512 let mut output = vec![0.0f32; tokens * experts.input_width];
6513 let per_rank = experts.expert_count / experts.ranks.len();
6514 for token in 0..tokens {
6515 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6516 for slot in 0..experts_per_token {
6517 let pair = token * experts_per_token + slot;
6518 let expert = selected[pair];
6519 if expert >= experts.expert_count {
6520 return Err(format!(
6521 "EP selected expert {expert} outside 0..{}",
6522 experts.expert_count
6523 )
6524 .into());
6525 }
6526 let owner = expert / per_rank;
6527 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6528 let rank = &experts.ranks[owner];
6529 let engine = &self.ranks[owner];
6530 let gate =
6531 run_resident_bank_expert(engine, &rank.gate, local_expert, input_row, 1)?;
6532 let up = run_resident_bank_expert(engine, &rank.up, local_expert, input_row, 1)?;
6533 let activated: Vec<f32> = gate
6534 .iter()
6535 .zip(&up)
6536 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6537 .collect();
6538 debug_assert_eq!(activated.len(), experts.expert_width);
6539 let down =
6540 run_resident_bank_expert(engine, &rank.down, local_expert, &activated, 1)?;
6541 let weight = route_weights[pair];
6542 for (sum, value) in output
6543 [token * experts.input_width..(token + 1) * experts.input_width]
6544 .iter_mut()
6545 .zip(down)
6546 {
6547 *sum += weight * value;
6548 }
6549 }
6550 }
6551 Ok(output)
6552 }
6553
6554 fn run_routed_experts_native(
6555 &self,
6556 experts: &ResidentExpertParallel,
6557 input: &[f32],
6558 tokens: usize,
6559 selected: &[usize],
6560 route_weights: &[f32],
6561 experts_per_token: usize,
6562 activation_limit: Option<f32>,
6563 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6564 if !self.native_p2p || self.ranks.len() < 2 {
6565 return Err("native EP execution requires at least two P2P ranks".into());
6566 }
6567 if self.ep_device_arithmetic {
6568 return self.run_routed_experts_native_device(
6569 experts,
6570 input,
6571 tokens,
6572 selected,
6573 route_weights,
6574 experts_per_token,
6575 activation_limit,
6576 );
6577 }
6578 let mut output = vec![0.0f32; tokens * experts.input_width];
6579 let per_rank = experts.expert_count / experts.ranks.len();
6580 for token in 0..tokens {
6581 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6582 let mut rank_inputs = (0..self.ranks.len())
6583 .map(|_| None)
6584 .collect::<Vec<Option<CudaSlice<f32>>>>();
6585 rank_inputs[0] = Some({
6586 let root = &self.ranks[0];
6587 let _main = root.gpu.enter_main()?;
6588 root.htod(input_row)?
6589 });
6590
6591 for slot in 0..experts_per_token {
6592 let pair = token * experts_per_token + slot;
6593 let expert = selected[pair];
6594 if expert >= experts.expert_count {
6595 return Err(format!(
6596 "EP selected expert {expert} outside 0..{}",
6597 experts.expert_count
6598 )
6599 .into());
6600 }
6601 let owner = expert / per_rank;
6602 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6603 if rank_inputs[owner].is_none() {
6604 let peer_input = {
6605 let root_input = rank_inputs[0]
6606 .as_ref()
6607 .ok_or("native EP lost its root input")?;
6608 let engine = &self.ranks[owner];
6609 let _main = engine.gpu.enter_main()?;
6610 let mut peer_input = engine.uninit(experts.input_width)?;
6611 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6612 peer_input
6613 };
6614 rank_inputs[owner] = Some(peer_input);
6615 }
6616
6617 let rank = &experts.ranks[owner];
6618 let engine = &self.ranks[owner];
6619 let owner_input = rank_inputs[owner]
6620 .as_ref()
6621 .ok_or("native EP owner input is absent after dispatch")?;
6622 let gate = run_resident_bank_expert_device(
6623 engine,
6624 &rank.gate,
6625 local_expert,
6626 owner_input,
6627 1,
6628 )?;
6629 let up = run_resident_bank_expert_device(
6630 engine,
6631 &rank.up,
6632 local_expert,
6633 owner_input,
6634 1,
6635 )?;
6636 let (gate, up) = {
6637 let _main = engine.gpu.enter_main()?;
6638 (engine.dtoh(&gate)?, engine.dtoh(&up)?)
6639 };
6640 let activated = gate
6641 .iter()
6642 .zip(&up)
6643 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
6644 .collect::<Vec<_>>();
6645 debug_assert_eq!(activated.len(), experts.expert_width);
6646 let activated = {
6647 let _main = engine.gpu.enter_main()?;
6648 engine.htod(&activated)?
6649 };
6650 let down = run_resident_bank_expert_device(
6651 engine,
6652 &rank.down,
6653 local_expert,
6654 &activated,
6655 1,
6656 )?;
6657 let down = if owner == 0 {
6658 let _main = engine.gpu.enter_main()?;
6659 engine.dtoh(&down)?
6660 } else {
6661 let root = &self.ranks[0];
6662 let _main = root.gpu.enter_main()?;
6663 let mut root_down = root.uninit(experts.input_width)?;
6664 root.stream().memcpy_dtod(&down, &mut root_down)?;
6665 root.dtoh(&root_down)?
6666 };
6667 let weight = route_weights[pair];
6668 for (sum, value) in output
6669 [token * experts.input_width..(token + 1) * experts.input_width]
6670 .iter_mut()
6671 .zip(down)
6672 {
6673 *sum += weight * value;
6674 }
6675 }
6676 }
6677 Ok(output)
6678 }
6679
6680 fn run_routed_experts_native_device(
6681 &self,
6682 experts: &ResidentExpertParallel,
6683 input: &[f32],
6684 tokens: usize,
6685 selected: &[usize],
6686 route_weights: &[f32],
6687 experts_per_token: usize,
6688 activation_limit: Option<f32>,
6689 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6690 if !self.native_p2p || !self.ep_device_arithmetic || self.ranks.len() < 2 {
6691 return Err(
6692 "device-resident EP arithmetic requires at least two native P2P ranks".into(),
6693 );
6694 }
6695 let mut output = Vec::with_capacity(tokens * experts.input_width);
6696 let per_rank = experts.expert_count / experts.ranks.len();
6697 let root = &self.ranks[0];
6698 for token in 0..tokens {
6699 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
6700 let mut rank_inputs = (0..self.ranks.len())
6701 .map(|_| None)
6702 .collect::<Vec<Option<CudaSlice<f32>>>>();
6703 rank_inputs[0] = Some({
6704 let _main = root.gpu.enter_main()?;
6705 root.htod(input_row)?
6706 });
6707 let mut root_output = {
6708 let _main = root.gpu.enter_main()?;
6709 root.zeros(experts.input_width)?
6710 };
6711 let mut remote_down_keepalive = Vec::new();
6712
6713 for slot in 0..experts_per_token {
6714 let pair = token * experts_per_token + slot;
6715 let expert = selected[pair];
6716 if expert >= experts.expert_count {
6717 return Err(format!(
6718 "EP selected expert {expert} outside 0..{}",
6719 experts.expert_count
6720 )
6721 .into());
6722 }
6723 let owner = expert / per_rank;
6724 let local_expert = expert - experts.ranks[owner].gate.expert_range.start;
6725 if rank_inputs[owner].is_none() {
6726 let peer_input = {
6727 let root_input = rank_inputs[0]
6728 .as_ref()
6729 .ok_or("native EP lost its root input")?;
6730 let engine = &self.ranks[owner];
6731 let _main = engine.gpu.enter_main()?;
6732 let mut peer_input = engine.uninit(experts.input_width)?;
6733 engine.stream().memcpy_dtod(root_input, &mut peer_input)?;
6734 peer_input
6735 };
6736 rank_inputs[owner] = Some(peer_input);
6737 }
6738
6739 let rank = &experts.ranks[owner];
6740 let engine = &self.ranks[owner];
6741 let owner_input = rank_inputs[owner]
6742 .as_ref()
6743 .ok_or("native EP owner input is absent after dispatch")?;
6744 let gate = run_resident_bank_expert_device(
6745 engine,
6746 &rank.gate,
6747 local_expert,
6748 owner_input,
6749 1,
6750 )?;
6751 let up = run_resident_bank_expert_device(
6752 engine,
6753 &rank.up,
6754 local_expert,
6755 owner_input,
6756 1,
6757 )?;
6758 let activated = {
6759 let _main = engine.gpu.enter_main()?;
6760 let mut activated = engine.uninit(experts.expert_width)?;
6761 if let Some(limit) = activation_limit {
6762 engine.silu_clamped_mul_host_expf(
6763 &gate,
6764 &up,
6765 limit,
6766 &mut activated,
6767 experts.expert_width,
6768 )?;
6769 } else {
6770 engine.silu_mul_host_expf(
6771 &gate,
6772 &up,
6773 &mut activated,
6774 experts.expert_width,
6775 )?;
6776 }
6777 activated
6778 };
6779 let down = run_resident_bank_expert_device(
6780 engine,
6781 &rank.down,
6782 local_expert,
6783 &activated,
6784 1,
6785 )?;
6786 let root_down = if owner == 0 {
6787 down
6788 } else {
6789 let _main = root.gpu.enter_main()?;
6790 let mut root_down = root.uninit(experts.input_width)?;
6791 root.stream().memcpy_dtod(&down, &mut root_down)?;
6792 remote_down_keepalive.push(down);
6796 root_down
6797 };
6798 let _main = root.gpu.enter_main()?;
6799 let mut destination = root_output.slice_mut(0..experts.input_width);
6800 root.axpy_host_into(
6801 &root_down.slice(0..root_down.len()),
6802 route_weights[pair],
6803 &mut destination,
6804 experts.input_width,
6805 )?;
6806 }
6807
6808 let _main = root.gpu.enter_main()?;
6809 let root_output = root.dtoh(&root_output)?;
6810 drop(remote_down_keepalive);
6811 output.extend(root_output);
6812 }
6813 Ok(output)
6814 }
6815}
6816
6817fn validate_column_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
6818 if matrix.out_features % tp != 0 {
6819 return Err(format!(
6820 "column-parallel out_features {} is not divisible by TP={tp}",
6821 matrix.out_features
6822 ));
6823 }
6824 let local_out = matrix.out_features / tp;
6825 if local_out % FP8_BLOCK != 0 {
6826 return Err(format!(
6827 "column-parallel output shard {local_out} cuts through a {FP8_BLOCK}-row \
6828 E4M3 scale block"
6829 ));
6830 }
6831 Ok(())
6832}
6833
6834fn step_bf16_canonical_chunk_rows(out_features: usize, tp: usize) -> Result<usize, String> {
6835 if !matches!(tp, 1 | 2 | 4 | 8) {
6836 return Err(format!(
6837 "Step BF16 canonical projection requires TP1/TP2/TP4/TP8, got TP={tp}"
6838 ));
6839 }
6840 if out_features == 0 || out_features % PRODUCT_MAX_CARDS != 0 {
6841 return Err(format!(
6842 "Step BF16 output width {out_features} is not divisible by the TP8 product envelope"
6843 ));
6844 }
6845 let canonical_rows = out_features / PRODUCT_MAX_CARDS;
6846 let local_out = out_features / tp;
6847 if local_out % canonical_rows != 0 {
6848 return Err(format!(
6849 "Step BF16 TP={tp} output shard {local_out} is not divisible by canonical \
6850 {canonical_rows}-row chunks"
6851 ));
6852 }
6853 Ok(canonical_rows)
6854}
6855
6856fn step_bf16_canonical_chunk_cols(in_features: usize, tp: usize) -> Result<usize, String> {
6857 if !matches!(tp, 1 | 2 | 4 | 8) {
6858 return Err(format!(
6859 "Step BF16 canonical row projection requires TP1/TP2/TP4/TP8, got TP={tp}"
6860 ));
6861 }
6862 if in_features == 0 || in_features % PRODUCT_MAX_CARDS != 0 {
6863 return Err(format!(
6864 "Step BF16 input width {in_features} is not divisible by the TP8 product envelope"
6865 ));
6866 }
6867 let canonical_cols = in_features / PRODUCT_MAX_CARDS;
6868 let local_in = in_features / tp;
6869 if local_in % canonical_cols != 0 {
6870 return Err(format!(
6871 "Step BF16 TP={tp} input shard {local_in} is not divisible by canonical \
6872 {canonical_cols}-column chunks"
6873 ));
6874 }
6875 Ok(canonical_cols)
6876}
6877
6878fn validate_row_shape(matrix: E4m3BlockMatrix<'_>, tp: usize) -> Result<(), String> {
6879 if matrix.in_features % tp != 0 {
6880 return Err(format!(
6881 "row-parallel in_features {} is not divisible by TP={tp}",
6882 matrix.in_features
6883 ));
6884 }
6885 let local_in = matrix.in_features / tp;
6886 if local_in % FP8_BLOCK != 0 {
6887 return Err(format!(
6888 "row-parallel input shard {local_in} cuts through a {FP8_BLOCK}-column \
6889 E4M3 scale block"
6890 ));
6891 }
6892 Ok(())
6893}
6894
6895fn upload_rank(
6896 engine: &Engine,
6897 matrix: E4m3BlockMatrix<'_>,
6898) -> Result<ResidentE4m3Rank, Box<dyn std::error::Error>> {
6899 let _main = engine.gpu.enter_main()?;
6900 matrix.validate()?;
6901 Ok(ResidentE4m3Rank {
6902 codes: engine.htod_bytes(matrix.codes)?,
6903 scales: engine.htod(matrix.scales)?,
6904 out_features: matrix.out_features,
6905 in_features: matrix.in_features,
6906 })
6907}
6908
6909fn upload_bf16_rank(
6910 engine: &Engine,
6911 matrix: Bf16Matrix<'_>,
6912 f32_mirror: bool,
6913) -> Result<ResidentBf16Rank, Box<dyn std::error::Error>> {
6914 let _main = engine.gpu.enter_main()?;
6915 matrix.validate()?;
6916 let bytes = engine.htod_bytes(matrix.bytes)?;
6917 let weight = if f32_mirror {
6918 let values = matrix
6919 .out_features
6920 .checked_mul(matrix.in_features)
6921 .ok_or("resident BF16 mirror element count overflow")?;
6922 ResidentBf16Weight::F32(engine.bf16_to_f32(&bytes.slice(0..bytes.len()), values)?)
6923 } else {
6924 ResidentBf16Weight::Bf16(bytes)
6925 };
6926 Ok(ResidentBf16Rank {
6927 weight,
6928 out_features: matrix.out_features,
6929 in_features: matrix.in_features,
6930 })
6931}
6932
6933fn upload_expert_bank_rank(
6934 engine: &Engine,
6935 bank: E4m3ExpertBank<'_>,
6936 expert_range: Range<usize>,
6937) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
6938 let _main = engine.gpu.enter_main()?;
6939 bank.validate()?;
6940 if expert_range.start >= expert_range.end || expert_range.end > bank.expert_count {
6941 return Err(format!(
6942 "invalid EP expert range {expert_range:?} for {} experts",
6943 bank.expert_count
6944 )
6945 .into());
6946 }
6947 let code_stride = bank.out_features * bank.in_features;
6948 let scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * bank.in_features.div_ceil(FP8_BLOCK);
6949 Ok(ResidentE4m3ExpertBankRank {
6950 codes: engine.htod_bytes(
6951 &bank.codes[expert_range.start * code_stride..expert_range.end * code_stride],
6952 )?,
6953 scales: engine.htod(
6954 &bank.scales[expert_range.start * scale_stride..expert_range.end * scale_stride],
6955 )?,
6956 expert_range,
6957 out_features: bank.out_features,
6958 in_features: bank.in_features,
6959 code_stride,
6960 scale_stride,
6961 k_blocks: None,
6962 })
6963}
6964
6965fn validate_column_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
6966 if bank.out_features % tp != 0 {
6967 return Err(format!(
6968 "TP expert output width {} is not divisible by TP={tp}",
6969 bank.out_features
6970 ));
6971 }
6972 let local_out = bank.out_features / tp;
6973 if local_out % FP8_BLOCK != 0 {
6974 return Err(format!(
6975 "TP expert output shard {local_out} cuts through a {FP8_BLOCK}-row E4M3 scale block"
6976 ));
6977 }
6978 Ok(())
6979}
6980
6981fn validate_row_bank_shape(bank: E4m3ExpertBank<'_>, tp: usize) -> Result<(), String> {
6982 if bank.in_features % tp != 0 {
6983 return Err(format!(
6984 "TP expert input width {} is not divisible by TP={tp}",
6985 bank.in_features
6986 ));
6987 }
6988 let local_in = bank.in_features / tp;
6989 if local_in % FP8_BLOCK != 0 {
6990 return Err(format!(
6991 "TP expert input shard {local_in} cuts through a {FP8_BLOCK}-column E4M3 scale block"
6992 ));
6993 }
6994 Ok(())
6995}
6996
6997fn upload_column_bank_rank(
6998 engine: &Engine,
6999 bank: E4m3ExpertBank<'_>,
7000 tp: usize,
7001 rank: usize,
7002) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7003 let _main = engine.gpu.enter_main()?;
7004 let packed = pack_column_bank_rank(bank, tp, rank)?;
7005 Ok(ResidentE4m3ExpertBankRank {
7006 codes: engine.htod_bytes(&packed.codes)?,
7007 scales: engine.htod(&packed.scales)?,
7008 expert_range: packed.expert_range,
7009 out_features: packed.out_features,
7010 in_features: packed.in_features,
7011 code_stride: packed.code_stride,
7012 scale_stride: packed.scale_stride,
7013 k_blocks: packed.k_blocks,
7014 })
7015}
7016
7017fn pack_column_bank_rank(
7018 bank: E4m3ExpertBank<'_>,
7019 tp: usize,
7020 rank: usize,
7021) -> Result<PackedE4m3ExpertBankRank, String> {
7022 bank.validate()?;
7023 validate_column_bank_shape(bank, tp)?;
7024 if rank >= tp {
7025 return Err(format!("TP rank {rank} outside 0..{tp}"));
7026 }
7027 let local_out = bank.out_features / tp;
7028 let full_code_stride = bank.out_features * bank.in_features;
7029 let local_code_stride = local_out * bank.in_features;
7030 let scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7031 let full_scale_stride = bank.out_features.div_ceil(FP8_BLOCK) * scale_cols;
7032 let local_scale_rows = local_out / FP8_BLOCK;
7033 let local_scale_stride = local_scale_rows * scale_cols;
7034 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7035 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7036 let row_start = rank * local_out;
7037 let scale_row_start = rank * local_scale_rows;
7038 for expert in 0..bank.expert_count {
7039 let code_start = expert * full_code_stride + row_start * bank.in_features;
7040 codes.extend_from_slice(&bank.codes[code_start..code_start + local_code_stride]);
7041 let scale_start = expert * full_scale_stride + scale_row_start * scale_cols;
7042 scales.extend_from_slice(&bank.scales[scale_start..scale_start + local_scale_stride]);
7043 }
7044 Ok(PackedE4m3ExpertBankRank {
7045 codes,
7046 scales,
7047 expert_range: 0..bank.expert_count,
7048 out_features: local_out,
7049 in_features: bank.in_features,
7050 code_stride: local_code_stride,
7051 scale_stride: local_scale_stride,
7052 k_blocks: None,
7053 })
7054}
7055
7056fn upload_row_bank_rank(
7057 engine: &Engine,
7058 bank: E4m3ExpertBank<'_>,
7059 tp: usize,
7060 rank: usize,
7061) -> Result<ResidentE4m3ExpertBankRank, Box<dyn std::error::Error>> {
7062 let _main = engine.gpu.enter_main()?;
7063 let packed = pack_row_bank_rank(bank, tp, rank)?;
7064 Ok(ResidentE4m3ExpertBankRank {
7065 codes: engine.htod_bytes(&packed.codes)?,
7066 scales: engine.htod(&packed.scales)?,
7067 expert_range: packed.expert_range,
7068 out_features: packed.out_features,
7069 in_features: packed.in_features,
7070 code_stride: packed.code_stride,
7071 scale_stride: packed.scale_stride,
7072 k_blocks: packed.k_blocks,
7073 })
7074}
7075
7076fn pack_row_bank_rank(
7077 bank: E4m3ExpertBank<'_>,
7078 tp: usize,
7079 rank: usize,
7080) -> Result<PackedE4m3ExpertBankRank, String> {
7081 bank.validate()?;
7082 validate_row_bank_shape(bank, tp)?;
7083 if rank >= tp {
7084 return Err(format!("TP rank {rank} outside 0..{tp}"));
7085 }
7086 let local_in = bank.in_features / tp;
7087 let full_code_stride = bank.out_features * bank.in_features;
7088 let local_code_stride = bank.out_features * local_in;
7089 let full_scale_cols = bank.in_features.div_ceil(FP8_BLOCK);
7090 let local_scale_cols = local_in / FP8_BLOCK;
7091 let scale_rows = bank.out_features.div_ceil(FP8_BLOCK);
7092 let full_scale_stride = scale_rows * full_scale_cols;
7093 let local_scale_stride = scale_rows * local_scale_cols;
7094 let global_block_start = rank * local_scale_cols;
7095 let mut codes = Vec::with_capacity(bank.expert_count * local_code_stride);
7096 let mut scales = Vec::with_capacity(bank.expert_count * local_scale_stride);
7097 for expert in 0..bank.expert_count {
7098 let expert_code_start = expert * full_code_stride;
7099 let expert_scale_start = expert * full_scale_stride;
7100 for local_block in 0..local_scale_cols {
7101 let global_block = global_block_start + local_block;
7102 let column_start = global_block * FP8_BLOCK;
7103 for row in 0..bank.out_features {
7104 let start = expert_code_start + row * bank.in_features + column_start;
7105 codes.extend_from_slice(&bank.codes[start..start + FP8_BLOCK]);
7106 }
7107 for row in 0..scale_rows {
7108 scales.push(bank.scales[expert_scale_start + row * full_scale_cols + global_block]);
7109 }
7110 }
7111 }
7112 Ok(PackedE4m3ExpertBankRank {
7113 codes,
7114 scales,
7115 expert_range: 0..bank.expert_count,
7116 out_features: bank.out_features,
7117 in_features: local_in,
7118 code_stride: local_code_stride,
7119 scale_stride: local_scale_stride,
7120 k_blocks: Some(local_scale_cols),
7121 })
7122}
7123
7124fn validate_resident_ranks(engines: &[Engine], ranks: &[ResidentE4m3Rank]) -> Result<(), String> {
7125 if engines.len() != ranks.len() {
7126 return Err(format!(
7127 "resident TP rank count {} != runtime rank count {}",
7128 ranks.len(),
7129 engines.len()
7130 ));
7131 }
7132 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7133 let device = engine.ctx().ordinal();
7134 if matrix.codes.ordinal() != device || matrix.scales.ordinal() != device {
7135 return Err(format!(
7136 "resident TP rank {rank} is not owned by runtime device {device}"
7137 ));
7138 }
7139 }
7140 Ok(())
7141}
7142
7143fn validate_tp_bank_residency(
7144 engines: &[Engine],
7145 experts: &ResidentTpExpertBank,
7146) -> Result<(), String> {
7147 if engines.len() != experts.gate.len()
7148 || engines.len() != experts.up.len()
7149 || engines.len() != experts.down.len()
7150 {
7151 return Err(format!(
7152 "resident TP expert-bank rank counts gate={} up={} down={} != runtime {}",
7153 experts.gate.len(),
7154 experts.up.len(),
7155 experts.down.len(),
7156 engines.len()
7157 ));
7158 }
7159 for (rank, engine) in engines.iter().enumerate() {
7160 let device = engine.ctx().ordinal();
7161 for (projection, bank) in [
7162 ("gate", &experts.gate[rank]),
7163 ("up", &experts.up[rank]),
7164 ("down", &experts.down[rank]),
7165 ] {
7166 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7167 return Err(format!(
7168 "resident TP rank {rank} {projection} bank is not owned by runtime device \
7169 {device}"
7170 ));
7171 }
7172 }
7173 }
7174 Ok(())
7175}
7176
7177fn validate_ep_residency(
7178 engines: &[Engine],
7179 experts: &ResidentExpertParallel,
7180) -> Result<(), String> {
7181 if engines.len() != experts.ranks.len() {
7182 return Err(format!(
7183 "resident EP rank count {} != runtime rank count {}",
7184 experts.ranks.len(),
7185 engines.len()
7186 ));
7187 }
7188 for (rank, (engine, resident)) in engines.iter().zip(&experts.ranks).enumerate() {
7189 let device = engine.ctx().ordinal();
7190 for (projection, bank) in [
7191 ("gate", &resident.gate),
7192 ("up", &resident.up),
7193 ("down", &resident.down),
7194 ] {
7195 if bank.codes.ordinal() != device || bank.scales.ordinal() != device {
7196 return Err(format!(
7197 "resident EP rank {rank} {projection} bank is not owned by runtime device \
7198 {device}"
7199 ));
7200 }
7201 }
7202 }
7203 Ok(())
7204}
7205
7206fn run_rank(
7207 engine: &Engine,
7208 matrix: E4m3BlockMatrix<'_>,
7209 activations: &[f32],
7210 tokens: usize,
7211) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7212 let _main = engine.gpu.enter_main()?;
7213 let codes = engine.htod_bytes(matrix.codes)?;
7214 let scales = engine.htod(matrix.scales)?;
7215 let activations = engine.htod(activations)?;
7216 let output = engine.qmatvec_mmq_fp8_blk(
7217 &codes,
7218 &scales,
7219 &activations,
7220 tokens,
7221 matrix.in_features,
7222 matrix.out_features,
7223 )?;
7224 engine.dtoh(&output)
7225}
7226
7227fn run_resident_rank(
7228 engine: &Engine,
7229 matrix: &ResidentE4m3Rank,
7230 activations: &[f32],
7231 tokens: usize,
7232) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7233 let _main = engine.gpu.enter_main()?;
7234 let activations = engine.htod(activations)?;
7235 let output = engine.qmatvec_mmq_fp8_blk(
7236 &matrix.codes,
7237 &matrix.scales,
7238 &activations,
7239 tokens,
7240 matrix.in_features,
7241 matrix.out_features,
7242 )?;
7243 engine.dtoh(&output)
7244}
7245
7246fn run_resident_bf16_rank(
7247 engine: &Engine,
7248 matrix: &ResidentBf16Rank,
7249 activations: &[f32],
7250 tokens: usize,
7251 canonical_chunk_rows: Option<usize>,
7252) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7253 let _main = engine.gpu.enter_main()?;
7254 let activations = engine.htod(activations)?;
7255 let output = run_resident_bf16_rank_device(
7256 engine,
7257 matrix,
7258 &activations,
7259 tokens,
7260 canonical_chunk_rows,
7261 false,
7262 )?;
7263 engine.dtoh(&output)
7264}
7265
7266fn run_resident_bf16_rank_device(
7267 engine: &Engine,
7268 matrix: &ResidentBf16Rank,
7269 activations: &CudaSlice<f32>,
7270 tokens: usize,
7271 canonical_chunk_rows: Option<usize>,
7272 strided_chunk_output: bool,
7273) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7274 let _main = engine.gpu.enter_main()?;
7275 if activations.ordinal() != engine.ctx().ordinal() {
7276 return Err(format!(
7277 "resident BF16 activation device {} != rank device {}",
7278 activations.ordinal(),
7279 engine.ctx().ordinal()
7280 )
7281 .into());
7282 }
7283 if activations.len() != tokens * matrix.in_features {
7284 return Err(format!(
7285 "resident BF16 activation count {} != {tokens}x{}",
7286 activations.len(),
7287 matrix.in_features
7288 )
7289 .into());
7290 }
7291 match (&matrix.weight, canonical_chunk_rows) {
7292 (ResidentBf16Weight::Bf16(bytes), Some(rows)) => engine
7293 .linear_bf16_resident_canonical_rows(
7294 activations,
7295 bytes,
7296 tokens,
7297 matrix.in_features,
7298 matrix.out_features,
7299 rows,
7300 ),
7301 (ResidentBf16Weight::Bf16(bytes), None) => engine.linear_bf16_resident(
7302 activations,
7303 bytes,
7304 tokens,
7305 matrix.in_features,
7306 matrix.out_features,
7307 ),
7308 (ResidentBf16Weight::F32(values), Some(rows)) if strided_chunk_output => engine
7309 .linear_f32_resident_canonical_rows_strided(
7310 activations,
7311 values,
7312 tokens,
7313 matrix.in_features,
7314 matrix.out_features,
7315 rows,
7316 ),
7317 (ResidentBf16Weight::F32(values), Some(rows)) => engine.linear_f32_resident_canonical_rows(
7318 activations,
7319 values,
7320 tokens,
7321 matrix.in_features,
7322 matrix.out_features,
7323 rows,
7324 ),
7325 (ResidentBf16Weight::F32(values), None) => engine.linear(
7326 activations,
7327 values,
7328 tokens,
7329 matrix.in_features,
7330 matrix.out_features,
7331 ),
7332 }
7333}
7334
7335fn validate_resident_bf16_ranks(
7336 engines: &[Engine],
7337 ranks: &[ResidentBf16Rank],
7338) -> Result<(), String> {
7339 if engines.len() != ranks.len() {
7340 return Err(format!(
7341 "resident BF16 TP rank count {} != runtime rank count {}",
7342 ranks.len(),
7343 engines.len(),
7344 ));
7345 }
7346 for (rank, (engine, matrix)) in engines.iter().zip(ranks).enumerate() {
7347 let device = engine.ctx().ordinal();
7348 if matrix.weight.ordinal() != device {
7349 return Err(format!(
7350 "resident BF16 TP rank {rank} is not owned by runtime device {device}"
7351 ));
7352 }
7353 }
7354 Ok(())
7355}
7356
7357fn validate_step_bf16_row_residency(
7358 engines: &[Engine],
7359 matrix: &ResidentStepBf16RowParallel,
7360) -> Result<(), String> {
7361 if engines.len() != matrix.ranks.len() {
7362 return Err(format!(
7363 "resident Step BF16 row rank count {} != runtime rank count {}",
7364 matrix.ranks.len(),
7365 engines.len(),
7366 ));
7367 }
7368 let canonical_cols = step_bf16_canonical_chunk_cols(matrix.in_features, engines.len())?;
7369 if matrix.canonical_chunk_cols != canonical_cols {
7370 return Err(format!(
7371 "resident Step BF16 row canonical columns {} != registered {canonical_cols}",
7372 matrix.canonical_chunk_cols
7373 ));
7374 }
7375 let blocks_per_rank = PRODUCT_MAX_CARDS / engines.len();
7376 for (rank, (engine, blocks)) in engines.iter().zip(&matrix.ranks).enumerate() {
7377 if blocks.len() != blocks_per_rank {
7378 return Err(format!(
7379 "resident Step BF16 row rank {rank} has {} blocks, expected {blocks_per_rank}",
7380 blocks.len()
7381 ));
7382 }
7383 let device = engine.ctx().ordinal();
7384 for (block, resident) in blocks.iter().enumerate() {
7385 if resident.weight.ordinal() != device
7386 || resident.in_features != canonical_cols
7387 || resident.out_features != matrix.out_features
7388 {
7389 return Err(format!(
7390 "resident Step BF16 row rank {rank} block {block} has inconsistent \
7391 device or geometry"
7392 ));
7393 }
7394 }
7395 }
7396 Ok(())
7397}
7398
7399fn validate_replicated_device_rows(
7400 engines: &[Engine],
7401 rows: &ResidentReplicatedDeviceRows,
7402) -> Result<(), String> {
7403 let rank_lengths = rows
7404 .ranks
7405 .iter()
7406 .map(|rank_rows| rank_rows.len())
7407 .collect::<Vec<_>>();
7408 replicated_device_row_values(rows.tokens, rows.width, engines.len(), &rank_lengths)?;
7409 if rows
7410 .ranks
7411 .iter()
7412 .zip(engines)
7413 .any(|(rank_rows, engine)| rank_rows.ordinal() != engine.ctx().ordinal())
7414 {
7415 return Err("replicated device rows are owned by the wrong CUDA contexts".into());
7416 }
7417 Ok(())
7418}
7419
7420fn replicated_device_row_values(
7421 tokens: usize,
7422 width: usize,
7423 expected_ranks: usize,
7424 rank_lengths: &[usize],
7425) -> Result<usize, String> {
7426 let values = tokens
7427 .checked_mul(width)
7428 .ok_or("replicated device row size overflow")?;
7429 if tokens == 0
7430 || width == 0
7431 || expected_ranks == 0
7432 || rank_lengths.len() != expected_ranks
7433 || rank_lengths.iter().any(|&rank_len| rank_len != values)
7434 {
7435 return Err(format!(
7436 "replicated device rows have inconsistent geometry tokens={} width={} ranks={}/{}",
7437 tokens,
7438 width,
7439 rank_lengths.len(),
7440 expected_ranks
7441 ));
7442 }
7443 Ok(values)
7444}
7445
7446fn replicated_device_row_source_values(
7447 tokens: usize,
7448 width: usize,
7449 source_len: usize,
7450 source_device: usize,
7451 root_device: usize,
7452) -> Result<usize, String> {
7453 let values = tokens
7454 .checked_mul(width)
7455 .ok_or("replicated device row size overflow")?;
7456 if tokens == 0 || width == 0 || source_len != values || source_device != root_device {
7457 return Err(format!(
7458 "replicated device row source has inconsistent geometry/device \
7459 tokens={tokens} width={width} source={source_len}@{source_device} root={root_device}"
7460 ));
7461 }
7462 Ok(values)
7463}
7464
7465fn bf16_column_shard(
7466 matrix: Bf16Matrix<'_>,
7467 tp: usize,
7468 rank: usize,
7469) -> Result<Bf16Matrix<'_>, String> {
7470 matrix.validate()?;
7471 if tp == 0 || rank >= tp || matrix.out_features % tp != 0 {
7472 return Err(format!(
7473 "invalid BF16 column shard out={} TP={tp} rank={rank}",
7474 matrix.out_features
7475 ));
7476 }
7477 let local_out = matrix.out_features / tp;
7478 let row_bytes = matrix.in_features * 2;
7479 let start = rank * local_out * row_bytes;
7480 Ok(Bf16Matrix {
7481 bytes: &matrix.bytes[start..start + local_out * row_bytes],
7482 out_features: local_out,
7483 in_features: matrix.in_features,
7484 })
7485}
7486
7487fn bf16_row_shard(matrix: Bf16Matrix<'_>, tp: usize, rank: usize) -> Result<Vec<u8>, String> {
7488 matrix.validate()?;
7489 if tp == 0 || rank >= tp || matrix.in_features % tp != 0 {
7490 return Err(format!(
7491 "invalid BF16 row shard in={} TP={tp} rank={rank}",
7492 matrix.in_features
7493 ));
7494 }
7495 let local_in = matrix.in_features / tp;
7496 let mut bytes = Vec::with_capacity(matrix.out_features * local_in * 2);
7497 for row in 0..matrix.out_features {
7498 let start = (row * matrix.in_features + rank * local_in) * 2;
7499 bytes.extend_from_slice(&matrix.bytes[start..start + local_in * 2]);
7500 }
7501 Ok(bytes)
7502}
7503
7504fn bf16_row_block(
7505 matrix: Bf16Matrix<'_>,
7506 col_start: usize,
7507 block_cols: usize,
7508) -> Result<Vec<u8>, String> {
7509 matrix.validate()?;
7510 let col_end = col_start
7511 .checked_add(block_cols)
7512 .ok_or("BF16 row block column overflow")?;
7513 if block_cols == 0 || col_end > matrix.in_features {
7514 return Err(format!(
7515 "invalid BF16 row block columns {col_start}..{col_end} for input width {}",
7516 matrix.in_features
7517 ));
7518 }
7519 let mut bytes = Vec::with_capacity(matrix.out_features * block_cols * 2);
7520 for row in 0..matrix.out_features {
7521 let start = (row * matrix.in_features + col_start) * 2;
7522 bytes.extend_from_slice(&matrix.bytes[start..start + block_cols * 2]);
7523 }
7524 Ok(bytes)
7525}
7526
7527fn run_resident_bank_expert(
7528 engine: &Engine,
7529 bank: &ResidentE4m3ExpertBankRank,
7530 local_expert: usize,
7531 activations: &[f32],
7532 tokens: usize,
7533) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7534 let _main = engine.gpu.enter_main()?;
7535 if bank.k_blocks.is_some() {
7536 return Err("block-major TP row bank requires canonical block execution".into());
7537 }
7538 let local_count = bank.expert_range.end - bank.expert_range.start;
7539 if local_expert >= local_count {
7540 return Err(format!(
7541 "local EP expert {local_expert} outside 0..{local_count} for range {:?}",
7542 bank.expert_range
7543 )
7544 .into());
7545 }
7546 validate_activations(activations, tokens, bank.in_features)?;
7547 let activations = engine.htod(activations)?;
7548 let weight = bank
7549 .codes
7550 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7551 let scales = bank
7552 .scales
7553 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7554 let input = activations.slice(0..activations.len());
7555 let output = engine.qmatvec_mmq_fp8_blk_view(
7556 &weight,
7557 &scales,
7558 &input,
7559 tokens,
7560 bank.in_features,
7561 bank.out_features,
7562 )?;
7563 engine.dtoh(&output)
7564}
7565
7566fn run_resident_bank_expert_block(
7567 engine: &Engine,
7568 bank: &ResidentE4m3ExpertBankRank,
7569 local_expert: usize,
7570 block: usize,
7571 activations: &[f32],
7572) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
7573 let _main = engine.gpu.enter_main()?;
7574 let local_count = bank.expert_range.end - bank.expert_range.start;
7575 if local_expert >= local_count {
7576 return Err(format!(
7577 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7578 bank.expert_range
7579 )
7580 .into());
7581 }
7582 let blocks = bank
7583 .k_blocks
7584 .ok_or("TP row bank is not packed in native K-block order")?;
7585 if block >= blocks {
7586 return Err(format!("TP row block {block} outside 0..{blocks}").into());
7587 }
7588 validate_activations(activations, 1, FP8_BLOCK)?;
7589 let block_code_stride = bank.out_features * FP8_BLOCK;
7590 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7591 if bank.in_features != blocks * FP8_BLOCK
7592 || bank.code_stride != blocks * block_code_stride
7593 || bank.scale_stride != blocks * block_scale_stride
7594 {
7595 return Err("TP row bank block-major geometry is inconsistent".into());
7596 }
7597
7598 let expert_code_start = local_expert * bank.code_stride;
7599 let expert_scale_start = local_expert * bank.scale_stride;
7600 let weight = bank.codes.slice(
7601 expert_code_start + block * block_code_stride
7602 ..expert_code_start + (block + 1) * block_code_stride,
7603 );
7604 let scales = bank.scales.slice(
7605 expert_scale_start + block * block_scale_stride
7606 ..expert_scale_start + (block + 1) * block_scale_stride,
7607 );
7608 let activations = engine.htod(activations)?;
7609 let input = activations.slice(0..activations.len());
7610 let output = engine.qmatvec_mmq_fp8_blk_view(
7611 &weight,
7612 &scales,
7613 &input,
7614 1,
7615 FP8_BLOCK,
7616 bank.out_features,
7617 )?;
7618 engine.dtoh(&output)
7619}
7620
7621fn run_resident_bank_expert_device(
7622 engine: &Engine,
7623 bank: &ResidentE4m3ExpertBankRank,
7624 local_expert: usize,
7625 activations: &CudaSlice<f32>,
7626 tokens: usize,
7627) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7628 let _main = engine.gpu.enter_main()?;
7629 if bank.k_blocks.is_some() {
7630 return Err("block-major TP row bank requires canonical block execution".into());
7631 }
7632 let local_count = bank.expert_range.end - bank.expert_range.start;
7633 if local_expert >= local_count {
7634 return Err(format!(
7635 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7636 bank.expert_range
7637 )
7638 .into());
7639 }
7640 let expected = tokens
7641 .checked_mul(bank.in_features)
7642 .ok_or("native TP activation size overflow")?;
7643 if activations.len() != expected || activations.ordinal() != engine.ctx().ordinal() {
7644 return Err(format!(
7645 "native TP activation len/device {}/{} != expected {expected}/{}",
7646 activations.len(),
7647 activations.ordinal(),
7648 engine.ctx().ordinal()
7649 )
7650 .into());
7651 }
7652 let weight = bank
7653 .codes
7654 .slice(local_expert * bank.code_stride..(local_expert + 1) * bank.code_stride);
7655 let scales = bank
7656 .scales
7657 .slice(local_expert * bank.scale_stride..(local_expert + 1) * bank.scale_stride);
7658 let input = activations.slice(0..activations.len());
7659 engine.qmatvec_mmq_fp8_blk_view(
7660 &weight,
7661 &scales,
7662 &input,
7663 tokens,
7664 bank.in_features,
7665 bank.out_features,
7666 )
7667}
7668
7669fn run_resident_bank_expert_block_device(
7670 engine: &Engine,
7671 bank: &ResidentE4m3ExpertBankRank,
7672 local_expert: usize,
7673 block: usize,
7674 activations: &cudarc::driver::CudaView<'_, f32>,
7675) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7676 let _main = engine.gpu.enter_main()?;
7677 let local_count = bank.expert_range.end - bank.expert_range.start;
7678 if local_expert >= local_count {
7679 return Err(format!(
7680 "local TP expert {local_expert} outside 0..{local_count} for range {:?}",
7681 bank.expert_range
7682 )
7683 .into());
7684 }
7685 let blocks = bank
7686 .k_blocks
7687 .ok_or("native TP row bank is not packed in checkpoint-block order")?;
7688 if block >= blocks {
7689 return Err(format!("native TP row block {block} outside 0..{blocks}").into());
7690 }
7691 let activation_device = activations.stream().context().ordinal();
7692 if activations.len() != FP8_BLOCK || activation_device != engine.ctx().ordinal() {
7693 return Err(format!(
7694 "native TP block activation len/device {}/{} != expected {FP8_BLOCK}/{}",
7695 activations.len(),
7696 activation_device,
7697 engine.ctx().ordinal()
7698 )
7699 .into());
7700 }
7701 let block_code_stride = bank.out_features * FP8_BLOCK;
7702 let block_scale_stride = bank.out_features.div_ceil(FP8_BLOCK);
7703 if bank.in_features != blocks * FP8_BLOCK
7704 || bank.code_stride != blocks * block_code_stride
7705 || bank.scale_stride != blocks * block_scale_stride
7706 {
7707 return Err("native TP row bank block-major geometry is inconsistent".into());
7708 }
7709 let expert_code_start = local_expert * bank.code_stride;
7710 let expert_scale_start = local_expert * bank.scale_stride;
7711 let weight = bank.codes.slice(
7712 expert_code_start + block * block_code_stride
7713 ..expert_code_start + (block + 1) * block_code_stride,
7714 );
7715 let scales = bank.scales.slice(
7716 expert_scale_start + block * block_scale_stride
7717 ..expert_scale_start + (block + 1) * block_scale_stride,
7718 );
7719 engine.qmatvec_mmq_fp8_blk_view(
7720 &weight,
7721 &scales,
7722 activations,
7723 1,
7724 FP8_BLOCK,
7725 bank.out_features,
7726 )
7727}
7728
7729fn configure_native_p2p(
7730 ranks: &[Engine],
7731 devices: &[usize],
7732) -> Result<(), Box<dyn std::error::Error>> {
7733 if ranks.len() != devices.len() || ranks.len() < 2 {
7734 return Err("native TP P2P setup requires matching multi-rank devices".into());
7735 }
7736 for (rank, (&device, engine)) in devices.iter().zip(ranks).enumerate() {
7737 if engine.ctx().ordinal() != device {
7738 return Err(format!(
7739 "native TP rank {rank} context device {} != requested device {device}",
7740 engine.ctx().ordinal()
7741 )
7742 .into());
7743 }
7744 }
7745
7746 for src in 0..ranks.len() {
7747 for dst in 0..ranks.len() {
7748 if src == dst {
7749 continue;
7750 }
7751 let mut can_access = 0;
7752 unsafe {
7753 cudarc::driver::sys::cuDeviceCanAccessPeer(
7754 &mut can_access,
7755 ranks[src].ctx().cu_device(),
7756 ranks[dst].ctx().cu_device(),
7757 )
7758 .result()?;
7759 }
7760 if can_access == 0 {
7761 return Err(format!(
7762 "native TP requires P2P, but dev{} cannot access dev{}",
7763 devices[src], devices[dst]
7764 )
7765 .into());
7766 }
7767 ranks[src].ctx().bind_to_thread()?;
7768 let rc =
7769 unsafe { cudarc::driver::sys::cuCtxEnablePeerAccess(ranks[dst].ctx().cu_ctx(), 0) };
7770 use cudarc::driver::sys::cudaError_enum as E;
7771 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
7772 return Err(format!(
7773 "native TP cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
7774 devices[src], devices[dst]
7775 )
7776 .into());
7777 }
7778 }
7779 }
7780
7781 for &owner in devices {
7782 for &accessor in devices {
7783 if owner == accessor {
7784 continue;
7785 }
7786 let device = cudarc::driver::result::device::get(owner as i32)?;
7787 let mut pool: cudarc::driver::sys::CUmemoryPool = std::ptr::null_mut();
7788 unsafe {
7789 cudarc::driver::sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
7790 }
7791 let desc = cudarc::driver::sys::CUmemAccessDesc {
7792 location: cudarc::driver::sys::CUmemLocation {
7793 type_: cudarc::driver::sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
7794 id: accessor as i32,
7795 },
7796 flags: cudarc::driver::sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
7797 };
7798 let rc = unsafe { cudarc::driver::sys::cuMemPoolSetAccess(pool, &desc, 1) };
7799 if rc != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
7800 return Err(format!(
7801 "native TP cuMemPoolSetAccess(dev{owner} pool -> dev{accessor}) failed: \
7802 {rc:?}"
7803 )
7804 .into());
7805 }
7806 }
7807 }
7808
7809 for src in 0..ranks.len() {
7810 for dst in 0..ranks.len() {
7811 if src == dst {
7812 continue;
7813 }
7814 let expected = (0..NATIVE_P2P_PROBE_WORDS)
7815 .map(|index| {
7816 (index as u32)
7817 .wrapping_mul(0x9e37_79b9)
7818 .wrapping_add(((src as u32) << 16) | dst as u32)
7819 })
7820 .collect::<Vec<_>>();
7821 let poison = expected.iter().map(|value| !value).collect::<Vec<_>>();
7822 let source = ranks[src].htod_u32_v(&expected)?;
7823 let mut destination = ranks[dst].htod_u32_v(&poison)?;
7824 ranks[dst].stream().memcpy_dtod(&source, &mut destination)?;
7825 let actual = ranks[dst].dtoh_u32(&destination)?;
7826 if actual != expected {
7827 let mismatches = actual
7828 .iter()
7829 .zip(&expected)
7830 .filter(|(actual, expected)| actual != expected)
7831 .count();
7832 return Err(format!(
7833 "native TP peer probe dev{}->dev{} failed: {mismatches}/{} words differ",
7834 devices[src],
7835 devices[dst],
7836 expected.len()
7837 )
7838 .into());
7839 }
7840 }
7841 }
7842 ranks[0].ctx().bind_to_thread()?;
7843 eprintln!(
7844 "[tp] native peer byte-integrity probe PASS: devices={devices:?} \
7845 directions={} bytes={} mismatches=0",
7846 ranks.len() * (ranks.len() - 1),
7847 NATIVE_P2P_PROBE_WORDS * std::mem::size_of::<u32>(),
7848 );
7849 Ok(())
7850}
7851
7852fn validate_activations(
7853 activations: &[f32],
7854 tokens: usize,
7855 in_features: usize,
7856) -> Result<(), String> {
7857 let expected = tokens
7858 .checked_mul(in_features)
7859 .ok_or_else(|| "activation size overflow".to_string())?;
7860 if activations.len() != expected {
7861 return Err(format!(
7862 "activation count {} != {tokens}x{in_features} ({expected})",
7863 activations.len()
7864 ));
7865 }
7866 if !activations.iter().all(|value| value.is_finite()) {
7867 return Err("activations contain a non-finite value".to_string());
7868 }
7869 Ok(())
7870}
7871
7872fn column_shard(
7873 matrix: E4m3BlockMatrix<'_>,
7874 tp: usize,
7875 rank: usize,
7876) -> Result<E4m3BlockMatrix<'_>, String> {
7877 let local_out = matrix.out_features / tp;
7878 let row_start = rank * local_out;
7879 let code_start = row_start * matrix.in_features;
7880 let code_end = code_start + local_out * matrix.in_features;
7881 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
7882 let local_scale_rows = local_out / FP8_BLOCK;
7883 let scale_start = rank * local_scale_rows * scale_cols;
7884 let scale_end = scale_start + local_scale_rows * scale_cols;
7885 Ok(E4m3BlockMatrix {
7886 codes: &matrix.codes[code_start..code_end],
7887 scales: &matrix.scales[scale_start..scale_end],
7888 out_features: local_out,
7889 in_features: matrix.in_features,
7890 })
7891}
7892
7893fn row_shard(
7894 matrix: E4m3BlockMatrix<'_>,
7895 tp: usize,
7896 rank: usize,
7897) -> Result<(Vec<u8>, Vec<f32>), String> {
7898 let local_in = matrix.in_features / tp;
7899 let col_start = rank * local_in;
7900 let mut codes = Vec::with_capacity(matrix.out_features * local_in);
7901 for row in 0..matrix.out_features {
7902 let start = row * matrix.in_features + col_start;
7903 codes.extend_from_slice(&matrix.codes[start..start + local_in]);
7904 }
7905
7906 let scale_rows = matrix.out_features.div_ceil(FP8_BLOCK);
7907 let scale_cols = matrix.in_features.div_ceil(FP8_BLOCK);
7908 let local_scale_cols = local_in / FP8_BLOCK;
7909 let scale_col_start = rank * local_scale_cols;
7910 let mut scales = Vec::with_capacity(scale_rows * local_scale_cols);
7911 for row in 0..scale_rows {
7912 let start = row * scale_cols + scale_col_start;
7913 scales.extend_from_slice(&matrix.scales[start..start + local_scale_cols]);
7914 }
7915 Ok((codes, scales))
7916}
7917
7918fn activation_shard(
7919 activations: &[f32],
7920 tokens: usize,
7921 in_features: usize,
7922 tp: usize,
7923 rank: usize,
7924) -> Vec<f32> {
7925 let local_in = in_features / tp;
7926 let col_start = rank * local_in;
7927 let mut shard = Vec::with_capacity(tokens * local_in);
7928 for token in 0..tokens {
7929 let start = token * in_features + col_start;
7930 shard.extend_from_slice(&activations[start..start + local_in]);
7931 }
7932 shard
7933}
7934
7935#[derive(Clone, Copy)]
7955pub struct Nvfp4BlockMatrix<'a> {
7956 pub codes: &'a [u8], pub scales: &'a [u8], pub macro_scale: f32, pub out_features: usize,
7960 pub in_features: usize,
7961}
7962
7963impl Nvfp4BlockMatrix<'_> {
7964 pub fn validate(&self) -> Result<(), String> {
7965 if self.in_features == 0 || self.out_features == 0 {
7966 return Err("NVFP4 matrix has a zero dimension".to_string());
7967 }
7968 if self.in_features % 64 != 0 {
7969 return Err(format!(
7970 "NVFP4 in_features {} is not 64-aligned (memra block_nvfp4 superblock)",
7971 self.in_features
7972 ));
7973 }
7974 if self.codes.len() != self.out_features * self.in_features / 2 {
7975 return Err(format!(
7976 "NVFP4 code bytes {} != {}x{}/2",
7977 self.codes.len(),
7978 self.out_features,
7979 self.in_features
7980 ));
7981 }
7982 if self.scales.len() != self.out_features * self.in_features / 16 {
7983 return Err(format!(
7984 "NVFP4 scale bytes {} != {}x{}/16",
7985 self.scales.len(),
7986 self.out_features,
7987 self.in_features
7988 ));
7989 }
7990 if !self.macro_scale.is_finite() || self.macro_scale <= 0.0 {
7991 return Err(format!(
7992 "NVFP4 macro scale {} is not finite-positive",
7993 self.macro_scale
7994 ));
7995 }
7996 Ok(())
7997 }
7998}
7999
8000#[derive(Clone, Copy)]
8002pub struct Nvfp4ExpertBank<'a> {
8003 pub codes: &'a [u8], pub scales: &'a [u8], pub macros: &'a [f32], pub expert_count: usize,
8007 pub out_features: usize,
8008 pub in_features: usize,
8009}
8010
8011impl Nvfp4ExpertBank<'_> {
8012 pub fn validate(&self) -> Result<(), String> {
8013 if self.expert_count == 0 {
8014 return Err("NVFP4 expert bank is empty".to_string());
8015 }
8016 if self.macros.len() != self.expert_count {
8017 return Err(format!(
8018 "NVFP4 bank macros {} != expert count {}",
8019 self.macros.len(),
8020 self.expert_count
8021 ));
8022 }
8023 self.expert(0).map(|_| ())
8024 }
8025
8026 pub fn expert(&self, expert: usize) -> Result<Nvfp4BlockMatrix<'_>, String> {
8027 if expert >= self.expert_count {
8028 return Err(format!("expert {expert} outside 0..{}", self.expert_count));
8029 }
8030 let code_stride = self.out_features * self.in_features / 2;
8031 let scale_stride = self.out_features * self.in_features / 16;
8032 if self.codes.len() != self.expert_count * code_stride
8033 || self.scales.len() != self.expert_count * scale_stride
8034 {
8035 return Err("NVFP4 bank byte extents do not match the declared geometry".to_string());
8036 }
8037 let matrix = Nvfp4BlockMatrix {
8038 codes: &self.codes[expert * code_stride..(expert + 1) * code_stride],
8039 scales: &self.scales[expert * scale_stride..(expert + 1) * scale_stride],
8040 macro_scale: self.macros[expert],
8041 out_features: self.out_features,
8042 in_features: self.in_features,
8043 };
8044 matrix.validate()?;
8045 Ok(matrix)
8046 }
8047}
8048
8049pub struct ResidentNvfp4Rank {
8051 blocks: crate::CudaSlice<u8>,
8052 macro_scale: f32,
8053 out_features: usize,
8054 in_features: usize,
8055 row_bytes: usize,
8056}
8057
8058pub struct ResidentNvfp4ColumnParallel {
8059 ranks: Vec<ResidentNvfp4Rank>,
8060 pub out_features: usize,
8061 pub in_features: usize,
8062}
8063
8064pub struct ResidentNvfp4RowParallel {
8065 ranks: Vec<ResidentNvfp4Rank>,
8066 pub out_features: usize,
8067 pub in_features: usize,
8068}
8069
8070pub struct ResidentTpNvfp4Expert {
8071 gate: ResidentNvfp4ColumnParallel,
8072 up: ResidentNvfp4ColumnParallel,
8073 down: ResidentNvfp4RowParallel,
8074 pub input_width: usize,
8075 pub expert_width: usize,
8076}
8077
8078pub struct ResidentNvfp4ColumnBankRank {
8082 bank: crate::CudaSlice<u8>,
8086 expert_bytes: usize,
8087 local_out: usize,
8088 in_features: usize,
8089 row_bytes: usize,
8090}
8091
8092impl ResidentNvfp4ColumnBankRank {
8093 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8094 self.bank
8095 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8096 }
8097}
8098
8099pub const NVFP4_CANONICAL_ROW_SHARDS: usize = 2;
8105
8106pub struct ResidentNvfp4RowBankRank {
8107 bank: crate::CudaSlice<u8>,
8109 expert_bytes: usize,
8110 device_rank: usize, out_features: usize,
8112 local_in: usize,
8113 row_bytes: usize,
8114}
8115
8116impl ResidentNvfp4RowBankRank {
8117 fn expert(&self, index: usize) -> cudarc::driver::CudaView<'_, u8> {
8118 self.bank
8119 .slice(index * self.expert_bytes..(index + 1) * self.expert_bytes)
8120 }
8121}
8122
8123impl ResidentNvfp4TensorParallel {
8124 pub(crate) fn device_workspace_handle(
8125 &self,
8126 ) -> &std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>> {
8127 &self.device_workspace
8128 }
8129}
8130
8131pub struct ResidentNvfp4TensorParallel {
8132 gate: Vec<ResidentNvfp4ColumnBankRank>,
8133 up: Vec<ResidentNvfp4ColumnBankRank>,
8134 down: Vec<ResidentNvfp4RowBankRank>,
8135 macros_gate: Vec<f32>,
8136 macros_up: Vec<f32>,
8137 macros_down: Vec<f32>,
8138 macros_gate_dev: Vec<crate::CudaSlice<f32>>,
8142 macros_up_dev: Vec<crate::CudaSlice<f32>>,
8143 macros_down_dev: Vec<crate::CudaSlice<f32>>,
8144 pub expert_count: usize,
8145 pub input_width: usize,
8146 pub expert_width: usize,
8147 device_workspace: std::sync::Mutex<Option<Nvfp4DeviceRoutesWorkspace>>,
8150 t2_workspace: std::sync::Mutex<Option<Nvfp4T2Workspace>>,
8154}
8155
8156pub struct Nvfp4T2Workspace {
8160 input2: Vec<crate::CudaSlice<f32>>,
8161 in_q2: Vec<crate::CudaSlice<i8>>,
8162 in_d2: Vec<crate::CudaSlice<f32>>,
8163 sel2: Vec<crate::CudaSlice<i32>>,
8164 route_w2: Vec<crate::CudaSlice<f32>>,
8165 gate_out2: Vec<crate::CudaSlice<f32>>,
8166 up_out2: Vec<crate::CudaSlice<f32>>,
8167 act_q2: Vec<crate::CudaSlice<i8>>,
8168 act_d2: Vec<crate::CudaSlice<f32>>,
8169 partial2: Vec<crate::CudaSlice<f32>>,
8170 acc_a: Vec<crate::CudaSlice<f32>>,
8172 acc_b: Vec<crate::CudaSlice<f32>>,
8173 peer_a: crate::CudaSlice<f32>,
8175 peer_b: crate::CudaSlice<f32>,
8176 omix_a: crate::CudaSlice<f32>,
8177 omix_b: crate::CudaSlice<f32>,
8178 ev_entry: CudaEvent,
8179 ev_rank: Vec<CudaEvent>,
8180 ev_root: CudaEvent,
8181 n_sel: usize,
8182 e_device: usize,
8183}
8184
8185struct RoutesGraph {
8192 exec: cudarc::driver::sys::CUgraphExec,
8193 parent: cudarc::driver::sys::CUgraph,
8194 _children: Vec<cudarc::driver::CudaGraph>,
8195}
8196unsafe impl Send for RoutesGraph {}
8199
8200impl Drop for RoutesGraph {
8201 fn drop(&mut self) {
8202 unsafe {
8203 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
8204 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
8205 }
8206 }
8207}
8208
8209impl Nvfp4DeviceRoutesWorkspace {
8210 pub(crate) fn in_stage_handle(&self) -> Option<&crate::CudaSlice<f32>> {
8211 self.in_stage_e.as_ref()
8212 }
8213 pub(crate) fn in_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8214 self.in_stage_e.as_mut()
8215 }
8216 pub(crate) fn out_stage_mut(&mut self) -> Option<&mut crate::CudaSlice<f32>> {
8217 self.out_stage_e.as_mut()
8218 }
8219 pub(crate) fn arm_stages(
8221 &mut self,
8222 e: &Engine,
8223 width: usize,
8224 n_sel: usize,
8225 ) -> Result<(), Box<dyn std::error::Error>> {
8226 let _main = e.gpu.enter_main()?;
8227 if self.in_stage_e.is_none() {
8228 self.in_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8229 self.out_stage_e = Some(e.htod(&vec![0.0f32; width])?);
8230 }
8231 if self.dev_route_e.is_none() {
8232 self.dev_route_e = Some((
8233 e.htod_i32(&vec![0i32; n_sel])?,
8234 e.htod(&vec![0.0f32; n_sel])?,
8235 ));
8236 }
8237 Ok(())
8238 }
8239
8240 pub(crate) fn in_and_out_stages_mut(
8242 &mut self,
8243 ) -> Option<(&crate::CudaSlice<f32>, &mut crate::CudaSlice<f32>)> {
8244 match (self.in_stage_e.as_ref(), self.out_stage_e.as_mut()) {
8245 (Some(input), Some(output)) => Some((input, output)),
8246 _ => None,
8247 }
8248 }
8249 pub(crate) fn dev_route_e_mut(
8250 &mut self,
8251 ) -> Option<(&mut crate::CudaSlice<i32>, &mut crate::CudaSlice<f32>)> {
8252 self.dev_route_e.as_mut().map(|(a, b)| (a, b))
8253 }
8254}
8255
8256pub struct Nvfp4DeviceRoutesWorkspace {
8257 gate_out: Vec<crate::CudaSlice<f32>>,
8260 up_out: Vec<crate::CudaSlice<f32>>,
8261 act_q: Vec<crate::CudaSlice<i8>>,
8262 act_d: Vec<crate::CudaSlice<f32>>,
8263 sel: Vec<crate::CudaSlice<i32>>,
8264 partial: Vec<crate::CudaSlice<f32>>,
8265 accumulator: Vec<crate::CudaSlice<f32>>,
8266 combine_w: Vec<crate::CudaSlice<f32>>,
8268 route_w: Vec<crate::CudaSlice<f32>>,
8271 in_q: Vec<crate::CudaSlice<i8>>,
8274 in_d: Vec<crate::CudaSlice<f32>>,
8275 dev_route_e: Option<(crate::CudaSlice<i32>, crate::CudaSlice<f32>)>,
8279 prestaged: bool,
8282 rank1_routed: bool,
8285 fence_flags_raw: u64,
8289 fence_ticket: u32,
8290 ev_input: Option<(CudaEvent, usize)>,
8292 in_stage_e: Option<crate::CudaSlice<f32>>,
8295 out_stage_e: Option<crate::CudaSlice<f32>>,
8296 routes_graph: Option<RoutesGraph>,
8297 raw_dev_route_e: Option<(u64, u64)>,
8299 raw_combine: Option<(u64, u64, u64, u64)>,
8300 raw_input: Vec<u64>,
8301 raw_sel: Vec<u64>,
8302 raw_route_w: Vec<u64>,
8303 remote: crate::CudaSlice<f32>,
8304 combined: crate::CudaSlice<f32>,
8305 n_sel: usize,
8306 input: Vec<crate::CudaSlice<f32>>,
8310 ev_rank: Vec<CudaEvent>,
8311 ev_done: Option<CudaEvent>,
8312 ev_entry: Option<(CudaEvent, usize)>,
8313}
8314
8315struct ResidentNvfp4EpRank {
8317 gate: Vec<crate::CudaSlice<u8>>,
8318 up: Vec<crate::CudaSlice<u8>>,
8319 down: Vec<crate::CudaSlice<u8>>,
8320 #[allow(dead_code)]
8321 expert_range: Range<usize>,
8322}
8323
8324pub struct ResidentNvfp4ExpertParallel {
8325 ranks: Vec<ResidentNvfp4EpRank>,
8326 macros_gate: Vec<f32>,
8327 macros_up: Vec<f32>,
8328 macros_down: Vec<f32>,
8329 pub expert_count: usize,
8330 pub input_width: usize,
8331 pub expert_width: usize,
8332 gate_row_bytes: usize,
8333 down_row_bytes: usize,
8334}
8335
8336fn nvfp4_repack_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8337 memra_gguf::nvfp4_repack::repack_modelopt_to_gguf(
8338 matrix.codes,
8339 matrix.scales,
8340 matrix.out_features,
8341 matrix.in_features,
8342 )
8343}
8344
8345fn nvfp4_row_bytes(in_features: usize) -> usize {
8346 in_features / 64 * 36 }
8348
8349pub(crate) fn fuse_rope_append_on() -> bool {
8357 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8358 *ON.get_or_init(|| std::env::var("MEMRA_FUSE_ROPE_APPEND").as_deref() == Ok("1"))
8359}
8360
8361pub(crate) fn no_local_shadow_on() -> bool {
8362 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8363 *ON.get_or_init(|| std::env::var("MEMRA_NO_LOCAL_SHADOW").as_deref() == Ok("1"))
8364}
8365
8366pub(crate) fn nvfp4_bank_v2_on() -> bool {
8367 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8368 *ON.get_or_init(|| std::env::var("MEMRA_NVFP4_BANK_V2").as_deref() == Ok("1"))
8369}
8370
8371fn nvfp4_matrix_v2_permute(v1: &[u8], out_features: usize, in_features: usize) -> Vec<u8> {
8375 let row_bytes = nvfp4_row_bytes(in_features);
8376 assert_eq!(v1.len(), out_features * row_bytes, "v2 permute geometry");
8377 let n_slots = in_features / 32;
8378 let mut out = Vec::with_capacity(v1.len());
8379 for row in 0..out_features {
8380 let r = &v1[row * row_bytes..(row + 1) * row_bytes];
8381 for g in 0..n_slots {
8382 let (sblk, h) = (g / 2, g % 2);
8383 let b = &r[sblk * 36..sblk * 36 + 36];
8384 out.extend_from_slice(&b[4 + 16 * h..4 + 16 * h + 16]);
8385 }
8386 for g in 0..n_slots {
8387 let (sblk, h) = (g / 2, g % 2);
8388 let b = &r[sblk * 36..sblk * 36 + 36];
8389 out.push(b[2 * h]);
8390 out.push(b[2 * h + 1]);
8391 }
8392 }
8393 out
8394}
8395
8396fn nvfp4_repack_bank_matrix(matrix: Nvfp4BlockMatrix<'_>) -> Vec<u8> {
8398 let (out_features, in_features) = (matrix.out_features, matrix.in_features);
8399 let v1 = nvfp4_repack_matrix(matrix);
8400 if nvfp4_bank_v2_on() {
8401 nvfp4_matrix_v2_permute(&v1, out_features, in_features)
8402 } else {
8403 v1
8404 }
8405}
8406
8407fn nvfp4_column_shard<'a>(
8410 matrix: Nvfp4BlockMatrix<'a>,
8411 tp: usize,
8412 rank: usize,
8413) -> Result<Nvfp4BlockMatrix<'a>, String> {
8414 if matrix.out_features % tp != 0 {
8415 return Err(format!(
8416 "NVFP4 column-parallel out_features {} is not divisible by TP={tp}",
8417 matrix.out_features
8418 ));
8419 }
8420 let local_out = matrix.out_features / tp;
8421 let code_row = matrix.in_features / 2;
8422 let scale_row = matrix.in_features / 16;
8423 Ok(Nvfp4BlockMatrix {
8424 codes: &matrix.codes[rank * local_out * code_row..(rank + 1) * local_out * code_row],
8425 scales: &matrix.scales[rank * local_out * scale_row..(rank + 1) * local_out * scale_row],
8426 macro_scale: matrix.macro_scale,
8427 out_features: local_out,
8428 in_features: matrix.in_features,
8429 })
8430}
8431
8432fn nvfp4_row_shard(
8435 matrix: Nvfp4BlockMatrix<'_>,
8436 tp: usize,
8437 rank: usize,
8438) -> Result<(Vec<u8>, Vec<u8>, usize), String> {
8439 if matrix.in_features % tp != 0 {
8440 return Err(format!(
8441 "NVFP4 row-parallel in_features {} is not divisible by TP={tp}",
8442 matrix.in_features
8443 ));
8444 }
8445 let local_in = matrix.in_features / tp;
8446 if local_in % 64 != 0 {
8447 return Err(format!(
8448 "NVFP4 row-parallel input shard {local_in} cuts through a 64-element superblock"
8449 ));
8450 }
8451 let code_row = matrix.in_features / 2;
8452 let scale_row = matrix.in_features / 16;
8453 let local_code = local_in / 2;
8454 let local_scale = local_in / 16;
8455 let mut codes = Vec::with_capacity(matrix.out_features * local_code);
8456 let mut scales = Vec::with_capacity(matrix.out_features * local_scale);
8457 for row in 0..matrix.out_features {
8458 let code_start = row * code_row + rank * local_code;
8459 codes.extend_from_slice(&matrix.codes[code_start..code_start + local_code]);
8460 let scale_start = row * scale_row + rank * local_scale;
8461 scales.extend_from_slice(&matrix.scales[scale_start..scale_start + local_scale]);
8462 }
8463 Ok((codes, scales, local_in))
8464}
8465
8466fn run_rank_nvfp4(
8470 engine: &Engine,
8471 matrix: Nvfp4BlockMatrix<'_>,
8472 activations: &[f32],
8473 tokens: usize,
8474) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8475 matrix.validate()?;
8476 validate_activations(activations, tokens, matrix.in_features)?;
8477 let _main = engine.gpu.enter_main()?;
8478 let blocks = engine.htod_bytes(&nvfp4_repack_matrix(matrix))?;
8479 let activations = engine.htod(activations)?;
8480 let output = engine.qmatvec_nvfp4_fast(
8481 &blocks.slice(0..blocks.len()),
8482 &activations,
8483 tokens,
8484 matrix.in_features,
8485 matrix.out_features,
8486 nvfp4_row_bytes(matrix.in_features),
8487 )?;
8488 engine.dtoh(&output)
8489}
8490
8491fn upload_rank_nvfp4(
8492 engine: &Engine,
8493 matrix: Nvfp4BlockMatrix<'_>,
8494) -> Result<ResidentNvfp4Rank, Box<dyn std::error::Error>> {
8495 matrix.validate()?;
8496 let _main = engine.gpu.enter_main()?;
8497 Ok(ResidentNvfp4Rank {
8498 blocks: engine.htod_bytes(&nvfp4_repack_matrix(matrix))?,
8499 macro_scale: matrix.macro_scale,
8500 out_features: matrix.out_features,
8501 in_features: matrix.in_features,
8502 row_bytes: nvfp4_row_bytes(matrix.in_features),
8503 })
8504}
8505
8506fn run_resident_rank_nvfp4(
8507 engine: &Engine,
8508 rank: &ResidentNvfp4Rank,
8509 activations: &[f32],
8510 tokens: usize,
8511) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8512 validate_activations(activations, tokens, rank.in_features)?;
8513 let _main = engine.gpu.enter_main()?;
8514 let activations = engine.htod(activations)?;
8515 let output = engine.qmatvec_nvfp4_fast(
8516 &rank.blocks.slice(0..rank.blocks.len()),
8517 &activations,
8518 tokens,
8519 rank.in_features,
8520 rank.out_features,
8521 rank.row_bytes,
8522 )?;
8523 engine.dtoh(&output)
8524}
8525
8526fn apply_macro(values: &mut [f32], macro_scale: f32) {
8527 for value in values.iter_mut() {
8528 *value *= macro_scale;
8529 }
8530}
8531
8532impl TpE4m3HostBounce {
8533 pub fn full_nvfp4(
8535 &self,
8536 matrix: Nvfp4BlockMatrix<'_>,
8537 activations: &[f32],
8538 tokens: usize,
8539 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8540 let mut output = run_rank_nvfp4(&self.ranks[0], matrix, activations, tokens)?;
8541 apply_macro(&mut output, matrix.macro_scale);
8542 Ok(output)
8543 }
8544
8545 pub fn column_parallel_nvfp4(
8548 &self,
8549 matrix: Nvfp4BlockMatrix<'_>,
8550 activations: &[f32],
8551 tokens: usize,
8552 ) -> Result<ColumnParallelResult, Box<dyn std::error::Error>> {
8553 matrix.validate()?;
8554 validate_activations(activations, tokens, matrix.in_features)?;
8555 let tp = self.ranks.len();
8556 let local_out = matrix.out_features / tp;
8557 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8558 let mut rank_outputs = Vec::with_capacity(tp);
8559 for (rank_index, rank) in self.ranks.iter().enumerate() {
8560 let shard = nvfp4_column_shard(matrix, tp, rank_index)?;
8561 let output = run_rank_nvfp4(rank, shard, activations, tokens)?;
8562 let row_start = rank_index * local_out;
8563 for token in 0..tokens {
8564 gathered[token * matrix.out_features + row_start
8565 ..token * matrix.out_features + row_start + local_out]
8566 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8567 }
8568 rank_outputs.push(output);
8569 }
8570 apply_macro(&mut gathered, matrix.macro_scale);
8571 Ok(ColumnParallelResult {
8572 gathered,
8573 rank_outputs,
8574 })
8575 }
8576
8577 pub fn row_parallel_nvfp4(
8580 &self,
8581 matrix: Nvfp4BlockMatrix<'_>,
8582 activations: &[f32],
8583 tokens: usize,
8584 ) -> Result<RowParallelResult, Box<dyn std::error::Error>> {
8585 matrix.validate()?;
8586 validate_activations(activations, tokens, matrix.in_features)?;
8587 let tp = self.ranks.len();
8588 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8589 let mut rank_partials = Vec::with_capacity(tp);
8590 for (rank_index, rank) in self.ranks.iter().enumerate() {
8591 let (codes, scales, local_in) = nvfp4_row_shard(matrix, tp, rank_index)?;
8592 let local_activations =
8593 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8594 let shard = Nvfp4BlockMatrix {
8595 codes: &codes,
8596 scales: &scales,
8597 macro_scale: matrix.macro_scale,
8598 out_features: matrix.out_features,
8599 in_features: local_in,
8600 };
8601 let partial = run_rank_nvfp4(rank, shard, &local_activations, tokens)?;
8602 for (sum, value) in reduced.iter_mut().zip(&partial) {
8603 *sum += *value;
8604 }
8605 rank_partials.push(partial);
8606 }
8607 apply_macro(&mut reduced, matrix.macro_scale);
8608 Ok(RowParallelResult {
8609 reduced,
8610 rank_partials,
8611 })
8612 }
8613
8614 pub fn upload_expert_nvfp4(
8615 &self,
8616 gate: Nvfp4BlockMatrix<'_>,
8617 up: Nvfp4BlockMatrix<'_>,
8618 down: Nvfp4BlockMatrix<'_>,
8619 ) -> Result<ResidentTpNvfp4Expert, Box<dyn std::error::Error>> {
8620 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8621 return Err("NVFP4 TP expert gate/up dimensions differ".into());
8622 }
8623 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8624 return Err(format!(
8625 "NVFP4 TP expert down {}x{} does not invert gate/up {}x{}",
8626 down.out_features, down.in_features, gate.out_features, gate.in_features
8627 )
8628 .into());
8629 }
8630 let tp = self.ranks.len();
8631 let mut gate_ranks = Vec::with_capacity(tp);
8632 let mut up_ranks = Vec::with_capacity(tp);
8633 let mut down_ranks = Vec::with_capacity(tp);
8634 for (rank_index, engine) in self.ranks.iter().enumerate() {
8635 gate_ranks.push(upload_rank_nvfp4(
8636 engine,
8637 nvfp4_column_shard(gate, tp, rank_index)?,
8638 )?);
8639 up_ranks.push(upload_rank_nvfp4(
8640 engine,
8641 nvfp4_column_shard(up, tp, rank_index)?,
8642 )?);
8643 let (codes, scales, local_in) = nvfp4_row_shard(down, tp, rank_index)?;
8644 down_ranks.push(upload_rank_nvfp4(
8645 engine,
8646 Nvfp4BlockMatrix {
8647 codes: &codes,
8648 scales: &scales,
8649 macro_scale: down.macro_scale,
8650 out_features: down.out_features,
8651 in_features: local_in,
8652 },
8653 )?);
8654 }
8655 Ok(ResidentTpNvfp4Expert {
8656 gate: ResidentNvfp4ColumnParallel {
8657 ranks: gate_ranks,
8658 out_features: gate.out_features,
8659 in_features: gate.in_features,
8660 },
8661 up: ResidentNvfp4ColumnParallel {
8662 ranks: up_ranks,
8663 out_features: up.out_features,
8664 in_features: up.in_features,
8665 },
8666 down: ResidentNvfp4RowParallel {
8667 ranks: down_ranks,
8668 out_features: down.out_features,
8669 in_features: down.in_features,
8670 },
8671 input_width: gate.in_features,
8672 expert_width: gate.out_features,
8673 })
8674 }
8675
8676 fn column_parallel_resident_nvfp4(
8677 &self,
8678 matrix: &ResidentNvfp4ColumnParallel,
8679 activations: &[f32],
8680 tokens: usize,
8681 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8682 validate_activations(activations, tokens, matrix.in_features)?;
8683 let local_out = matrix.out_features / self.ranks.len();
8684 let mut gathered = vec![0.0f32; tokens * matrix.out_features];
8685 let mut macro_scale = None;
8686 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8687 let output = run_resident_rank_nvfp4(engine, shard, activations, tokens)?;
8688 let row_start = rank_index * local_out;
8689 for token in 0..tokens {
8690 gathered[token * matrix.out_features + row_start
8691 ..token * matrix.out_features + row_start + local_out]
8692 .copy_from_slice(&output[token * local_out..(token + 1) * local_out]);
8693 }
8694 macro_scale = Some(shard.macro_scale);
8695 }
8696 apply_macro(
8697 &mut gathered,
8698 macro_scale.ok_or("NVFP4 column-parallel matrix has no ranks")?,
8699 );
8700 Ok(gathered)
8701 }
8702
8703 fn row_parallel_resident_nvfp4(
8704 &self,
8705 matrix: &ResidentNvfp4RowParallel,
8706 activations: &[f32],
8707 tokens: usize,
8708 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8709 validate_activations(activations, tokens, matrix.in_features)?;
8710 let tp = self.ranks.len();
8711 let local_in = matrix.in_features / tp;
8712 let mut reduced = vec![0.0f32; tokens * matrix.out_features];
8713 let mut macro_scale = None;
8714 for (rank_index, (engine, shard)) in self.ranks.iter().zip(&matrix.ranks).enumerate() {
8715 if shard.in_features != local_in {
8716 return Err(format!(
8717 "NVFP4 resident row shard in_features {} != expected {local_in}",
8718 shard.in_features
8719 )
8720 .into());
8721 }
8722 let local_activations =
8723 activation_shard(activations, tokens, matrix.in_features, tp, rank_index);
8724 let partial = run_resident_rank_nvfp4(engine, shard, &local_activations, tokens)?;
8725 for (sum, value) in reduced.iter_mut().zip(&partial) {
8726 *sum += *value;
8727 }
8728 macro_scale = Some(shard.macro_scale);
8729 }
8730 apply_macro(
8731 &mut reduced,
8732 macro_scale.ok_or("NVFP4 row-parallel matrix has no ranks")?,
8733 );
8734 Ok(reduced)
8735 }
8736
8737 pub fn run_expert_nvfp4(
8738 &self,
8739 expert: &ResidentTpNvfp4Expert,
8740 input: &[f32],
8741 tokens: usize,
8742 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8743 validate_activations(input, tokens, expert.input_width)?;
8744 let gate = self.column_parallel_resident_nvfp4(&expert.gate, input, tokens)?;
8745 let up = self.column_parallel_resident_nvfp4(&expert.up, input, tokens)?;
8746 let activated: Vec<f32> = gate
8747 .iter()
8748 .zip(&up)
8749 .map(|(&gate, &up)| gate / (1.0 + (-gate).exp()) * up)
8750 .collect();
8751 debug_assert_eq!(activated.len(), tokens * expert.expert_width);
8752 self.row_parallel_resident_nvfp4(&expert.down, &activated, tokens)
8753 }
8754
8755 pub fn upload_tensor_parallel_nvfp4(
8757 &self,
8758 gate: Nvfp4ExpertBank<'_>,
8759 up: Nvfp4ExpertBank<'_>,
8760 down: Nvfp4ExpertBank<'_>,
8761 ) -> Result<ResidentNvfp4TensorParallel, Box<dyn std::error::Error>> {
8762 gate.validate()?;
8763 up.validate()?;
8764 down.validate()?;
8765 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
8766 return Err("NVFP4 TP gate/up/down expert counts differ".into());
8767 }
8768 if gate.in_features != up.in_features || gate.out_features != up.out_features {
8769 return Err("NVFP4 TP gate/up dimensions differ".into());
8770 }
8771 if down.in_features != gate.out_features || down.out_features != gate.in_features {
8772 return Err(format!(
8773 "NVFP4 TP down {}x{} does not invert gate/up {}x{}",
8774 down.out_features, down.in_features, gate.out_features, gate.in_features
8775 )
8776 .into());
8777 }
8778 let tp = self.ranks.len();
8779 if gate.out_features % tp != 0 {
8780 return Err(format!(
8781 "NVFP4 TP expert output width {} is not divisible by TP={tp}",
8782 gate.out_features
8783 )
8784 .into());
8785 }
8786 if down.in_features % NVFP4_CANONICAL_ROW_SHARDS != 0
8787 || (down.in_features / NVFP4_CANONICAL_ROW_SHARDS) % 64 != 0
8788 {
8789 return Err(format!(
8790 "NVFP4 TP expert input width {} does not split into 64-aligned canonical \
8791 shards ({NVFP4_CANONICAL_ROW_SHARDS})",
8792 down.in_features
8793 )
8794 .into());
8795 }
8796 if tp > NVFP4_CANONICAL_ROW_SHARDS {
8797 return Err(format!(
8798 "NVFP4 TP world {tp} exceeds the canonical row-shard grid \
8799 ({NVFP4_CANONICAL_ROW_SHARDS})"
8800 )
8801 .into());
8802 }
8803
8804 let mut gate_ranks = Vec::with_capacity(tp);
8805 let mut up_ranks = Vec::with_capacity(tp);
8806 let mut macros_gate_dev = Vec::with_capacity(tp);
8807 let mut macros_up_dev = Vec::with_capacity(tp);
8808 let mut macros_down_dev = Vec::with_capacity(tp);
8809 for (rank_index, engine) in self.ranks.iter().enumerate() {
8810 let _main = engine.gpu.enter_main()?;
8811 let mut gate_host: Vec<u8> = Vec::new();
8815 let mut up_host: Vec<u8> = Vec::new();
8816 for expert in 0..gate.expert_count {
8817 let gate_shard = nvfp4_column_shard(gate.expert(expert)?, tp, rank_index)?;
8818 gate_host.extend_from_slice(&nvfp4_repack_bank_matrix(gate_shard));
8819 let up_shard = nvfp4_column_shard(up.expert(expert)?, tp, rank_index)?;
8820 up_host.extend_from_slice(&nvfp4_repack_bank_matrix(up_shard));
8821 }
8822 let gate_expert_bytes = gate_host.len() / gate.expert_count;
8823 let up_expert_bytes = up_host.len() / up.expert_count;
8824 gate_ranks.push(ResidentNvfp4ColumnBankRank {
8825 bank: engine.htod_bytes(&gate_host)?,
8826 expert_bytes: gate_expert_bytes,
8827 local_out: gate.out_features / tp,
8828 in_features: gate.in_features,
8829 row_bytes: nvfp4_row_bytes(gate.in_features),
8830 });
8831 up_ranks.push(ResidentNvfp4ColumnBankRank {
8832 bank: engine.htod_bytes(&up_host)?,
8833 expert_bytes: up_expert_bytes,
8834 local_out: up.out_features / tp,
8835 in_features: up.in_features,
8836 row_bytes: nvfp4_row_bytes(up.in_features),
8837 });
8838 macros_gate_dev.push(engine.htod(gate.macros)?);
8839 macros_up_dev.push(engine.htod(up.macros)?);
8840 macros_down_dev.push(engine.htod(down.macros)?);
8841 }
8842 let mut down_ranks = Vec::with_capacity(NVFP4_CANONICAL_ROW_SHARDS);
8846 for shard_index in 0..NVFP4_CANONICAL_ROW_SHARDS {
8847 let device_rank = shard_index % tp;
8848 let engine = &self.ranks[device_rank];
8849 let _main = engine.gpu.enter_main()?;
8850 let mut down_host: Vec<u8> = Vec::new();
8851 for expert in 0..down.expert_count {
8852 let down_matrix = down.expert(expert)?;
8853 let (codes, scales, local_in) =
8854 nvfp4_row_shard(down_matrix, NVFP4_CANONICAL_ROW_SHARDS, shard_index)?;
8855 down_host.extend_from_slice(&nvfp4_repack_bank_matrix(Nvfp4BlockMatrix {
8856 codes: &codes,
8857 scales: &scales,
8858 macro_scale: down_matrix.macro_scale,
8859 out_features: down_matrix.out_features,
8860 in_features: local_in,
8861 }));
8862 }
8863 let down_expert_bytes = down_host.len() / down.expert_count;
8864 down_ranks.push(ResidentNvfp4RowBankRank {
8865 bank: engine.htod_bytes(&down_host)?,
8866 expert_bytes: down_expert_bytes,
8867 device_rank,
8868 out_features: down.out_features,
8869 local_in: down.in_features / NVFP4_CANONICAL_ROW_SHARDS,
8870 row_bytes: nvfp4_row_bytes(down.in_features / NVFP4_CANONICAL_ROW_SHARDS),
8871 });
8872 }
8873 Ok(ResidentNvfp4TensorParallel {
8874 gate: gate_ranks,
8875 up: up_ranks,
8876 down: down_ranks,
8877 macros_gate: gate.macros.to_vec(),
8878 macros_up: up.macros.to_vec(),
8879 macros_down: down.macros.to_vec(),
8880 macros_gate_dev,
8881 macros_up_dev,
8882 macros_down_dev,
8883 expert_count: gate.expert_count,
8884 input_width: gate.in_features,
8885 expert_width: gate.out_features,
8886 device_workspace: std::sync::Mutex::new(None),
8887 t2_workspace: std::sync::Mutex::new(None),
8888 })
8889 }
8890
8891 fn run_column_bank_expert_nvfp4(
8892 &self,
8893 ranks: &[ResidentNvfp4ColumnBankRank],
8894 macros: &[f32],
8895 expert: usize,
8896 input: &[f32],
8897 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8898 let local_out = ranks
8899 .first()
8900 .ok_or("NVFP4 TP column bank has no ranks")?
8901 .local_out;
8902 let mut gathered = vec![0.0f32; local_out * ranks.len()];
8903 for (rank_index, (engine, bank)) in self.ranks.iter().zip(ranks).enumerate() {
8904 let _main = engine.gpu.enter_main()?;
8905 let activations = engine.htod(input)?;
8906 let output = if nvfp4_bank_v2_on() {
8907 engine.qmatvec_nvfp4_fast_v2(
8908 &bank.expert(expert),
8909 &activations,
8910 1,
8911 bank.in_features,
8912 bank.local_out,
8913 bank.row_bytes,
8914 )?
8915 } else {
8916 engine.qmatvec_nvfp4_fast(
8917 &bank.expert(expert),
8918 &activations,
8919 1,
8920 bank.in_features,
8921 bank.local_out,
8922 bank.row_bytes,
8923 )?
8924 };
8925 let output = engine.dtoh(&output)?;
8926 gathered[rank_index * local_out..(rank_index + 1) * local_out].copy_from_slice(&output);
8927 }
8928 apply_macro(&mut gathered, macros[expert]);
8929 Ok(gathered)
8930 }
8931
8932 fn run_row_bank_expert_nvfp4(
8936 &self,
8937 shards: &[ResidentNvfp4RowBankRank],
8938 macros: &[f32],
8939 expert: usize,
8940 input: &[f32],
8941 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8942 let out_features = shards
8943 .first()
8944 .ok_or("NVFP4 TP row bank has no canonical shards")?
8945 .out_features;
8946 let in_features = shards.iter().map(|shard| shard.local_in).sum::<usize>();
8947 let mut reduced = vec![0.0f32; out_features];
8948 for (shard_index, shard) in shards.iter().enumerate() {
8949 let engine = self
8950 .ranks
8951 .get(shard.device_rank)
8952 .ok_or("NVFP4 canonical shard names a rank outside this runtime")?;
8953 let _main = engine.gpu.enter_main()?;
8954 let local_activations =
8955 activation_shard(input, 1, in_features, shards.len(), shard_index);
8956 let activations = engine.htod(&local_activations)?;
8957 let output = if nvfp4_bank_v2_on() {
8958 engine.qmatvec_nvfp4_fast_v2(
8959 &shard.expert(expert),
8960 &activations,
8961 1,
8962 shard.local_in,
8963 shard.out_features,
8964 shard.row_bytes,
8965 )?
8966 } else {
8967 engine.qmatvec_nvfp4_fast(
8968 &shard.expert(expert),
8969 &activations,
8970 1,
8971 shard.local_in,
8972 shard.out_features,
8973 shard.row_bytes,
8974 )?
8975 };
8976 let partial = engine.dtoh(&output)?;
8977 for (sum, value) in reduced.iter_mut().zip(&partial) {
8978 *sum += *value;
8979 }
8980 }
8981 apply_macro(&mut reduced, macros[expert]);
8982 Ok(reduced)
8983 }
8984
8985 pub fn upload_expert_parallel_nvfp4(
8989 &self,
8990 gate: Nvfp4ExpertBank<'_>,
8991 up: Nvfp4ExpertBank<'_>,
8992 down: Nvfp4ExpertBank<'_>,
8993 ) -> Result<ResidentNvfp4ExpertParallel, Box<dyn std::error::Error>> {
8994 gate.validate()?;
8995 up.validate()?;
8996 down.validate()?;
8997 if gate.expert_count != up.expert_count || gate.expert_count != down.expert_count {
8998 return Err("NVFP4 EP gate/up/down expert counts differ".into());
8999 }
9000 if gate.in_features != up.in_features || gate.out_features != up.out_features {
9001 return Err("NVFP4 EP gate/up dimensions differ".into());
9002 }
9003 if down.in_features != gate.out_features || down.out_features != gate.in_features {
9004 return Err(format!(
9005 "NVFP4 EP down {}x{} does not invert gate/up {}x{}",
9006 down.out_features, down.in_features, gate.out_features, gate.in_features
9007 )
9008 .into());
9009 }
9010 let world = self.ranks.len();
9011 if gate.expert_count % world != 0 {
9012 return Err(format!(
9013 "NVFP4 EP expert count {} is not divisible by {world} ranks",
9014 gate.expert_count
9015 )
9016 .into());
9017 }
9018 let experts_per_rank = gate.expert_count / world;
9019 let mut ranks = Vec::with_capacity(world);
9020 for (rank_index, engine) in self.ranks.iter().enumerate() {
9021 let _main = engine.gpu.enter_main()?;
9022 let expert_range = rank_index * experts_per_rank..(rank_index + 1) * experts_per_rank;
9023 let mut gate_experts = Vec::with_capacity(experts_per_rank);
9024 let mut up_experts = Vec::with_capacity(experts_per_rank);
9025 let mut down_experts = Vec::with_capacity(experts_per_rank);
9026 for expert in expert_range.clone() {
9027 gate_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(gate.expert(expert)?))?);
9028 up_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(up.expert(expert)?))?);
9029 down_experts.push(engine.htod_bytes(&nvfp4_repack_matrix(down.expert(expert)?))?);
9030 }
9031 ranks.push(ResidentNvfp4EpRank {
9032 gate: gate_experts,
9033 up: up_experts,
9034 down: down_experts,
9035 expert_range,
9036 });
9037 }
9038 Ok(ResidentNvfp4ExpertParallel {
9039 ranks,
9040 macros_gate: gate.macros.to_vec(),
9041 macros_up: up.macros.to_vec(),
9042 macros_down: down.macros.to_vec(),
9043 expert_count: gate.expert_count,
9044 input_width: gate.in_features,
9045 expert_width: gate.out_features,
9046 gate_row_bytes: nvfp4_row_bytes(gate.in_features),
9047 down_row_bytes: nvfp4_row_bytes(down.in_features),
9048 })
9049 }
9050
9051 #[allow(clippy::too_many_arguments)]
9057 pub fn run_routed_experts_nvfp4(
9058 &self,
9059 experts: &ResidentNvfp4ExpertParallel,
9060 input: &[f32],
9061 tokens: usize,
9062 selected: &[usize],
9063 route_weights: &[f32],
9064 experts_per_token: usize,
9065 activation_limit: Option<f32>,
9066 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9067 validate_activations(input, tokens, experts.input_width)?;
9068 let pairs = tokens
9069 .checked_mul(experts_per_token)
9070 .ok_or("NVFP4 EP route count overflow")?;
9071 if selected.len() != pairs || route_weights.len() != pairs {
9072 return Err(format!(
9073 "NVFP4 EP routes selected={} weights={} != tokens {tokens} x experts/token \
9074 {experts_per_token} ({pairs})",
9075 selected.len(),
9076 route_weights.len(),
9077 )
9078 .into());
9079 }
9080 if !route_weights.iter().all(|weight| weight.is_finite()) {
9081 return Err("NVFP4 EP route weights contain a non-finite value".into());
9082 }
9083 let experts_per_rank = experts.expert_count / experts.ranks.len();
9084 let mut output = vec![0.0f32; tokens * experts.input_width];
9085 for token in 0..tokens {
9086 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
9087 for slot in 0..experts_per_token {
9088 let pair = token * experts_per_token + slot;
9089 let expert = selected[pair];
9090 if expert >= experts.expert_count {
9091 return Err(format!(
9092 "NVFP4 EP selected expert {expert} outside 0..{}",
9093 experts.expert_count
9094 )
9095 .into());
9096 }
9097 let owner = expert / experts_per_rank;
9098 let local = expert - owner * experts_per_rank;
9099 let rank = &experts.ranks[owner];
9100 let engine = &self.ranks[owner];
9101 let _main = engine.gpu.enter_main()?;
9102 let device_input = engine.htod(input_row)?;
9103 let gate_out = engine.qmatvec_nvfp4_fast(
9104 &rank.gate[local].slice(0..rank.gate[local].len()),
9105 &device_input,
9106 1,
9107 experts.input_width,
9108 experts.expert_width,
9109 experts.gate_row_bytes,
9110 )?;
9111 let up_out = engine.qmatvec_nvfp4_fast(
9112 &rank.up[local].slice(0..rank.up[local].len()),
9113 &device_input,
9114 1,
9115 experts.input_width,
9116 experts.expert_width,
9117 experts.gate_row_bytes,
9118 )?;
9119 let mut gate_host = engine.dtoh(&gate_out)?;
9120 let mut up_host = engine.dtoh(&up_out)?;
9121 apply_macro(&mut gate_host, experts.macros_gate[expert]);
9122 apply_macro(&mut up_host, experts.macros_up[expert]);
9123 let activated: Vec<f32> = gate_host
9124 .iter()
9125 .zip(&up_host)
9126 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
9127 .collect();
9128 let device_activated = engine.htod(&activated)?;
9129 let down_out = engine.qmatvec_nvfp4_fast(
9130 &rank.down[local].slice(0..rank.down[local].len()),
9131 &device_activated,
9132 1,
9133 experts.expert_width,
9134 experts.input_width,
9135 experts.down_row_bytes,
9136 )?;
9137 let mut down_host = engine.dtoh(&down_out)?;
9138 apply_macro(&mut down_host, experts.macros_down[expert]);
9139 let weight = route_weights[pair];
9140 for (sum, value) in output
9141 [token * experts.input_width..(token + 1) * experts.input_width]
9142 .iter_mut()
9143 .zip(down_host)
9144 {
9145 *sum += weight * value;
9146 }
9147 }
9148 }
9149 Ok(output)
9150 }
9151
9152 pub fn run_tensor_parallel_routes_nvfp4_device(
9166 &self,
9167 experts: &ResidentNvfp4TensorParallel,
9168 input: &[f32],
9169 selected: &[usize],
9170 route_weights: &[f32],
9171 experts_per_token: usize,
9172 activation_limit: Option<f32>,
9173 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
9174 validate_activations(input, 1, experts.input_width)?;
9175 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9176 return Err(format!(
9177 "NVFP4 device routes selected={} weights={} != experts/token {experts_per_token}",
9178 selected.len(),
9179 route_weights.len(),
9180 )
9181 .into());
9182 }
9183 if !route_weights.iter().all(|weight| weight.is_finite()) {
9184 return Err("NVFP4 device route weights contain a non-finite value".into());
9185 }
9186 let world = self.ranks.len();
9187 if world != NVFP4_CANONICAL_ROW_SHARDS {
9188 return Err(format!(
9189 "NVFP4 device routes require world == canonical shard grid \
9190 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9191 )
9192 .into());
9193 }
9194 let local_out = experts.expert_width / world;
9195
9196 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9200 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9201 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9202 let started = timing.then(std::time::Instant::now);
9203
9204 let n_sel = experts_per_token;
9205 let mut workspace_guard = experts
9206 .device_workspace
9207 .lock()
9208 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9209 if workspace_guard.is_none() {
9210 let mut gate_out = Vec::with_capacity(world);
9211 let mut up_out = Vec::with_capacity(world);
9212 let mut act_q = Vec::with_capacity(world);
9213 let mut act_d = Vec::with_capacity(world);
9214 let mut sel = Vec::with_capacity(world);
9215 let mut partial = Vec::with_capacity(world);
9216 let mut accumulator = Vec::with_capacity(world);
9217 let mut combine_w = Vec::with_capacity(world);
9218 let mut route_w = Vec::with_capacity(world);
9219 let mut in_q = Vec::with_capacity(world);
9220 let mut in_d = Vec::with_capacity(world);
9221 let mut input = Vec::with_capacity(world);
9222 let mut ev_rank = Vec::with_capacity(world);
9223 let moe_direct = moe_direct_on();
9224 for (rank, engine) in self.ranks.iter().enumerate() {
9225 let _main = engine.gpu.enter_main()?;
9226 gate_out.push(engine.uninit(n_sel * local_out)?);
9227 up_out.push(engine.uninit(n_sel * local_out)?);
9228 act_q.push(engine.uninit_i8(n_sel * local_out)?);
9229 act_d.push(engine.uninit(n_sel * local_out / 32)?);
9230 sel.push(engine.htod_i32(&vec![0i32; n_sel])?);
9231 partial.push(engine.uninit(n_sel * experts.input_width)?);
9232 if moe_direct && rank != 0 {
9234 let root = &self.ranks[0];
9235 let _root_main = root.gpu.enter_main()?;
9236 accumulator.push(root.zeros(experts.input_width)?);
9237 } else {
9238 accumulator.push(engine.zeros(experts.input_width)?);
9239 }
9240 combine_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9241 route_w.push(engine.htod(&vec![0.0f32; n_sel])?);
9242 in_q.push(engine.uninit_i8(experts.input_width)?);
9243 in_d.push(engine.uninit(experts.input_width / 32)?);
9244 input.push(engine.uninit(experts.input_width)?);
9245 ev_rank.push(engine.ctx().new_event(None)?);
9246 }
9247 let root = &self.ranks[0];
9248 let _main = root.gpu.enter_main()?;
9249 *workspace_guard = Some(Nvfp4DeviceRoutesWorkspace {
9250 prestaged: false,
9251 rank1_routed: false,
9252 ev_input: None,
9253 fence_flags_raw: 0,
9254 fence_ticket: 0,
9255 gate_out,
9256 up_out,
9257 act_q,
9258 act_d,
9259 sel,
9260 partial,
9261 accumulator,
9262 combine_w,
9263 route_w,
9264 in_q,
9265 in_d,
9266 dev_route_e: None,
9267 in_stage_e: None,
9268 out_stage_e: None,
9269 routes_graph: None,
9270 raw_dev_route_e: None,
9271 raw_combine: None,
9272 raw_input: Vec::new(),
9273 raw_sel: Vec::new(),
9274 raw_route_w: Vec::new(),
9275 remote: root.uninit(experts.input_width)?,
9276 combined: root.uninit(experts.input_width)?,
9277 n_sel,
9278 input,
9279 ev_rank,
9280 ev_done: Some(root.ctx().new_event(None)?),
9281 ev_entry: None,
9282 });
9283 }
9284 let workspace = workspace_guard
9285 .as_mut()
9286 .expect("NVFP4 device routes workspace initialized above");
9287 if workspace.n_sel != n_sel {
9288 return Err(format!(
9289 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
9290 workspace.n_sel
9291 )
9292 .into());
9293 }
9294 for &expert in selected {
9295 if expert >= experts.expert_count {
9296 return Err(format!(
9297 "NVFP4 device selected expert {expert} outside 0..{}",
9298 experts.expert_count
9299 )
9300 .into());
9301 }
9302 }
9303 let sel_i32 = selected
9304 .iter()
9305 .map(|&expert| expert as i32)
9306 .collect::<Vec<_>>();
9307
9308 for (rank_index, engine) in self.ranks.iter().enumerate() {
9315 let _main = engine.gpu.enter_main()?;
9316 let device_input = engine.htod(input)?;
9317 let Nvfp4DeviceRoutesWorkspace { in_q, in_d, .. } = &mut *workspace;
9318 engine.quantize_q8_1_into(
9319 &device_input,
9320 1,
9321 experts.input_width,
9322 &mut in_q[rank_index],
9323 &mut in_d[rank_index],
9324 )?;
9325 }
9327 self.nvfp4_routes_batched_sweeps(
9328 experts,
9329 workspace,
9330 selected,
9331 route_weights,
9332 &sel_i32,
9333 local_out,
9334 n_sel,
9335 activation_limit,
9336 false,
9337 )?;
9338
9339 let root = &self.ranks[0];
9342 for engine in &self.ranks[1..] {
9343 let _main = engine.gpu.enter_main()?;
9344 engine.stream().synchronize()?;
9345 }
9346 let _main = root.gpu.enter_main()?;
9347 root.stream()
9348 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
9349 root.add(
9350 &workspace.accumulator[0],
9351 &workspace.remote,
9352 &mut workspace.combined,
9353 experts.input_width,
9354 )?;
9355 let output = root.dtoh(&workspace.combined)?;
9356 if let Some(started) = started {
9357 use std::sync::atomic::Ordering;
9358 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9359 + started.elapsed().as_nanos() as u64;
9360 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9361 if calls % 430 == 0 {
9362 eprintln!(
9363 "[nvfp4-dev-routes-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9364 ns as f64 / 1.0e6,
9365 ns as f64 / calls as f64 / 1.0e3,
9366 );
9367 }
9368 }
9369 Ok(output)
9370 }
9371
9372 #[allow(clippy::too_many_arguments)]
9377 fn nvfp4_routes_batched_sweeps(
9378 &self,
9379 experts: &ResidentNvfp4TensorParallel,
9380 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9381 selected: &[usize],
9382 route_weights: &[f32],
9383 sel_i32: &[i32],
9384 local_out: usize,
9385 n_sel: usize,
9386 activation_limit: Option<f32>,
9387 device_routed: bool,
9388 ) -> Result<(), Box<dyn std::error::Error>> {
9389 for rank_index in 0..self.ranks.len() {
9390 self.nvfp4_routes_batched_sweeps_rank(
9391 experts,
9392 workspace,
9393 selected,
9394 route_weights,
9395 sel_i32,
9396 local_out,
9397 n_sel,
9398 activation_limit,
9399 device_routed,
9400 rank_index,
9401 )?;
9402 }
9403 Ok(())
9404 }
9405
9406 #[allow(clippy::too_many_arguments)]
9409 fn nvfp4_routes_batched_sweeps_rank(
9410 &self,
9411 experts: &ResidentNvfp4TensorParallel,
9412 workspace: &mut Nvfp4DeviceRoutesWorkspace,
9413 selected: &[usize],
9414 route_weights: &[f32],
9415 sel_i32: &[i32],
9416 local_out: usize,
9417 n_sel: usize,
9418 activation_limit: Option<f32>,
9419 device_routed: bool,
9420 rank_index: usize,
9421 ) -> Result<(), Box<dyn std::error::Error>> {
9422 {
9423 let engine = &self.ranks[rank_index];
9424 let _main = engine.gpu.enter_main()?;
9425 if !device_routed {
9426 engine.htod_i32_into(&mut workspace.sel[rank_index], sel_i32)?;
9427 let folded = (0..n_sel)
9430 .map(|pair| route_weights[pair] * experts.macros_down[selected[pair]])
9431 .collect::<Vec<_>>();
9432 let mut view = workspace.combine_w[rank_index].slice_mut(0..n_sel);
9433 engine.stream().memcpy_htod(&folded, &mut view)?;
9434 }
9435 let gate_bank = &experts.gate[rank_index];
9436 let up_bank = &experts.up[rank_index];
9437 let (aq, ad) = (&workspace.in_q[rank_index], &workspace.in_d[rank_index]);
9438 let gu_fused = nvfp4_bank_v2_on()
9441 && gate_bank.in_features == up_bank.in_features
9442 && gate_bank.local_out == up_bank.local_out
9443 && gate_bank.row_bytes == up_bank.row_bytes
9444 && gate_bank.expert_bytes == up_bank.expert_bytes;
9445 if gu_fused {
9446 let Nvfp4DeviceRoutesWorkspace {
9447 sel,
9448 gate_out,
9449 up_out,
9450 in_q,
9451 in_d,
9452 ..
9453 } = &mut *workspace;
9454 engine.qmatvec_nvfp4_sel_gu_into(
9455 &gate_bank.bank,
9456 &up_bank.bank,
9457 &sel[rank_index],
9458 &in_q[rank_index],
9459 &in_d[rank_index],
9460 &mut gate_out[rank_index],
9461 &mut up_out[rank_index],
9462 n_sel,
9463 gate_bank.in_features,
9464 gate_bank.local_out,
9465 gate_bank.row_bytes,
9466 gate_bank.expert_bytes,
9467 )?;
9468 } else {
9469 engine.qmatvec_nvfp4_sel_into(
9470 &gate_bank.bank,
9471 &workspace.sel[rank_index],
9472 aq,
9473 ad,
9474 &mut workspace.gate_out[rank_index],
9475 n_sel,
9476 gate_bank.in_features,
9477 gate_bank.local_out,
9478 gate_bank.row_bytes,
9479 gate_bank.expert_bytes,
9480 0,
9481 0,
9482 )?;
9483 engine.qmatvec_nvfp4_sel_into(
9484 &up_bank.bank,
9485 &workspace.sel[rank_index],
9486 aq,
9487 ad,
9488 &mut workspace.up_out[rank_index],
9489 n_sel,
9490 up_bank.in_features,
9491 up_bank.local_out,
9492 up_bank.row_bytes,
9493 up_bank.expert_bytes,
9494 0,
9495 0,
9496 )?;
9497 }
9498 {
9502 let Nvfp4DeviceRoutesWorkspace {
9503 gate_out,
9504 up_out,
9505 sel,
9506 act_q,
9507 act_d,
9508 ..
9509 } = &mut *workspace;
9510 engine.silu_mul_scaled_q8_1_sel_into(
9511 &gate_out[rank_index],
9512 &up_out[rank_index],
9513 &experts.macros_gate_dev[rank_index],
9514 &experts.macros_up_dev[rank_index],
9515 &sel[rank_index],
9516 activation_limit,
9517 &mut act_q[rank_index],
9518 &mut act_d[rank_index],
9519 local_out,
9520 n_sel,
9521 )?;
9522 }
9523 let shard = &experts.down[rank_index];
9524 if shard.device_rank != rank_index || shard.local_in != local_out {
9525 return Err(
9526 "NVFP4 device routes: down canonical shard placement drifted from \
9527 the gate/up column split"
9528 .into(),
9529 );
9530 }
9531 let down8 = device_routed && sel_down8_on() && (shard.local_in >> 5) <= 32;
9538 if down8 {
9539 let Nvfp4DeviceRoutesWorkspace {
9540 sel,
9541 act_q,
9542 act_d,
9543 route_w,
9544 accumulator,
9545 ..
9546 } = &mut *workspace;
9547 engine.qmatvec_nvfp4_sel_down8_into(
9548 &shard.bank,
9549 &sel[rank_index],
9550 &act_q[rank_index],
9551 &act_d[rank_index],
9552 &route_w[rank_index],
9553 &experts.macros_down_dev[rank_index],
9554 &mut accumulator[rank_index],
9555 n_sel,
9556 shard.local_in,
9557 shard.out_features,
9558 shard.row_bytes,
9559 shard.expert_bytes,
9560 local_out,
9561 local_out / 32,
9562 )?;
9563 } else {
9564 let Nvfp4DeviceRoutesWorkspace {
9565 sel,
9566 act_q,
9567 act_d,
9568 partial,
9569 ..
9570 } = &mut *workspace;
9571 engine.qmatvec_nvfp4_sel_into(
9572 &shard.bank,
9573 &sel[rank_index],
9574 &act_q[rank_index],
9575 &act_d[rank_index],
9576 &mut partial[rank_index],
9577 n_sel,
9578 shard.local_in,
9579 shard.out_features,
9580 shard.row_bytes,
9581 shard.expert_bytes,
9582 local_out,
9583 local_out / 32,
9584 )?;
9585 }
9586 if !down8 {
9591 let Nvfp4DeviceRoutesWorkspace {
9592 partial,
9593 combine_w,
9594 route_w,
9595 sel,
9596 accumulator,
9597 ..
9598 } = &mut *workspace;
9599 if device_routed {
9600 engine.axpy_rows_seq_md_into(
9601 &partial[rank_index],
9602 &route_w[rank_index],
9603 &experts.macros_down_dev[rank_index],
9604 &sel[rank_index],
9605 &mut accumulator[rank_index],
9606 experts.input_width,
9607 n_sel,
9608 )?;
9609 } else {
9610 engine.axpy_rows_seq_into(
9611 &partial[rank_index],
9612 &combine_w[rank_index],
9613 &mut accumulator[rank_index],
9614 experts.input_width,
9615 n_sel,
9616 )?;
9617 }
9618 }
9619 }
9620 Ok(())
9621 }
9622
9623 pub fn run_tensor_parallel_routes_nvfp4_device_io(
9631 &self,
9632 experts: &ResidentNvfp4TensorParallel,
9633 e: &Engine,
9634 input_dev: &crate::CudaSlice<f32>,
9635 selected: &[usize],
9636 route_weights: &[f32],
9637 experts_per_token: usize,
9638 activation_limit: Option<f32>,
9639 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
9640 if input_dev.len() != experts.input_width {
9641 return Err(format!(
9642 "NVFP4 device-io routes input {} != width {}",
9643 input_dev.len(),
9644 experts.input_width
9645 )
9646 .into());
9647 }
9648 if selected.len() != experts_per_token || route_weights.len() != experts_per_token {
9649 return Err(format!(
9650 "NVFP4 device-io routes selected={} weights={} != experts/token {experts_per_token}",
9651 selected.len(),
9652 route_weights.len(),
9653 )
9654 .into());
9655 }
9656 if !route_weights.iter().all(|weight| weight.is_finite()) {
9657 return Err("NVFP4 device route weights contain a non-finite value".into());
9658 }
9659 let world = self.ranks.len();
9660 if world != NVFP4_CANONICAL_ROW_SHARDS {
9661 return Err(format!(
9662 "NVFP4 device routes require world == canonical shard grid \
9663 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
9664 )
9665 .into());
9666 }
9667 let local_out = experts.expert_width / world;
9668 let n_sel = experts_per_token;
9669
9670 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9671 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
9672 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
9673 let started = timing.then(std::time::Instant::now);
9674
9675 let mut workspace_guard = experts
9676 .device_workspace
9677 .lock()
9678 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9679 if workspace_guard.is_none() {
9680 drop(workspace_guard);
9681 let zero = vec![0.0f32; experts.input_width];
9684 let zero_sel = vec![0usize; n_sel];
9685 let zero_w = vec![0.0f32; n_sel];
9686 let _ = self.run_tensor_parallel_routes_nvfp4_device(
9687 experts,
9688 &zero,
9689 &zero_sel,
9690 &zero_w,
9691 n_sel,
9692 activation_limit,
9693 )?;
9694 workspace_guard = experts
9695 .device_workspace
9696 .lock()
9697 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9698 }
9699 let workspace = workspace_guard
9700 .as_mut()
9701 .expect("NVFP4 device routes workspace initialized above");
9702 if workspace.n_sel != n_sel {
9703 return Err(format!(
9704 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
9705 workspace.n_sel
9706 )
9707 .into());
9708 }
9709 for &expert in selected {
9710 if expert >= experts.expert_count {
9711 return Err(format!(
9712 "NVFP4 device selected expert {expert} outside 0..{}",
9713 experts.expert_count
9714 )
9715 .into());
9716 }
9717 }
9718 let sel_i32 = selected
9719 .iter()
9720 .map(|&expert| expert as i32)
9721 .collect::<Vec<_>>();
9722
9723 if let Some((_, device)) = workspace.ev_entry.as_ref() {
9727 if *device != e.ctx().ordinal() {
9728 return Err("NVFP4 device-io routes engine changed".into());
9729 }
9730 } else {
9731 let _main = e.gpu.enter_main()?;
9732 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
9733 }
9734 {
9735 let _main = e.gpu.enter_main()?;
9736 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
9737 ev_entry.record(&e.stream())?;
9738 }
9739 for (rank_index, engine) in self.ranks.iter().enumerate() {
9740 let _main = engine.gpu.enter_main()?;
9741 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
9742 engine.stream().wait(ev_entry)?;
9743 {
9744 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
9745 engine
9746 .stream()
9747 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
9748 }
9749 {
9750 let Nvfp4DeviceRoutesWorkspace {
9751 input, in_q, in_d, ..
9752 } = &mut *workspace;
9753 engine.quantize_q8_1_into(
9754 &input[rank_index],
9755 1,
9756 experts.input_width,
9757 &mut in_q[rank_index],
9758 &mut in_d[rank_index],
9759 )?;
9760 }
9761 }
9762 self.nvfp4_routes_batched_sweeps(
9763 experts,
9764 workspace,
9765 selected,
9766 route_weights,
9767 &sel_i32,
9768 local_out,
9769 n_sel,
9770 activation_limit,
9771 false,
9772 )?;
9773
9774 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
9780 let _main = engine.gpu.enter_main()?;
9781 workspace.ev_rank[rank_index].record(&engine.stream())?;
9782 }
9783 if moe_direct_on() && self.ranks.len() == 2 {
9784 {
9791 let root = &self.ranks[0];
9792 let _main = root.gpu.enter_main()?;
9793 workspace
9794 .ev_done
9795 .as_ref()
9796 .expect("device routes done event")
9797 .record(&root.stream())?;
9798 }
9799 let _main = e.gpu.enter_main()?;
9800 e.stream().wait(
9801 workspace
9802 .ev_done
9803 .as_ref()
9804 .expect("device routes done event"),
9805 )?;
9806 for ev in workspace.ev_rank.iter().skip(1) {
9807 e.stream().wait(ev)?;
9808 }
9809 let mut output = e.uninit(experts.input_width)?;
9810 e.add(
9811 &workspace.accumulator[0],
9812 &workspace.accumulator[1],
9813 &mut output,
9814 experts.input_width,
9815 )?;
9816 let output = output;
9817 if let Some(started) = started {
9818 use std::sync::atomic::Ordering;
9819 let ns = TIMING_NS
9820 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9821 + started.elapsed().as_nanos() as u64;
9822 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9823 if calls % 430 == 0 {
9824 eprintln!(
9825 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9826 ns as f64 / 1.0e6,
9827 ns as f64 / calls as f64 / 1.0e3,
9828 );
9829 }
9830 }
9831 return Ok(output);
9832 }
9833 {
9834 let root = &self.ranks[0];
9835 let _main = root.gpu.enter_main()?;
9836 for ev in workspace.ev_rank.iter().skip(1) {
9837 root.stream().wait(ev)?;
9838 }
9839 root.stream()
9840 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
9841 {
9842 let Nvfp4DeviceRoutesWorkspace {
9843 accumulator,
9844 remote,
9845 combined,
9846 ..
9847 } = &mut *workspace;
9848 root.add(&accumulator[0], remote, combined, experts.input_width)?;
9849 }
9850 workspace
9851 .ev_done
9852 .as_ref()
9853 .expect("device routes done event")
9854 .record(&root.stream())?;
9855 }
9856 let output = {
9857 let _main = e.gpu.enter_main()?;
9858 e.stream().wait(
9859 workspace
9860 .ev_done
9861 .as_ref()
9862 .expect("device routes done event"),
9863 )?;
9864 let mut output = e.uninit(experts.input_width)?;
9867 e.stream().memcpy_dtod(
9868 &workspace.combined.slice(0..experts.input_width),
9869 &mut output.slice_mut(0..experts.input_width),
9870 )?;
9871 output
9872 };
9873 if let Some(started) = started {
9874 use std::sync::atomic::Ordering;
9875 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
9876 + started.elapsed().as_nanos() as u64;
9877 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
9878 if calls % 430 == 0 {
9879 eprintln!(
9880 "[nvfp4-dev-routes-io-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
9881 ns as f64 / 1.0e6,
9882 ns as f64 / calls as f64 / 1.0e3,
9883 );
9884 }
9885 }
9886 Ok(output)
9887 }
9888
9889 #[allow(clippy::too_many_arguments)]
9895 pub fn nvfp4_routes_prestage(
9900 &self,
9901 experts: &ResidentNvfp4TensorParallel,
9902 e: &Engine,
9903 input_dev: &crate::CudaSlice<f32>,
9904 ) -> Result<bool, Box<dyn std::error::Error>> {
9905 self.nvfp4_routes_prestage_with(experts, e, input_dev, |_, _, _, _| Ok(false))
9906 }
9907
9908 pub fn nvfp4_routes_prestage_with(
9914 &self,
9915 experts: &ResidentNvfp4TensorParallel,
9916 e: &Engine,
9917 input_dev: &crate::CudaSlice<f32>,
9918 rank1_router: impl FnOnce(
9919 &Engine,
9920 &crate::CudaSlice<f32>,
9921 &mut crate::CudaSlice<i32>,
9922 &mut crate::CudaSlice<f32>,
9923 ) -> Result<bool, Box<dyn std::error::Error>>,
9924 ) -> Result<bool, Box<dyn std::error::Error>> {
9925 if !routes_prestage_on() || step_tp_graph_enabled()? {
9926 return Ok(false);
9927 }
9928 if input_dev.len() != experts.input_width {
9929 return Err("NVFP4 prestage input width mismatch".into());
9930 }
9931 let mut workspace_guard = experts
9932 .device_workspace
9933 .lock()
9934 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
9935 let Some(workspace) = workspace_guard.as_mut() else {
9936 return Ok(false);
9937 };
9938 if workspace.ev_input.is_none() {
9939 let _main = e.gpu.enter_main()?;
9940 workspace.ev_input = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
9941 } else if workspace.ev_input.as_ref().map(|(_, d)| *d) != Some(e.ctx().ordinal()) {
9942 return Err("NVFP4 prestage engine changed".into());
9943 }
9944 {
9945 let _main = e.gpu.enter_main()?;
9946 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
9947 ev.record(&e.stream())?;
9948 }
9949 for (rank_index, engine) in self.ranks.iter().enumerate() {
9950 let _main = engine.gpu.enter_main()?;
9951 let (ev, _) = workspace.ev_input.as_ref().expect("armed above");
9952 engine.stream().wait(ev)?;
9953 {
9954 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
9955 engine
9956 .stream()
9957 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
9958 }
9959 {
9960 let Nvfp4DeviceRoutesWorkspace {
9961 input, in_q, in_d, ..
9962 } = &mut *workspace;
9963 engine.quantize_q8_1_into(
9964 &input[rank_index],
9965 1,
9966 experts.input_width,
9967 &mut in_q[rank_index],
9968 &mut in_d[rank_index],
9969 )?;
9970 }
9971 }
9972 if self.ranks.len() == 2 {
9973 let rank1 = &self.ranks[1];
9974 let _r1 = rank1.gpu.enter_main()?;
9975 let Nvfp4DeviceRoutesWorkspace {
9976 input,
9977 sel,
9978 route_w,
9979 ..
9980 } = &mut *workspace;
9981 let (in1, rest_sel) = (&input[1], &mut sel[1]);
9982 if rank1_router(rank1, in1, rest_sel, &mut route_w[1])? {
9983 workspace.rank1_routed = true;
9984 }
9985 }
9986 workspace.prestaged = true;
9987 Ok(true)
9988 }
9989
9990 #[allow(clippy::too_many_arguments)]
10001 pub fn run_tensor_parallel_routes_nvfp4_device_routed_t2(
10002 &self,
10003 experts: &ResidentNvfp4TensorParallel,
10004 e: &Engine,
10005 z2: &crate::CudaSlice<f32>,
10006 sel_d: &crate::CudaSlice<i32>,
10007 w_d: &crate::CudaSlice<f32>,
10008 n_sel_col: usize,
10009 activation_limit: Option<f32>,
10010 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10011 let world = self.ranks.len();
10012 if world != NVFP4_CANONICAL_ROW_SHARDS {
10013 return Err("NVFP4 t2 routes require the canonical 2-shard grid".into());
10014 }
10015 let width = experts.input_width;
10016 let n_sel = 2 * n_sel_col;
10017 if z2.len() < 2 * width || sel_d.len() < n_sel || w_d.len() < n_sel {
10018 return Err("NVFP4 t2 routes geometry".into());
10019 }
10020 if !nvfp4_bank_v2_on() {
10021 return Err("NVFP4 t2 routes require the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
10022 }
10023 let local_out = experts.expert_width / world;
10024 let mut guard = experts
10025 .t2_workspace
10026 .lock()
10027 .map_err(|_| "NVFP4 t2 workspace lock is poisoned")?;
10028 if guard.as_ref().is_none_or(|ws| ws.n_sel != n_sel) {
10029 let mut input2 = Vec::new();
10030 let mut in_q2 = Vec::new();
10031 let mut in_d2 = Vec::new();
10032 let mut sel2 = Vec::new();
10033 let mut route_w2 = Vec::new();
10034 let mut gate_out2 = Vec::new();
10035 let mut up_out2 = Vec::new();
10036 let mut act_q2 = Vec::new();
10037 let mut act_d2 = Vec::new();
10038 let mut partial2 = Vec::new();
10039 let mut acc_a = Vec::new();
10040 let mut acc_b = Vec::new();
10041 let mut ev_rank = Vec::new();
10042 for engine in &self.ranks {
10043 let _m = engine.gpu.enter_main()?;
10044 input2.push(engine.uninit(2 * width)?);
10045 in_q2.push(engine.alloc_i8_uninit(2 * width)?);
10046 in_d2.push(engine.uninit(2 * (width / 32))?);
10047 sel2.push(engine.htod_i32(&vec![0i32; n_sel])?);
10048 route_w2.push(engine.uninit(n_sel)?);
10049 gate_out2.push(engine.uninit(n_sel * local_out)?);
10050 up_out2.push(engine.uninit(n_sel * local_out)?);
10051 act_q2.push(engine.alloc_i8_uninit(n_sel * local_out)?);
10052 act_d2.push(engine.uninit(n_sel * (local_out / 32))?);
10053 partial2.push(engine.uninit(n_sel * width)?);
10054 acc_a.push(engine.uninit(width)?);
10055 acc_b.push(engine.uninit(width)?);
10056 ev_rank.push(engine.ctx().new_event(None)?);
10057 }
10058 let root = &self.ranks[0];
10059 let (peer_a, peer_b, omix_a, omix_b, ev_root) = {
10060 let _m = root.gpu.enter_main()?;
10061 (
10062 root.uninit(width)?,
10063 root.uninit(width)?,
10064 root.uninit(width)?,
10065 root.uninit(width)?,
10066 root.ctx().new_event(None)?,
10067 )
10068 };
10069 let ev_entry = {
10070 let _m = e.gpu.enter_main()?;
10071 e.ctx().new_event(None)?
10072 };
10073 *guard = Some(Nvfp4T2Workspace {
10074 input2,
10075 in_q2,
10076 in_d2,
10077 sel2,
10078 route_w2,
10079 gate_out2,
10080 up_out2,
10081 act_q2,
10082 act_d2,
10083 partial2,
10084 acc_a,
10085 acc_b,
10086 peer_a,
10087 peer_b,
10088 omix_a,
10089 omix_b,
10090 ev_entry,
10091 ev_rank,
10092 ev_root,
10093 n_sel,
10094 e_device: e.ctx().ordinal(),
10095 });
10096 }
10097 let ws = guard.as_mut().expect("armed above");
10098 if ws.e_device != e.ctx().ordinal() {
10099 return Err("NVFP4 t2 routes engine changed".into());
10100 }
10101 {
10102 let _main = e.gpu.enter_main()?;
10103 ws.ev_entry.record(&e.stream())?;
10104 }
10105 for rank in 0..world {
10106 let engine = &self.ranks[rank];
10107 let _main = engine.gpu.enter_main()?;
10108 engine.stream().wait(&ws.ev_entry)?;
10109 {
10110 let mut dst = ws.input2[rank].slice_mut(0..2 * width);
10111 engine
10112 .stream()
10113 .memcpy_dtod(&z2.slice(0..2 * width), &mut dst)?;
10114 }
10115 {
10116 let mut dst = ws.sel2[rank].slice_mut(0..n_sel);
10117 engine
10118 .stream()
10119 .memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10120 }
10121 {
10122 let mut dst = ws.route_w2[rank].slice_mut(0..n_sel);
10123 engine
10124 .stream()
10125 .memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10126 }
10127 {
10128 let Nvfp4T2Workspace {
10129 input2,
10130 in_q2,
10131 in_d2,
10132 ..
10133 } = &mut *ws;
10134 engine.quantize_q8_1_into(
10135 &input2[rank],
10136 2,
10137 width,
10138 &mut in_q2[rank],
10139 &mut in_d2[rank],
10140 )?;
10141 }
10142 let gate_bank = &experts.gate[rank];
10143 let up_bank = &experts.up[rank];
10144 if gate_bank.in_features != up_bank.in_features
10145 || gate_bank.local_out != up_bank.local_out
10146 || gate_bank.row_bytes != up_bank.row_bytes
10147 || gate_bank.expert_bytes != up_bank.expert_bytes
10148 {
10149 return Err("NVFP4 t2 routes need matched gate/up bank geometry".into());
10150 }
10151 {
10152 let Nvfp4T2Workspace {
10153 sel2,
10154 in_q2,
10155 in_d2,
10156 gate_out2,
10157 up_out2,
10158 ..
10159 } = &mut *ws;
10160 engine.qmatvec_nvfp4_sel_gu_tcol_into(
10161 &gate_bank.bank,
10162 &up_bank.bank,
10163 &sel2[rank],
10164 &in_q2[rank],
10165 &in_d2[rank],
10166 &mut gate_out2[rank],
10167 &mut up_out2[rank],
10168 n_sel,
10169 n_sel_col,
10170 gate_bank.in_features,
10171 gate_bank.local_out,
10172 gate_bank.row_bytes,
10173 gate_bank.expert_bytes,
10174 width,
10175 width / 32,
10176 )?;
10177 }
10178 {
10179 let Nvfp4T2Workspace {
10180 gate_out2,
10181 up_out2,
10182 sel2,
10183 act_q2,
10184 act_d2,
10185 ..
10186 } = &mut *ws;
10187 engine.silu_mul_scaled_q8_1_sel_into(
10188 &gate_out2[rank],
10189 &up_out2[rank],
10190 &experts.macros_gate_dev[rank],
10191 &experts.macros_up_dev[rank],
10192 &sel2[rank],
10193 activation_limit,
10194 &mut act_q2[rank],
10195 &mut act_d2[rank],
10196 local_out,
10197 n_sel,
10198 )?;
10199 }
10200 let shard = &experts.down[rank];
10201 if shard.device_rank != rank || shard.local_in != local_out {
10202 return Err("NVFP4 t2 routes: down shard placement drifted".into());
10203 }
10204 {
10205 let Nvfp4T2Workspace {
10206 sel2,
10207 act_q2,
10208 act_d2,
10209 partial2,
10210 ..
10211 } = &mut *ws;
10212 engine.qmatvec_nvfp4_sel_into(
10213 &shard.bank,
10214 &sel2[rank],
10215 &act_q2[rank],
10216 &act_d2[rank],
10217 &mut partial2[rank],
10218 n_sel,
10219 shard.local_in,
10220 shard.out_features,
10221 shard.row_bytes,
10222 shard.expert_bytes,
10223 local_out,
10224 local_out / 32,
10225 )?;
10226 }
10227 {
10228 let Nvfp4T2Workspace {
10229 partial2,
10230 route_w2,
10231 sel2,
10232 acc_a,
10233 acc_b,
10234 ..
10235 } = &mut *ws;
10236 engine.axpy_rows_seq_md_off_into(
10237 &partial2[rank],
10238 &route_w2[rank],
10239 &experts.macros_down_dev[rank],
10240 &sel2[rank],
10241 &mut acc_a[rank],
10242 width,
10243 n_sel_col,
10244 0,
10245 )?;
10246 engine.axpy_rows_seq_md_off_into(
10247 &partial2[rank],
10248 &route_w2[rank],
10249 &experts.macros_down_dev[rank],
10250 &sel2[rank],
10251 &mut acc_b[rank],
10252 width,
10253 n_sel_col,
10254 n_sel_col,
10255 )?;
10256 }
10257 if rank != 0 {
10258 ws.ev_rank[rank].record(&engine.stream())?;
10259 }
10260 }
10261 let root = &self.ranks[0];
10262 {
10263 let _main = root.gpu.enter_main()?;
10264 for ev in ws.ev_rank.iter().skip(1) {
10265 root.stream().wait(ev)?;
10266 }
10267 {
10268 let Nvfp4T2Workspace {
10269 acc_a,
10270 acc_b,
10271 peer_a,
10272 peer_b,
10273 omix_a,
10274 omix_b,
10275 ..
10276 } = &mut *ws;
10277 {
10278 let mut dst = peer_a.slice_mut(0..width);
10279 root.stream()
10280 .memcpy_dtod(&acc_a[1].slice(0..width), &mut dst)?;
10281 }
10282 {
10283 let mut dst = peer_b.slice_mut(0..width);
10284 root.stream()
10285 .memcpy_dtod(&acc_b[1].slice(0..width), &mut dst)?;
10286 }
10287 root.add(&acc_a[0], peer_a, omix_a, width)?;
10288 root.add(&acc_b[0], peer_b, omix_b, width)?;
10289 }
10290 ws.ev_root.record(&root.stream())?;
10291 }
10292 let _main = e.gpu.enter_main()?;
10293 e.stream().wait(&ws.ev_root)?;
10294 let mut out = e.uninit(2 * width)?;
10295 e.stream()
10296 .memcpy_dtod(&ws.omix_a.slice(0..width), &mut out.slice_mut(0..width))?;
10297 e.stream().memcpy_dtod(
10298 &ws.omix_b.slice(0..width),
10299 &mut out.slice_mut(width..2 * width),
10300 )?;
10301 Ok(out)
10302 }
10303
10304 pub fn run_tensor_parallel_routes_nvfp4_device_routed(
10305 &self,
10306 experts: &ResidentNvfp4TensorParallel,
10307 e: &Engine,
10308 input_dev: &crate::CudaSlice<f32>,
10309 sel_d: &crate::CudaSlice<i32>,
10310 w_d: &crate::CudaSlice<f32>,
10311 experts_per_token: usize,
10312 activation_limit: Option<f32>,
10313 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10314 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10315 experts,
10316 e,
10317 input_dev,
10318 sel_d,
10319 w_d,
10320 experts_per_token,
10321 activation_limit,
10322 || Ok(()),
10323 )
10324 }
10325
10326 #[allow(clippy::too_many_arguments)]
10332 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin(
10333 &self,
10334 experts: &ResidentNvfp4TensorParallel,
10335 e: &Engine,
10336 input_dev: &crate::CudaSlice<f32>,
10337 sel_d: &crate::CudaSlice<i32>,
10338 w_d: &crate::CudaSlice<f32>,
10339 experts_per_token: usize,
10340 activation_limit: Option<f32>,
10341 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10342 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10343 self.run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10344 experts,
10345 e,
10346 input_dev,
10347 sel_d,
10348 w_d,
10349 experts_per_token,
10350 activation_limit,
10351 pre_join,
10352 None,
10353 )
10354 }
10355
10356 #[allow(clippy::too_many_arguments)]
10361 pub fn run_tensor_parallel_routes_nvfp4_device_routed_prejoin_add3(
10362 &self,
10363 experts: &ResidentNvfp4TensorParallel,
10364 e: &Engine,
10365 input_dev: &crate::CudaSlice<f32>,
10366 sel_d: &crate::CudaSlice<i32>,
10367 w_d: &crate::CudaSlice<f32>,
10368 experts_per_token: usize,
10369 activation_limit: Option<f32>,
10370 pre_join: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
10371 post_add: Option<(u64, u64)>,
10372 ) -> Result<crate::CudaSlice<f32>, Box<dyn std::error::Error>> {
10373 if input_dev.len() != experts.input_width {
10374 return Err(format!(
10375 "NVFP4 device-routed input {} != width {}",
10376 input_dev.len(),
10377 experts.input_width
10378 )
10379 .into());
10380 }
10381 let n_sel = experts_per_token;
10382 if sel_d.len() < n_sel || w_d.len() < n_sel {
10383 return Err(format!(
10384 "NVFP4 device-routed routes sel={} w={} < experts/token {n_sel}",
10385 sel_d.len(),
10386 w_d.len()
10387 )
10388 .into());
10389 }
10390 let world = self.ranks.len();
10391 if world != NVFP4_CANONICAL_ROW_SHARDS {
10392 return Err(format!(
10393 "NVFP4 device routes require world == canonical shard grid \
10394 ({NVFP4_CANONICAL_ROW_SHARDS}), got {world}"
10395 )
10396 .into());
10397 }
10398 let local_out = experts.expert_width / world;
10399
10400 static TIMING_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10401 static TIMING_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
10402 let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
10403 let started = timing.then(std::time::Instant::now);
10404
10405 let mut workspace_guard = experts
10406 .device_workspace
10407 .lock()
10408 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10409 if workspace_guard.is_none() {
10410 drop(workspace_guard);
10411 let zero = vec![0.0f32; experts.input_width];
10412 let zero_sel = vec![0usize; n_sel];
10413 let zero_w = vec![0.0f32; n_sel];
10414 let _ = self.run_tensor_parallel_routes_nvfp4_device(
10415 experts,
10416 &zero,
10417 &zero_sel,
10418 &zero_w,
10419 n_sel,
10420 activation_limit,
10421 )?;
10422 workspace_guard = experts
10423 .device_workspace
10424 .lock()
10425 .map_err(|_| "NVFP4 device routes workspace lock is poisoned")?;
10426 }
10427 let workspace = workspace_guard
10428 .as_mut()
10429 .expect("NVFP4 device routes workspace initialized above");
10430 if workspace.n_sel != n_sel {
10431 return Err(format!(
10432 "NVFP4 device routes experts/token changed: workspace {} != call {n_sel}",
10433 workspace.n_sel
10434 )
10435 .into());
10436 }
10437
10438 if step_tp_graph_enabled()? {
10443 if workspace.dev_route_e.is_none() {
10444 let _main = e.gpu.enter_main()?;
10445 workspace.dev_route_e = Some((
10446 e.htod_i32(&vec![0i32; n_sel])?,
10447 e.htod(&vec![0.0f32; n_sel])?,
10448 ));
10449 }
10450 if workspace.in_stage_e.is_none() {
10451 let _main = e.gpu.enter_main()?;
10452 workspace.in_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
10453 workspace.out_stage_e = Some(e.htod(&vec![0.0f32; experts.input_width])?);
10454 }
10455 if workspace.routes_graph.is_none() {
10456 let graph = self.nvfp4_routes_build_graph(
10457 experts,
10458 workspace,
10459 local_out,
10460 n_sel,
10461 activation_limit,
10462 )?;
10463 workspace.routes_graph = Some(graph);
10464 eprintln!(
10465 "[step-tp-graph] routes segment captured: ranks={world} n_sel={n_sel} \
10466 children=3 updates=none performance_claim=false"
10467 );
10468 }
10469 let output = {
10470 let _main = e.gpu.enter_main()?;
10471 {
10472 let (sel_e, w_e) = workspace
10473 .dev_route_e
10474 .as_mut()
10475 .expect("device route staging set above");
10476 {
10477 let mut dst = sel_e.slice_mut(0..n_sel);
10478 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10479 }
10480 {
10481 let mut dst = w_e.slice_mut(0..n_sel);
10482 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10483 }
10484 }
10485 {
10486 let in_stage = workspace
10487 .in_stage_e
10488 .as_mut()
10489 .expect("graph staging set above");
10490 let mut dst = in_stage.slice_mut(0..experts.input_width);
10491 e.stream()
10492 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut dst)?;
10493 }
10494 unsafe {
10495 let r = cudarc::driver::sys::cuGraphLaunch(
10496 workspace
10497 .routes_graph
10498 .as_ref()
10499 .expect("routes graph built above")
10500 .exec,
10501 e.stream().cu_stream() as cudarc::driver::sys::CUstream,
10502 );
10503 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
10504 return Err(format!("routes graph launch: {r:?}").into());
10505 }
10506 }
10507 let mut output = e.uninit(experts.input_width)?;
10508 {
10509 let out_stage = workspace
10510 .out_stage_e
10511 .as_ref()
10512 .expect("graph staging set above");
10513 e.stream().memcpy_dtod(
10514 &out_stage.slice(0..experts.input_width),
10515 &mut output.slice_mut(0..experts.input_width),
10516 )?;
10517 }
10518 output
10519 };
10520 if let Some(started) = started {
10521 use std::sync::atomic::Ordering;
10522 let ns = TIMING_NS
10523 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10524 + started.elapsed().as_nanos() as u64;
10525 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10526 if calls % 430 == 0 {
10527 eprintln!(
10528 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10529 ns as f64 / 1.0e6,
10530 ns as f64 / calls as f64 / 1.0e3,
10531 );
10532 }
10533 }
10534 return Ok(output);
10535 }
10536
10537 if let Some((_, device)) = workspace.ev_entry.as_ref() {
10541 if *device != e.ctx().ordinal() {
10542 return Err("NVFP4 device-routed routes engine changed".into());
10543 }
10544 } else {
10545 let _main = e.gpu.enter_main()?;
10546 workspace.ev_entry = Some((e.ctx().new_event(None)?, e.ctx().ordinal()));
10547 }
10548 if workspace.dev_route_e.is_none() {
10549 let _main = e.gpu.enter_main()?;
10550 workspace.dev_route_e = Some((
10551 e.htod_i32(&vec![0i32; n_sel])?,
10552 e.htod(&vec![0.0f32; n_sel])?,
10553 ));
10554 }
10555 let mirror = sel_mirror_on() && !step_tp_graph_enabled()?;
10561 let e_device = e.ctx().ordinal();
10562 let rank1_routed_peek = workspace.rank1_routed;
10564 let stage_needed = !mirror
10565 || self.ranks.iter().enumerate().any(|(rank_index, engine)| {
10566 !(rank1_routed_peek && rank_index == 1) && engine.ctx().ordinal() != e_device
10567 });
10568 {
10569 let _main = e.gpu.enter_main()?;
10570 if stage_needed {
10571 let (sel_e, w_e) = workspace
10572 .dev_route_e
10573 .as_mut()
10574 .expect("device route staging set above");
10575 {
10576 let mut dst = sel_e.slice_mut(0..n_sel);
10577 e.stream().memcpy_dtod(&sel_d.slice(0..n_sel), &mut dst)?;
10578 }
10579 {
10580 let mut dst = w_e.slice_mut(0..n_sel);
10581 e.stream().memcpy_dtod(&w_d.slice(0..n_sel), &mut dst)?;
10582 }
10583 }
10584 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10585 ev_entry.record(&e.stream())?;
10586 }
10587 let prestaged = std::mem::take(&mut workspace.prestaged);
10590 let rank1_routed = std::mem::take(&mut workspace.rank1_routed);
10591 for (rank_index, engine) in self.ranks.iter().enumerate() {
10592 let _main = engine.gpu.enter_main()?;
10593 let (ev_entry, _) = workspace.ev_entry.as_ref().expect("entry event set above");
10594 engine.stream().wait(ev_entry)?;
10595 if !prestaged {
10596 let mut destination = workspace.input[rank_index].slice_mut(0..experts.input_width);
10597 engine
10598 .stream()
10599 .memcpy_dtod(&input_dev.slice(0..experts.input_width), &mut destination)?;
10600 }
10601 if !(rank1_routed && rank_index == 1) {
10602 let same_dev = engine.ctx().ordinal() == e_device;
10606 if mirror {
10607 let Nvfp4DeviceRoutesWorkspace {
10610 sel,
10611 route_w,
10612 dev_route_e,
10613 ..
10614 } = &mut *workspace;
10615 let (src_sel, src_w): (&crate::CudaSlice<i32>, &crate::CudaSlice<f32>) =
10616 if same_dev {
10617 (sel_d, w_d)
10618 } else {
10619 let (sel_e, w_e) = dev_route_e
10620 .as_ref()
10621 .expect("device route staging set above");
10622 (sel_e, w_e)
10623 };
10624 engine.moe_sel_w_mirror(
10625 src_sel,
10626 src_w,
10627 &mut sel[rank_index],
10628 &mut route_w[rank_index],
10629 n_sel,
10630 )?;
10631 } else {
10632 let (sel_e, w_e) = workspace
10633 .dev_route_e
10634 .as_ref()
10635 .expect("device route staging set above");
10636 {
10637 let mut dst = workspace.sel[rank_index].slice_mut(0..n_sel);
10638 engine
10639 .stream()
10640 .memcpy_dtod(&sel_e.slice(0..n_sel), &mut dst)?;
10641 }
10642 {
10643 let mut dst = workspace.route_w[rank_index].slice_mut(0..n_sel);
10644 engine
10645 .stream()
10646 .memcpy_dtod(&w_e.slice(0..n_sel), &mut dst)?;
10647 }
10648 }
10649 }
10650 if !prestaged {
10651 let Nvfp4DeviceRoutesWorkspace {
10652 input, in_q, in_d, ..
10653 } = &mut *workspace;
10654 engine.quantize_q8_1_into(
10655 &input[rank_index],
10656 1,
10657 experts.input_width,
10658 &mut in_q[rank_index],
10659 &mut in_d[rank_index],
10660 )?;
10661 }
10662 }
10663 self.nvfp4_routes_batched_sweeps(
10664 experts,
10665 workspace,
10666 &[],
10667 &[],
10668 &[],
10669 local_out,
10670 n_sel,
10671 activation_limit,
10672 true,
10673 )?;
10674
10675 for (rank_index, engine) in self.ranks.iter().enumerate().skip(1) {
10678 let _main = engine.gpu.enter_main()?;
10679 workspace.ev_rank[rank_index].record(&engine.stream())?;
10680 }
10681 let memops = fence_memops_on() && moe_direct_on() && self.ranks.len() == 2;
10684 let mut ticket = 0u32;
10685 if memops {
10686 use cudarc::driver::sys;
10687 if workspace.fence_flags_raw == 0 {
10688 let root = &self.ranks[0];
10689 let _main = root.gpu.enter_main()?;
10690 let mut ptr: sys::CUdeviceptr = 0;
10691 let r = unsafe { sys::cuMemAlloc_v2(&mut ptr, 8) };
10692 if r != sys::CUresult::CUDA_SUCCESS {
10693 return Err(format!("fence flag alloc: {r:?}").into());
10694 }
10695 let r = unsafe { sys::cuMemsetD8_v2(ptr, 0, 8) };
10696 if r != sys::CUresult::CUDA_SUCCESS {
10697 return Err(format!("fence flag memset: {r:?}").into());
10698 }
10699 workspace.fence_flags_raw = ptr as u64;
10700 }
10701 workspace.fence_ticket = workspace.fence_ticket.wrapping_add(1).max(1);
10702 ticket = workspace.fence_ticket;
10703 let base = workspace.fence_flags_raw;
10704 if fence_rank1_on() {
10710 let peer = &self.ranks[1];
10711 let _pmain = peer.gpu.enter_main()?;
10712 peer.ring_flag_raw(base, ticket)?;
10713 }
10714 {
10715 let root = &self.ranks[0];
10716 let _main = root.gpu.enter_main()?;
10717 let r = unsafe {
10718 sys::cuStreamWriteValue32_v2(
10719 root.stream().cu_stream() as sys::CUstream,
10720 (base + 4) as sys::CUdeviceptr,
10721 ticket,
10722 0,
10723 )
10724 };
10725 if r != sys::CUresult::CUDA_SUCCESS {
10726 return Err(format!("fence write root: {r:?}").into());
10727 }
10728 }
10729 }
10730 pre_join()?;
10733
10734 if moe_direct_on() && self.ranks.len() == 2 {
10735 let _main = e.gpu.enter_main()?;
10742 if memops {
10743 use cudarc::driver::sys;
10744 let base = workspace.fence_flags_raw;
10745 let r = unsafe {
10746 sys::cuStreamWaitValue32_v2(
10747 e.stream().cu_stream() as sys::CUstream,
10748 (base + 4) as sys::CUdeviceptr,
10749 ticket,
10750 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
10751 )
10752 };
10753 if r != sys::CUresult::CUDA_SUCCESS {
10754 return Err(format!("fence wait: {r:?}").into());
10755 }
10756 if fence_rank1_on() {
10757 let r = unsafe {
10759 sys::cuStreamWaitValue32_v2(
10760 e.stream().cu_stream() as sys::CUstream,
10761 base as sys::CUdeviceptr,
10762 ticket,
10763 sys::CUstreamWaitValue_flags::CU_STREAM_WAIT_VALUE_GEQ as u32,
10764 )
10765 };
10766 if r != sys::CUresult::CUDA_SUCCESS {
10767 return Err(format!("fence wait rank1: {r:?}").into());
10768 }
10769 } else {
10770 for ev in workspace.ev_rank.iter().skip(1) {
10771 e.stream().wait(ev)?;
10772 }
10773 }
10774 } else {
10775 {
10776 let root = &self.ranks[0];
10777 let _rmain = root.gpu.enter_main()?;
10778 workspace
10779 .ev_done
10780 .as_ref()
10781 .expect("device routes done event")
10782 .record(&root.stream())?;
10783 }
10784 e.stream().wait(
10785 workspace
10786 .ev_done
10787 .as_ref()
10788 .expect("device routes done event"),
10789 )?;
10790 for ev in workspace.ev_rank.iter().skip(1) {
10791 e.stream().wait(ev)?;
10792 }
10793 }
10794 let mut output = e.uninit(experts.input_width)?;
10795 if let Some((sh_raw, scale_raw)) = post_add {
10796 e.add3_raw(
10799 &workspace.accumulator[0],
10800 &workspace.accumulator[1],
10801 sh_raw,
10802 scale_raw,
10803 &mut output,
10804 experts.input_width,
10805 )?;
10806 } else {
10807 e.add(
10808 &workspace.accumulator[0],
10809 &workspace.accumulator[1],
10810 &mut output,
10811 experts.input_width,
10812 )?;
10813 }
10814 let output = output;
10815 if let Some(started) = started {
10816 use std::sync::atomic::Ordering;
10817 let ns = TIMING_NS
10818 .fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10819 + started.elapsed().as_nanos() as u64;
10820 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10821 if calls % 430 == 0 {
10822 eprintln!(
10823 "[nvfp4-dev-routes-direct-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10824 ns as f64 / 1.0e6,
10825 ns as f64 / calls as f64 / 1.0e3,
10826 );
10827 }
10828 }
10829 return Ok(output);
10830 }
10831 {
10832 let root = &self.ranks[0];
10833 let _main = root.gpu.enter_main()?;
10834 for ev in workspace.ev_rank.iter().skip(1) {
10835 root.stream().wait(ev)?;
10836 }
10837 root.stream()
10838 .memcpy_dtod(&workspace.accumulator[1], &mut workspace.remote)?;
10839 {
10840 let Nvfp4DeviceRoutesWorkspace {
10841 accumulator,
10842 remote,
10843 combined,
10844 ..
10845 } = &mut *workspace;
10846 root.add(&accumulator[0], remote, combined, experts.input_width)?;
10847 }
10848 workspace
10849 .ev_done
10850 .as_ref()
10851 .expect("device routes done event")
10852 .record(&root.stream())?;
10853 }
10854 let output = {
10855 let _main = e.gpu.enter_main()?;
10856 e.stream().wait(
10857 workspace
10858 .ev_done
10859 .as_ref()
10860 .expect("device routes done event"),
10861 )?;
10862 let mut output = e.uninit(experts.input_width)?;
10865 e.stream().memcpy_dtod(
10866 &workspace.combined.slice(0..experts.input_width),
10867 &mut output.slice_mut(0..experts.input_width),
10868 )?;
10869 output
10870 };
10871 if let Some(started) = started {
10872 use std::sync::atomic::Ordering;
10873 let ns = TIMING_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
10874 + started.elapsed().as_nanos() as u64;
10875 let calls = TIMING_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
10876 if calls % 430 == 0 {
10877 eprintln!(
10878 "[nvfp4-dev-routed-timing] calls={calls} total_ms={:.1} avg_us={:.1}",
10879 ns as f64 / 1.0e6,
10880 ns as f64 / calls as f64 / 1.0e3,
10881 );
10882 }
10883 }
10884 Ok(output)
10885 }
10886
10887 pub(crate) fn decode_v2_finish_root_fused(
10891 &self,
10892 ws: &mut StepTpDecodeV2Ws,
10893 ) -> Result<(), Box<dyn std::error::Error>> {
10894 let root = &self.ranks[0];
10895 let _main = root.gpu.enter_main()?;
10896 if ws.raw_peer_partial != 0 {
10897 raw_copy_bytes(ws.raw_peer_partial, ws.raw_o_partial1, ws.o_out * 4, root)?;
10899 } else {
10900 root.stream()
10901 .memcpy_dtod(&ws.o_partials[1][0], &mut ws.peer_partial)?;
10902 }
10903 {
10904 let StepTpDecodeV2Ws {
10905 o_partials,
10906 peer_partial,
10907 reduce_a,
10908 o_out,
10909 ..
10910 } = &mut *ws;
10911 root.add(&o_partials[0][0], peer_partial, reduce_a, *o_out)?;
10912 }
10913 let shadows = !no_local_shadow_on() || ws.raw_mixed_stage_e != 0;
10914 if shadows {
10915 let mut k_dst = ws.k_shadow.slice_mut(0..ws.local_kv_dim);
10918 root.stream().memcpy_dtod(&ws.k[0], &mut k_dst)?;
10919 let mut v_dst = ws.v_shadow.slice_mut(0..ws.local_kv_dim);
10920 root.stream().memcpy_dtod(&ws.v_raw[0], &mut v_dst)?;
10921 }
10922 if shadows && ws.raw_peer_partial != 0 {
10923 raw_copy_bytes(
10924 ws.raw_k_shadow + (ws.local_kv_dim * 4) as u64,
10925 ws.raw_k1,
10926 ws.local_kv_dim * 4,
10927 root,
10928 )?;
10929 raw_copy_bytes(
10930 ws.raw_v_shadow + (ws.local_kv_dim * 4) as u64,
10931 ws.raw_v1,
10932 ws.local_kv_dim * 4,
10933 root,
10934 )?;
10935 } else if shadows {
10936 let start = ws.local_kv_dim;
10937 let mut k_dst = ws.k_shadow.slice_mut(start..start + ws.local_kv_dim);
10938 root.stream().memcpy_dtod(&ws.k[1], &mut k_dst)?;
10939 let mut v_dst = ws.v_shadow.slice_mut(start..start + ws.local_kv_dim);
10940 root.stream().memcpy_dtod(&ws.v_raw[1], &mut v_dst)?;
10941 }
10942 if ws.raw_mixed_stage_e != 0 {
10943 raw_copy_bytes(ws.raw_mixed_stage_e, ws.raw_reduce_a, ws.o_out * 4, root)?;
10946 let (k_stage, v_stage) = ws.raw_shadow_stage_e;
10947 raw_copy_bytes(k_stage, ws.raw_k_shadow, 2 * ws.local_kv_dim * 4, root)?;
10948 raw_copy_bytes(v_stage, ws.raw_v_shadow, 2 * ws.local_kv_dim * 4, root)?;
10949 }
10950 Ok(())
10951 }
10952
10953 pub(crate) fn decode_v2_arm_token_mirrors(
10956 &self,
10957 ws: &mut StepTpDecodeV2Ws,
10958 mixed_stage_e: u64,
10959 shadow_stage_e: (u64, u64),
10960 ) -> Result<(), Box<dyn std::error::Error>> {
10961 use cudarc::driver::DevicePtr;
10962 let root = &self.ranks[0];
10963 let _main = root.gpu.enter_main()?;
10964 let stream = root.stream();
10965 let (a, _g) = ws.reduce_a.device_ptr(&stream);
10966 ws.raw_reduce_a = a as u64;
10967 ws.raw_mixed_stage_e = mixed_stage_e;
10968 ws.raw_shadow_stage_e = shadow_stage_e;
10969 Ok(())
10970 }
10971
10972 fn nvfp4_routes_build_graph(
10978 &self,
10979 experts: &ResidentNvfp4TensorParallel,
10980 workspace: &mut Nvfp4DeviceRoutesWorkspace,
10981 local_out: usize,
10982 n_sel: usize,
10983 activation_limit: Option<f32>,
10984 ) -> Result<RoutesGraph, Box<dyn std::error::Error>> {
10985 use cudarc::driver::DevicePtr;
10986 use cudarc::driver::sys;
10987 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
10988 if r == sys::CUresult::CUDA_SUCCESS {
10989 Ok(())
10990 } else {
10991 Err(format!("{what}: {r:?}").into())
10992 }
10993 }
10994 let world = self.ranks.len();
10995 if world != 2 {
10996 return Err("routes graph door is built for the TP2 pair".into());
10997 }
10998 let width = experts.input_width;
10999
11000 let ptr_f32 = |buf: &crate::CudaSlice<f32>, engine: &Engine| -> u64 {
11002 let stream = engine.stream();
11003 let (ptr, _g) = buf.device_ptr(&stream);
11004 ptr as u64
11005 };
11006 let ptr_i32 = |buf: &crate::CudaSlice<i32>, engine: &Engine| -> u64 {
11007 let stream = engine.stream();
11008 let (ptr, _g) = buf.device_ptr(&stream);
11009 ptr as u64
11010 };
11011 let (sel_e, w_e) = workspace
11012 .dev_route_e
11013 .as_ref()
11014 .expect("device route staging set before graph build");
11015 let root_engine = &self.ranks[0];
11016 let p_in_stage = ptr_f32(
11017 workspace.in_stage_e.as_ref().expect("graph staging"),
11018 root_engine,
11019 );
11020 let p_out_stage = ptr_f32(
11021 workspace.out_stage_e.as_ref().expect("graph staging"),
11022 root_engine,
11023 );
11024 let p_sel_e = ptr_i32(sel_e, root_engine);
11025 let p_w_e = ptr_f32(w_e, root_engine);
11026 let p_input: Vec<u64> = (0..world)
11027 .map(|r| ptr_f32(&workspace.input[r], &self.ranks[r]))
11028 .collect();
11029 let p_sel: Vec<u64> = (0..world)
11030 .map(|r| ptr_i32(&workspace.sel[r], &self.ranks[r]))
11031 .collect();
11032 let p_route_w: Vec<u64> = (0..world)
11033 .map(|r| ptr_f32(&workspace.route_w[r], &self.ranks[r]))
11034 .collect();
11035 let p_acc1 = ptr_f32(&workspace.accumulator[1], &self.ranks[1]);
11036 let p_remote = ptr_f32(&workspace.remote, root_engine);
11037 let p_combined = ptr_f32(&workspace.combined, root_engine);
11038
11039 let raw_copy = |dst: u64,
11040 src: u64,
11041 bytes: usize,
11042 engine: &Engine|
11043 -> Result<(), Box<dyn std::error::Error>> {
11044 unsafe {
11045 cu_try(
11046 sys::cuMemcpyAsync(
11047 dst as sys::CUdeviceptr,
11048 src as sys::CUdeviceptr,
11049 bytes,
11050 engine.stream().cu_stream() as sys::CUstream,
11051 ),
11052 "routes graph cuMemcpyAsync",
11053 )
11054 }
11055 };
11056
11057 let mut children = Vec::with_capacity(3);
11058 for rank in 0..world {
11059 let engine = &self.ranks[rank];
11060 let _main = engine.gpu.enter_main()?;
11061 let (child, _retained) = engine.capture_graph_retained(|_| {
11062 raw_copy(p_input[rank], p_in_stage, width * 4, engine)?;
11063 raw_copy(p_sel[rank], p_sel_e, n_sel * 4, engine)?;
11064 raw_copy(p_route_w[rank], p_w_e, n_sel * 4, engine)?;
11065 {
11066 let Nvfp4DeviceRoutesWorkspace {
11067 input, in_q, in_d, ..
11068 } = &mut *workspace;
11069 engine.quantize_q8_1_into(
11070 &input[rank],
11071 1,
11072 width,
11073 &mut in_q[rank],
11074 &mut in_d[rank],
11075 )?;
11076 }
11077 self.nvfp4_routes_batched_sweeps_rank(
11078 experts,
11079 workspace,
11080 &[],
11081 &[],
11082 &[],
11083 local_out,
11084 n_sel,
11085 activation_limit,
11086 true,
11087 rank,
11088 )?;
11089 Ok(())
11090 })?;
11091 children.push(child);
11092 }
11093 {
11094 let root = &self.ranks[0];
11095 let _main = root.gpu.enter_main()?;
11096 let (child, _retained) = root.capture_graph_retained(|_| {
11097 raw_copy(p_remote, p_acc1, width * 4, root)?;
11098 {
11099 let Nvfp4DeviceRoutesWorkspace {
11100 accumulator,
11101 remote,
11102 combined,
11103 ..
11104 } = &mut *workspace;
11105 root.add(&accumulator[0], remote, combined, width)?;
11106 }
11107 raw_copy(p_out_stage, p_combined, width * 4, root)?;
11108 Ok(())
11109 })?;
11110 children.push(child);
11111 }
11112
11113 let mut parent: sys::CUgraph = std::ptr::null_mut();
11114 unsafe {
11115 cu_try(sys::cuGraphCreate(&mut parent, 0), "routes cuGraphCreate")?;
11116 }
11117 let mut n0: sys::CUgraphNode = std::ptr::null_mut();
11118 let mut n1: sys::CUgraphNode = std::ptr::null_mut();
11119 let mut n2: sys::CUgraphNode = std::ptr::null_mut();
11120 unsafe {
11121 cu_try(
11122 sys::cuGraphAddChildGraphNode(
11123 &mut n0,
11124 parent,
11125 std::ptr::null(),
11126 0,
11127 children[0].cu_graph(),
11128 ),
11129 "routes child r0",
11130 )?;
11131 cu_try(
11132 sys::cuGraphAddChildGraphNode(
11133 &mut n1,
11134 parent,
11135 std::ptr::null(),
11136 0,
11137 children[1].cu_graph(),
11138 ),
11139 "routes child r1",
11140 )?;
11141 let deps = [n0, n1];
11142 cu_try(
11143 sys::cuGraphAddChildGraphNode(
11144 &mut n2,
11145 parent,
11146 deps.as_ptr(),
11147 2,
11148 children[2].cu_graph(),
11149 ),
11150 "routes child root",
11151 )?;
11152 }
11153 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
11154 unsafe {
11155 cu_try(
11156 sys::cuGraphInstantiateWithFlags(&mut exec, parent, 0),
11157 "routes instantiate",
11158 )?;
11159 }
11160 Ok(RoutesGraph {
11161 exec,
11162 parent,
11163 _children: children,
11164 })
11165 }
11166
11167 #[allow(clippy::too_many_arguments)]
11171 pub(crate) fn routes_rank_section(
11172 &self,
11173 experts: &ResidentNvfp4TensorParallel,
11174 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11175 raw_input_src: u64,
11176 local_out: usize,
11177 n_sel: usize,
11178 activation_limit: Option<f32>,
11179 rank_index: usize,
11180 ) -> Result<(), Box<dyn std::error::Error>> {
11181 let engine = &self.ranks[rank_index];
11182 {
11183 let _main = engine.gpu.enter_main()?;
11184 let (sel_e_ptr, w_e_ptr) = workspace
11186 .raw_dev_route_e
11187 .ok_or("routes rank section requires armed staging pointers")?;
11188 raw_copy_bytes(
11189 workspace.raw_input[rank_index],
11190 raw_input_src,
11191 experts.input_width * 4,
11192 engine,
11193 )?;
11194 raw_copy_bytes(workspace.raw_sel[rank_index], sel_e_ptr, n_sel * 4, engine)?;
11195 raw_copy_bytes(
11196 workspace.raw_route_w[rank_index],
11197 w_e_ptr,
11198 n_sel * 4,
11199 engine,
11200 )?;
11201 {
11202 let Nvfp4DeviceRoutesWorkspace {
11203 input, in_q, in_d, ..
11204 } = &mut *workspace;
11205 engine.quantize_q8_1_into(
11206 &input[rank_index],
11207 1,
11208 experts.input_width,
11209 &mut in_q[rank_index],
11210 &mut in_d[rank_index],
11211 )?;
11212 }
11213 }
11214 self.nvfp4_routes_batched_sweeps_rank(
11215 experts,
11216 workspace,
11217 &[],
11218 &[],
11219 &[],
11220 local_out,
11221 n_sel,
11222 activation_limit,
11223 true,
11224 rank_index,
11225 )
11226 }
11227
11228 pub(crate) fn routes_root_section(
11231 &self,
11232 experts: &ResidentNvfp4TensorParallel,
11233 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11234 ) -> Result<(), Box<dyn std::error::Error>> {
11235 let root = &self.ranks[0];
11236 let _main = root.gpu.enter_main()?;
11237 let (acc1_ptr, remote_ptr, combined_ptr, out_stage_ptr) = workspace
11238 .raw_combine
11239 .ok_or("routes root section requires armed combine pointers")?;
11240 raw_copy_bytes(remote_ptr, acc1_ptr, experts.input_width * 4, root)?;
11241 {
11242 let Nvfp4DeviceRoutesWorkspace {
11243 accumulator,
11244 remote,
11245 combined,
11246 ..
11247 } = &mut *workspace;
11248 root.add(&accumulator[0], remote, combined, experts.input_width)?;
11249 }
11250 raw_copy_bytes(out_stage_ptr, combined_ptr, experts.input_width * 4, root)?;
11251 Ok(())
11252 }
11253
11254 pub(crate) fn routes_arm_raw(
11257 &self,
11258 experts: &ResidentNvfp4TensorParallel,
11259 workspace: &mut Nvfp4DeviceRoutesWorkspace,
11260 ) -> Result<(), Box<dyn std::error::Error>> {
11261 use cudarc::driver::DevicePtr;
11262 if workspace.raw_dev_route_e.is_some() {
11263 return Ok(());
11264 }
11265 let _ = experts;
11266 let (sel_e, w_e) = workspace
11267 .dev_route_e
11268 .as_ref()
11269 .ok_or("routes staging not armed")?;
11270 let root = &self.ranks[0];
11271 {
11272 let _main = root.gpu.enter_main()?;
11273 let stream = root.stream();
11274 let (a, _g) = sel_e.device_ptr(&stream);
11275 let (b, _g) = w_e.device_ptr(&stream);
11276 workspace.raw_dev_route_e = Some((a as u64, b as u64));
11277 let (c, _g) = workspace.accumulator[1].device_ptr(&stream);
11278 let (d, _g) = workspace.remote.device_ptr(&stream);
11279 let (f, _g) = workspace.combined.device_ptr(&stream);
11280 let out_stage = workspace
11281 .out_stage_e
11282 .as_ref()
11283 .ok_or("routes out stage not armed")?;
11284 let (g_, _g) = out_stage.device_ptr(&stream);
11285 workspace.raw_combine = Some((c as u64, d as u64, f as u64, g_ as u64));
11286 }
11287 for rank in 0..self.ranks.len() {
11288 let engine = &self.ranks[rank];
11289 let _main = engine.gpu.enter_main()?;
11290 let stream = engine.stream();
11291 let (a, _g) = workspace.input[rank].device_ptr(&stream);
11292 let (b, _g) = workspace.sel[rank].device_ptr(&stream);
11293 let (c, _g) = workspace.route_w[rank].device_ptr(&stream);
11294 workspace.raw_input.push(a as u64);
11295 workspace.raw_sel.push(b as u64);
11296 workspace.raw_route_w.push(c as u64);
11297 }
11298 Ok(())
11299 }
11300
11301 pub fn run_tensor_parallel_routes_nvfp4(
11305 &self,
11306 experts: &ResidentNvfp4TensorParallel,
11307 input: &[f32],
11308 tokens: usize,
11309 selected: &[usize],
11310 route_weights: &[f32],
11311 experts_per_token: usize,
11312 activation_limit: Option<f32>,
11313 ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
11314 validate_activations(input, tokens, experts.input_width)?;
11315 let pairs = tokens
11316 .checked_mul(experts_per_token)
11317 .ok_or("NVFP4 TP route count overflow")?;
11318 if selected.len() != pairs || route_weights.len() != pairs {
11319 return Err(format!(
11320 "NVFP4 TP routes selected={} weights={} != tokens {tokens} x experts/token \
11321 {experts_per_token} ({pairs})",
11322 selected.len(),
11323 route_weights.len(),
11324 )
11325 .into());
11326 }
11327 if !route_weights.iter().all(|weight| weight.is_finite()) {
11328 return Err("NVFP4 TP route weights contain a non-finite value".into());
11329 }
11330
11331 let mut output = vec![0.0f32; tokens * experts.input_width];
11332 for token in 0..tokens {
11333 let input_row = &input[token * experts.input_width..(token + 1) * experts.input_width];
11334 for slot in 0..experts_per_token {
11335 let pair = token * experts_per_token + slot;
11336 let expert = selected[pair];
11337 if expert >= experts.expert_count {
11338 return Err(format!(
11339 "NVFP4 TP selected expert {expert} outside 0..{}",
11340 experts.expert_count
11341 )
11342 .into());
11343 }
11344 let gate = self.run_column_bank_expert_nvfp4(
11345 &experts.gate,
11346 &experts.macros_gate,
11347 expert,
11348 input_row,
11349 )?;
11350 let up = self.run_column_bank_expert_nvfp4(
11351 &experts.up,
11352 &experts.macros_up,
11353 expert,
11354 input_row,
11355 )?;
11356 let activated: Vec<f32> = gate
11357 .iter()
11358 .zip(&up)
11359 .map(|(&gate, &up)| step_expert_activation_host(gate, up, activation_limit))
11360 .collect();
11361 debug_assert_eq!(activated.len(), experts.expert_width);
11362 let down = self.run_row_bank_expert_nvfp4(
11363 &experts.down,
11364 &experts.macros_down,
11365 expert,
11366 &activated,
11367 )?;
11368 let weight = route_weights[pair];
11369 for (sum, value) in output
11370 [token * experts.input_width..(token + 1) * experts.input_width]
11371 .iter_mut()
11372 .zip(down)
11373 {
11374 *sum += weight * value;
11375 }
11376 }
11377 }
11378 Ok(output)
11379 }
11380}
11381
11382#[cfg(test)]
11383mod tests {
11384 use super::*;
11385
11386 #[test]
11387 fn step_expert_activation_clamps_each_arm_by_the_official_contract() {
11388 let limit = Some(7.0);
11389 assert_eq!(step_expert_activation_host(20.0, 9.0, limit), 49.0);
11390 assert_eq!(step_expert_activation_host(20.0, -9.0, limit), -49.0);
11391 assert!(
11392 step_expert_activation_host(-20.0, 9.0, limit).abs()
11393 < step_expert_activation_host(-20.0, 9.0, None).abs()
11394 );
11395 assert!(validate_step_expert_activation_limit(Some(f32::NAN)).is_err());
11396 assert!(validate_step_expert_activation_limit(Some(0.0)).is_err());
11397 assert!(validate_step_expert_activation_limit(limit).is_ok());
11398 }
11399
11400 #[test]
11401 fn moe_residual_host_preserves_official_add_order() {
11402 let output = moe_residual_host(&[1.0e20], &[-1.0e20], &[1.0]).unwrap();
11403 assert_eq!(output, [0.0]);
11404 assert_eq!(
11405 moe_residual_host(&[0.0], &[0.0, 1.0], &[0.0]).unwrap_err(),
11406 "MoE residual lengths residual=1 routed=2 shared=1"
11407 );
11408 }
11409
11410 #[test]
11411 fn expert_owner_routes_preserve_global_pair_order_with_local_expert_ids() {
11412 let selected = [0, 36, 72, 108, 144, 180, 216, 252];
11413 let owners = partition_expert_owner_routes(288, 4, 1, 8, &selected).unwrap();
11414 assert_eq!(owners.len(), 4);
11415 for (rank, owner) in owners.iter().enumerate() {
11416 assert_eq!(owner.rank, rank);
11417 assert_eq!(owner.selected, vec![0, 36]);
11418 assert_eq!(owner.token_rows, vec![0, 0]);
11419 assert_eq!(owner.global_pairs, vec![rank * 2, rank * 2 + 1]);
11420 }
11421 }
11422
11423 #[test]
11424 fn expert_owner_routes_validate_geometry_and_selected_experts() {
11425 assert!(partition_expert_owner_routes(288, 5, 1, 8, &[0; 8]).is_err());
11426 assert!(partition_expert_owner_routes(288, 4, 2, 8, &[0; 8]).is_err());
11427 let error = partition_expert_owner_routes(288, 4, 1, 8, &[288; 8]).unwrap_err();
11428 assert!(error.contains("outside 0..288"));
11429 }
11430
11431 #[test]
11432 fn step_grouped_owner_routes_validate_dynamic_top8_shapes() {
11433 let selected = [
11434 1, 73, 80, 145, 152, 159, 217, 224, 12, 84, 91, 156, 163, 170, 228, 235,
11435 ];
11436 assert_eq!(
11437 validate_step_grouped_owner_routes(288, 2, &selected).unwrap(),
11438 16
11439 );
11440 let owners = partition_expert_owner_routes(288, 4, 2, 8, &selected).unwrap();
11441 assert_eq!(
11442 owners
11443 .iter()
11444 .map(|owner| owner.selected.len())
11445 .collect::<Vec<_>>(),
11446 vec![2, 4, 6, 4]
11447 );
11448 assert!(validate_step_grouped_owner_routes(288, 2, &selected[..8]).is_err());
11449 assert!(validate_step_grouped_owner_routes(288, 1, &[0; 8]).is_err());
11450 assert!(validate_step_grouped_owner_routes(287, 2, &selected).is_err());
11451 }
11452
11453 #[test]
11454 fn weighted_route_combine_requires_a_canonical_pair_permutation() {
11455 let owner0 = [0usize, 3];
11456 let owner1 = [1usize, 2];
11457 let owners = [owner0.as_slice(), owner1.as_slice()];
11458 assert_eq!(
11459 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, 0.2, 0.3, 0.4],)
11460 .unwrap(),
11461 WeightedRouteCombineShape {
11462 pairs: 4,
11463 max_pairs: 12,
11464 }
11465 );
11466 let duplicate = [owner0.as_slice(), &[1usize, 1][..]];
11467 assert!(
11468 validate_weighted_route_combine(4096, 4, 3, 1, &duplicate, &[0.1, 0.2, 0.3, 0.4],)
11469 .is_err()
11470 );
11471 assert!(
11472 validate_weighted_route_combine(4096, 4, 3, 1, &owners, &[0.1, f32::NAN, 0.3, 0.4],)
11473 .is_err()
11474 );
11475 assert!(
11476 validate_weighted_route_combine(4096, 4, 1, 2, &owners, &[0.1, 0.2, 0.3, 0.4],)
11477 .is_err()
11478 );
11479 }
11480
11481 #[test]
11482 fn native_p2p_door_is_strict_and_default_off() {
11483 assert!(!parse_step_tp_native_p2p(None).unwrap());
11484 assert!(!parse_step_tp_native_p2p(Some("")).unwrap());
11485 assert!(!parse_step_tp_native_p2p(Some("0")).unwrap());
11486 assert!(parse_step_tp_native_p2p(Some("1")).unwrap());
11487 assert!(parse_step_tp_native_p2p(Some("true")).is_err());
11488 assert!(parse_step_tp_native_p2p(Some("2")).is_err());
11489 }
11490
11491 #[test]
11492 fn bulk_p2p_door_is_strict_and_default_off() {
11493 assert!(!parse_step_tp_bulk_p2p(None).unwrap());
11494 assert!(!parse_step_tp_bulk_p2p(Some("")).unwrap());
11495 assert!(!parse_step_tp_bulk_p2p(Some("0")).unwrap());
11496 assert!(parse_step_tp_bulk_p2p(Some("1")).unwrap());
11497 assert!(parse_step_tp_bulk_p2p(Some("true")).is_err());
11498 assert!(parse_step_tp_bulk_p2p(Some("2")).is_err());
11499 }
11500
11501 #[test]
11502 fn ep_device_arithmetic_door_is_strict_and_default_off() {
11503 assert!(!parse_step_ep_device_arithmetic(None).unwrap());
11504 assert!(!parse_step_ep_device_arithmetic(Some("")).unwrap());
11505 assert!(!parse_step_ep_device_arithmetic(Some("0")).unwrap());
11506 assert!(parse_step_ep_device_arithmetic(Some("1")).unwrap());
11507 assert!(parse_step_ep_device_arithmetic(Some("true")).is_err());
11508 assert!(parse_step_ep_device_arithmetic(Some("2")).is_err());
11509 }
11510
11511 #[test]
11512 fn f32_mirror_door_is_strict_and_default_off() {
11513 assert!(!parse_step_tp_f32_mirror(None).unwrap());
11514 assert!(!parse_step_tp_f32_mirror(Some("")).unwrap());
11515 assert!(!parse_step_tp_f32_mirror(Some("0")).unwrap());
11516 assert!(parse_step_tp_f32_mirror(Some("1")).unwrap());
11517 assert!(parse_step_tp_f32_mirror(Some("true")).is_err());
11518 assert!(parse_step_tp_f32_mirror(Some("2")).is_err());
11519 }
11520
11521 fn matrix(out_features: usize, in_features: usize) -> (Vec<u8>, Vec<f32>) {
11522 let codes = (0..out_features * in_features)
11523 .map(|index| (index % 251) as u8)
11524 .collect();
11525 let scales = (0..out_features.div_ceil(FP8_BLOCK) * in_features.div_ceil(FP8_BLOCK))
11526 .map(|index| index as f32 + 1.0)
11527 .collect();
11528 (codes, scales)
11529 }
11530
11531 fn bf16_matrix_bytes(out_features: usize, in_features: usize) -> Vec<u8> {
11532 (0..out_features * in_features)
11533 .flat_map(|value| (value as u16).to_le_bytes())
11534 .collect()
11535 }
11536
11537 fn decode_u16(bytes: &[u8]) -> Vec<u16> {
11538 bytes
11539 .chunks_exact(2)
11540 .map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
11541 .collect()
11542 }
11543
11544 #[test]
11545 fn bf16_matrix_rejects_wrong_byte_count() {
11546 let bytes = vec![0u8; 4 * 4 * 2 - 1];
11547 let matrix = Bf16Matrix {
11548 bytes: &bytes,
11549 out_features: 4,
11550 in_features: 4,
11551 };
11552 assert!(matrix.validate().unwrap_err().contains("4x4x2"));
11553 }
11554
11555 #[test]
11556 fn replicated_device_rows_require_exact_rank_local_shapes() {
11557 assert_eq!(
11558 replicated_device_row_values(3, 4096, 4, &[12_288; 4]).unwrap(),
11559 12_288
11560 );
11561 assert!(replicated_device_row_values(0, 4096, 4, &[0; 4]).is_err());
11562 assert!(replicated_device_row_values(3, 0, 4, &[0; 4]).is_err());
11563 assert!(replicated_device_row_values(3, 4096, 4, &[12_288; 3]).is_err());
11564 assert!(
11565 replicated_device_row_values(3, 4096, 4, &[12_288, 12_288, 12_287, 12_288]).is_err()
11566 );
11567 assert!(replicated_device_row_values(usize::MAX, 2, 1, &[0]).is_err());
11568 }
11569
11570 #[test]
11571 fn replicated_device_row_refresh_requires_exact_root_source() {
11572 assert_eq!(
11573 replicated_device_row_source_values(1, 12_288, 12_288, 3, 3).unwrap(),
11574 12_288
11575 );
11576 assert!(replicated_device_row_source_values(0, 12_288, 0, 3, 3).is_err());
11577 assert!(replicated_device_row_source_values(1, 0, 0, 3, 3).is_err());
11578 assert!(replicated_device_row_source_values(1, 12_288, 12_287, 3, 3).is_err());
11579 assert!(replicated_device_row_source_values(1, 12_288, 12_288, 2, 3).is_err());
11580 assert!(replicated_device_row_source_values(usize::MAX, 2, 0, 3, 3).is_err());
11581 }
11582
11583 #[test]
11584 fn step_bf16_canonical_rows_are_topology_invariant_through_tp8() {
11585 for tp in [1, 2, 4, 8] {
11586 assert_eq!(step_bf16_canonical_chunk_rows(8_192, tp).unwrap(), 1_024);
11587 assert_eq!(step_bf16_canonical_chunk_rows(12_288, tp).unwrap(), 1_536);
11588 assert_eq!(step_bf16_canonical_chunk_rows(1_024, tp).unwrap(), 128);
11589 assert_eq!(step_bf16_canonical_chunk_cols(8_192, tp).unwrap(), 1_024);
11590 assert_eq!(step_bf16_canonical_chunk_cols(12_288, tp).unwrap(), 1_536);
11591 }
11592 assert!(step_bf16_canonical_chunk_rows(12_288, 3).is_err());
11593 assert!(step_bf16_canonical_chunk_rows(1_001, 2).is_err());
11594 assert!(step_bf16_canonical_chunk_cols(12_288, 3).is_err());
11595 assert!(step_bf16_canonical_chunk_cols(1_001, 2).is_err());
11596 }
11597
11598 #[test]
11599 fn cache_rows_split_by_token_then_rank() {
11600 let rows = (0u8..24).collect::<Vec<_>>();
11601 assert_eq!(
11602 cache_rank_rows(&rows, 3, 4, 2, 0).unwrap(),
11603 vec![0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19]
11604 );
11605 assert_eq!(
11606 cache_rank_rows(&rows, 3, 4, 2, 1).unwrap(),
11607 vec![4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23]
11608 );
11609 assert!(cache_rank_rows(&rows[..23], 3, 4, 2, 0).is_err());
11610 assert!(cache_rank_rows(&rows, 3, 4, 2, 2).is_err());
11611 }
11612
11613 #[test]
11614 fn bf16_column_shard_preserves_contiguous_output_rows() {
11615 let bytes = bf16_matrix_bytes(4, 4);
11616 let matrix = Bf16Matrix {
11617 bytes: &bytes,
11618 out_features: 4,
11619 in_features: 4,
11620 };
11621 let shard = bf16_column_shard(matrix, 2, 1).unwrap();
11622 assert_eq!(shard.out_features, 2);
11623 assert_eq!(shard.in_features, 4);
11624 assert_eq!(decode_u16(shard.bytes), (8..16).collect::<Vec<_>>());
11625 }
11626
11627 #[test]
11628 fn bf16_row_shard_preserves_each_input_column_window() {
11629 let bytes = bf16_matrix_bytes(3, 4);
11630 let matrix = Bf16Matrix {
11631 bytes: &bytes,
11632 out_features: 3,
11633 in_features: 4,
11634 };
11635 let shard = bf16_row_shard(matrix, 2, 1).unwrap();
11636 assert_eq!(decode_u16(&shard), vec![2, 3, 6, 7, 10, 11]);
11637 }
11638
11639 #[test]
11640 fn bf16_row_block_preserves_global_column_order() {
11641 let bytes = bf16_matrix_bytes(3, 8);
11642 let matrix = Bf16Matrix {
11643 bytes: &bytes,
11644 out_features: 3,
11645 in_features: 8,
11646 };
11647 let block = bf16_row_block(matrix, 2, 3).unwrap();
11648 assert_eq!(decode_u16(&block), vec![2, 3, 4, 10, 11, 12, 18, 19, 20]);
11649 }
11650
11651 #[test]
11652 fn column_shard_preserves_contiguous_weight_and_scale_rows() {
11653 let (codes, scales) = matrix(1280, 4096);
11654 let matrix = E4m3BlockMatrix {
11655 codes: &codes,
11656 scales: &scales,
11657 out_features: 1280,
11658 in_features: 4096,
11659 };
11660 let shard = column_shard(matrix, 2, 1).unwrap();
11661 assert_eq!(shard.out_features, 640);
11662 assert_eq!(shard.codes, &codes[640 * 4096..]);
11663 assert_eq!(shard.scales, &scales[5 * 32..]);
11664 }
11665
11666 #[test]
11667 fn row_shard_preserves_each_weight_and_scale_column_window() {
11668 let (codes, scales) = matrix(4096, 1280);
11669 let matrix = E4m3BlockMatrix {
11670 codes: &codes,
11671 scales: &scales,
11672 out_features: 4096,
11673 in_features: 1280,
11674 };
11675 let (shard_codes, shard_scales) = row_shard(matrix, 2, 1).unwrap();
11676 assert_eq!(shard_codes.len(), 4096 * 640);
11677 assert_eq!(&shard_codes[..640], &codes[640..1280]);
11678 assert_eq!(&shard_codes[640..1280], &codes[1280 + 640..2560]);
11679 assert_eq!(shard_scales.len(), 32 * 5);
11680 assert_eq!(&shard_scales[..5], &scales[5..10]);
11681 assert_eq!(&shard_scales[5..10], &scales[15..20]);
11682 }
11683
11684 #[test]
11685 fn activation_shards_keep_token_rows_separate() {
11686 let activations: Vec<f32> = (0..2 * 8).map(|value| value as f32).collect();
11687 assert_eq!(
11688 activation_shard(&activations, 2, 8, 2, 1),
11689 vec![4.0, 5.0, 6.0, 7.0, 12.0, 13.0, 14.0, 15.0],
11690 );
11691 }
11692
11693 #[test]
11694 fn expert_bank_selects_expert_major_code_and_scale_planes() {
11695 let expert_count = 2;
11696 let out_features = 128;
11697 let in_features = 128;
11698 let code_stride = out_features * in_features;
11699 let codes: Vec<u8> = (0..expert_count * code_stride)
11700 .map(|index| (index % 251) as u8)
11701 .collect();
11702 let scales = vec![1.0f32, 2.0];
11703 let bank = E4m3ExpertBank {
11704 codes: &codes,
11705 scales: &scales,
11706 expert_count,
11707 out_features,
11708 in_features,
11709 };
11710 bank.validate().unwrap();
11711 let expert = bank.expert(1).unwrap();
11712 assert_eq!(expert.codes, &codes[code_stride..]);
11713 assert_eq!(expert.scales, &[2.0]);
11714 }
11715
11716 #[test]
11717 fn expert_bank_rejects_non_positive_scale() {
11718 let codes = vec![0u8; 128 * 128];
11719 let scales = vec![0.0f32];
11720 let bank = E4m3ExpertBank {
11721 codes: &codes,
11722 scales: &scales,
11723 expert_count: 1,
11724 out_features: 128,
11725 in_features: 128,
11726 };
11727 assert!(bank.validate().unwrap_err().contains("non-positive"));
11728 }
11729
11730 #[test]
11731 fn tensor_parallel_column_bank_keeps_each_expert_scale_plane_separate() {
11732 let expert_count = 2;
11733 let out_features = 256;
11734 let in_features = 128;
11735 let code_stride = out_features * in_features;
11736 let scale_stride = 2;
11737 let codes = (0..expert_count * code_stride)
11738 .map(|index| (index % 251) as u8)
11739 .collect::<Vec<_>>();
11740 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
11741 let bank = E4m3ExpertBank {
11742 codes: &codes,
11743 scales: &scales,
11744 expert_count,
11745 out_features,
11746 in_features,
11747 };
11748
11749 let rank = pack_column_bank_rank(bank, 2, 1).unwrap();
11750 assert_eq!(rank.out_features, 128);
11751 assert_eq!(rank.in_features, 128);
11752 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
11753 assert_eq!(rank.scales, vec![11.0, 21.0]);
11754 assert_eq!(&rank.codes[..128 * 128], &codes[128 * 128..256 * 128]);
11755 assert_eq!(
11756 &rank.codes[128 * 128..],
11757 &codes[code_stride + 128 * 128..2 * code_stride]
11758 );
11759 assert_eq!(scale_stride, scales.len() / expert_count);
11760 }
11761
11762 #[test]
11763 fn tensor_parallel_row_bank_keeps_each_expert_scale_plane_separate() {
11764 let expert_count = 2;
11765 let out_features = 128;
11766 let in_features = 256;
11767 let code_stride = out_features * in_features;
11768 let codes = (0..expert_count * code_stride)
11769 .map(|index| (index % 251) as u8)
11770 .collect::<Vec<_>>();
11771 let scales = vec![10.0f32, 11.0, 20.0, 21.0];
11772 let bank = E4m3ExpertBank {
11773 codes: &codes,
11774 scales: &scales,
11775 expert_count,
11776 out_features,
11777 in_features,
11778 };
11779
11780 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
11781 assert_eq!(rank.out_features, 128);
11782 assert_eq!(rank.in_features, 128);
11783 assert_eq!(rank.k_blocks, Some(1));
11784 assert_eq!(rank.codes.len(), expert_count * 128 * 128);
11785 assert_eq!(rank.scales, vec![11.0, 21.0]);
11786 assert_eq!(&rank.codes[..128], &codes[128..256]);
11787 assert_eq!(
11788 &rank.codes[128 * 128..128 * 128 + 128],
11789 &codes[code_stride + 128..code_stride + 256]
11790 );
11791 }
11792
11793 #[test]
11794 fn tensor_parallel_row_bank_preserves_global_k_block_order() {
11795 let expert_count = 2;
11796 let out_features = 256;
11797 let in_features = 512;
11798 let code_stride = out_features * in_features;
11799 let mut codes = vec![0u8; expert_count * code_stride];
11800 for expert in 0..expert_count {
11801 for row in 0..out_features {
11802 for block in 0..4 {
11803 let value = (expert * 80 + block * 16 + row % 16) as u8;
11804 let start = expert * code_stride + row * in_features + block * FP8_BLOCK;
11805 codes[start..start + FP8_BLOCK].fill(value);
11806 }
11807 }
11808 }
11809 let scales = vec![
11810 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,
11811 112.0, 113.0, 114.0,
11812 ];
11813 let bank = E4m3ExpertBank {
11814 codes: &codes,
11815 scales: &scales,
11816 expert_count,
11817 out_features,
11818 in_features,
11819 };
11820
11821 let rank = pack_row_bank_rank(bank, 2, 1).unwrap();
11822 assert_eq!(rank.out_features, out_features);
11823 assert_eq!(rank.in_features, 256);
11824 assert_eq!(rank.k_blocks, Some(2));
11825 assert_eq!(rank.code_stride, out_features * 256);
11826 assert_eq!(rank.scale_stride, 4);
11827 assert_eq!(&rank.scales[..4], &[3.0, 13.0, 4.0, 14.0]);
11828 assert_eq!(&rank.scales[4..], &[103.0, 113.0, 104.0, 114.0]);
11829
11830 let block_stride = out_features * FP8_BLOCK;
11831 assert!(rank.codes[..FP8_BLOCK].iter().all(|&code| code == 32));
11832 assert!(
11833 rank.codes[block_stride..block_stride + FP8_BLOCK]
11834 .iter()
11835 .all(|&code| code == 48)
11836 );
11837 assert!(
11838 rank.codes[rank.code_stride..rank.code_stride + FP8_BLOCK]
11839 .iter()
11840 .all(|&code| code == 112)
11841 );
11842 assert!(
11843 rank.codes
11844 [rank.code_stride + block_stride..rank.code_stride + block_stride + FP8_BLOCK]
11845 .iter()
11846 .all(|&code| code == 128)
11847 );
11848 }
11849
11850 #[test]
11851 fn step_ep_layer_specs_are_literal_and_fail_closed() {
11852 assert!(parse_step_ep_layer_specs(None).unwrap().is_empty());
11853 assert!(parse_step_ep_layer_specs(Some("0")).unwrap().is_empty());
11854 assert_eq!(
11855 parse_step_ep_layer_specs(Some("24@1,2")).unwrap(),
11856 vec![StepEpLayerSpec {
11857 layer: 24,
11858 devices: vec![1, 2],
11859 }]
11860 );
11861 assert_eq!(
11862 parse_step_ep_layer_specs(Some("24-25@1,2;31@0,2")).unwrap(),
11863 vec![
11864 StepEpLayerSpec {
11865 layer: 24,
11866 devices: vec![1, 2],
11867 },
11868 StepEpLayerSpec {
11869 layer: 25,
11870 devices: vec![1, 2],
11871 },
11872 StepEpLayerSpec {
11873 layer: 31,
11874 devices: vec![0, 2],
11875 },
11876 ]
11877 );
11878 assert!(parse_step_ep_layer_specs(Some("24@1")).is_err());
11879 assert!(parse_step_ep_layer_specs(Some("24@1,1")).is_err());
11880 assert!(parse_step_ep_layer_specs(Some("layer@1,2")).is_err());
11881 assert!(parse_step_ep_layer_specs(Some("25-24@1,2")).is_err());
11882 assert!(parse_step_ep_layer_specs(Some("0-128@1,2")).is_err());
11883 assert!(parse_step_ep_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
11884 assert!(parse_step_ep_layer_specs(Some("all@0,1")).is_err());
11885 }
11886
11887 #[test]
11888 fn step_tp_layer_specs_share_the_fail_closed_layer_contract() {
11889 assert!(parse_step_tp_layer_specs(None).unwrap().is_empty());
11890 assert!(parse_step_tp_layer_specs(Some("0")).unwrap().is_empty());
11891 assert_eq!(
11892 parse_step_tp_layer_specs(Some("24-25@1,2")).unwrap(),
11893 vec![
11894 StepTpLayerSpec {
11895 layer: 24,
11896 devices: vec![1, 2],
11897 },
11898 StepTpLayerSpec {
11899 layer: 25,
11900 devices: vec![1, 2],
11901 },
11902 ]
11903 );
11904 let error = parse_step_tp_layer_specs(Some("24@1")).unwrap_err();
11905 assert!(error.contains("MEMRA_STEP_TP"));
11906 assert!(parse_step_tp_layer_specs(Some("24@1,1")).is_err());
11907 assert!(parse_step_tp_layer_specs(Some("24-25@1,2;25@0,2")).is_err());
11908
11909 let all = parse_step_tp_layer_specs(Some("all@0,1,2,3,4,5,6,7")).unwrap();
11910 assert_eq!(all.len(), STEP37_TRUNK_LAYERS);
11911 assert_eq!(all.first().unwrap().layer, 0);
11912 assert_eq!(all.last().unwrap().layer, STEP37_TRUNK_LAYERS - 1);
11913 let devices = (0..8).collect::<Vec<_>>();
11914 assert!(all.iter().all(|spec| spec.devices == devices));
11915 assert!(parse_step_tp_layer_specs(Some("all@0,1;44@0,1")).is_err());
11916 }
11917}
11918
11919struct TokenGraphChild {
11931 graph: cudarc::driver::CudaGraph,
11932 node: cudarc::driver::sys::CUgraphNode,
11933 ctx: cudarc::driver::sys::CUcontext,
11934}
11935
11936struct TokenGraphFaSite {
11940 ctx: cudarc::driver::sys::CUcontext,
11941 memset_o: cudarc::driver::sys::CUgraphNode,
11942 memset_m: [cudarc::driver::sys::CUgraphNode; 2],
11943 fa: cudarc::driver::sys::CUgraphNode,
11944 combine: cudarc::driver::sys::CUgraphNode,
11945 window: usize,
11946 n_head: usize,
11947 n_head_kv: usize,
11948 head_dim: usize,
11949}
11950
11951pub struct TokenGraphBuilder {
11952 parent: cudarc::driver::sys::CUgraph,
11953 children: Vec<TokenGraphChild>,
11954 frontier: Vec<cudarc::driver::sys::CUgraphNode>,
11957 pending_detached: Vec<cudarc::driver::sys::CUgraphNode>,
11960 group: Option<(
11963 u32,
11964 Vec<cudarc::driver::sys::CUgraphNode>,
11965 Vec<cudarc::driver::sys::CUgraphNode>,
11966 )>,
11967}
11968
11969unsafe impl Send for TokenGraphBuilder {}
11971
11972impl TokenGraphBuilder {
11973 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
11974 use cudarc::driver::sys;
11975 let mut parent: sys::CUgraph = std::ptr::null_mut();
11976 let r = unsafe { sys::cuGraphCreate(&mut parent, 0) };
11977 if r != sys::CUresult::CUDA_SUCCESS {
11978 return Err(format!("token graph create: {r:?}").into());
11979 }
11980 Ok(Self {
11981 parent,
11982 children: Vec::new(),
11983 frontier: Vec::new(),
11984 pending_detached: Vec::new(),
11985 group: None,
11986 })
11987 }
11988
11989 fn push_child(
11990 &mut self,
11991 graph: cudarc::driver::CudaGraph,
11992 parallel_group: Option<u32>,
11993 detached: bool,
11994 absorb: bool,
11995 ctx: cudarc::driver::sys::CUcontext,
11996 ) -> Result<(), Box<dyn std::error::Error>> {
11997 use cudarc::driver::sys;
11998 let deps: Vec<sys::CUgraphNode> = match (&mut self.group, parallel_group) {
12002 (Some((open, base, _)), Some(group)) if *open == group => base.clone(),
12003 (state, Some(group)) => {
12004 if let Some((_, _, members)) = state.take() {
12006 self.frontier = members;
12007 }
12008 let base = self.frontier.clone();
12009 *state = Some((group, base.clone(), Vec::new()));
12010 base
12011 }
12012 (state, None) if detached => match state.as_ref() {
12013 Some((_, base, _)) => base.clone(),
12014 None => self.frontier.clone(),
12015 },
12016 (state, None) => {
12017 if let Some((_, _, members)) = state.take() {
12018 self.frontier = members;
12019 }
12020 let mut deps = self.frontier.clone();
12021 if absorb {
12022 deps.append(&mut self.pending_detached);
12023 }
12024 deps
12025 }
12026 };
12027 let mut node: sys::CUgraphNode = std::ptr::null_mut();
12028 let r = unsafe {
12029 sys::cuGraphAddChildGraphNode(
12030 &mut node,
12031 self.parent,
12032 if deps.is_empty() {
12033 std::ptr::null()
12034 } else {
12035 deps.as_ptr()
12036 },
12037 deps.len(),
12038 graph.cu_graph(),
12039 )
12040 };
12041 if r != sys::CUresult::CUDA_SUCCESS {
12042 return Err(format!("token graph child: {r:?}").into());
12043 }
12044 match (&mut self.group, parallel_group, detached) {
12045 (_, None, true) => self.pending_detached.push(node),
12046 (Some((_, _, members)), Some(_), _) => members.push(node),
12047 _ => self.frontier = vec![node],
12048 }
12049 self.children.push(TokenGraphChild { graph, node, ctx });
12050 Ok(())
12051 }
12052
12053 pub fn finish(mut self) -> Result<TokenGraph, Box<dyn std::error::Error>> {
12054 use cudarc::driver::sys;
12055 if let Some((_, _, members)) = self.group.take() {
12056 self.frontier = members;
12057 }
12058 let mut fa_sites = Vec::new();
12061 for child in &self.children {
12062 if let Some(site) = discover_fa_site(child.node, child.ctx)? {
12063 fa_sites.push(site);
12064 }
12065 }
12066 let mut exec: sys::CUgraphExec = std::ptr::null_mut();
12067 let r = unsafe { sys::cuGraphInstantiateWithFlags(&mut exec, self.parent, 0) };
12068 if r != sys::CUresult::CUDA_SUCCESS {
12069 return Err(format!("token graph instantiate: {r:?}").into());
12070 }
12071 Ok(TokenGraph {
12072 exec,
12073 parent: self.parent,
12074 _children: self.children,
12075 fa_sites,
12076 })
12077 }
12078}
12079
12080fn discover_fa_site(
12083 child_node: cudarc::driver::sys::CUgraphNode,
12084 ctx: cudarc::driver::sys::CUcontext,
12085) -> Result<Option<TokenGraphFaSite>, Box<dyn std::error::Error>> {
12086 use cudarc::driver::sys;
12087 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12088 if r == sys::CUresult::CUDA_SUCCESS {
12089 Ok(())
12090 } else {
12091 Err(format!("{what}: {r:?}").into())
12092 }
12093 }
12094 let mut graph: sys::CUgraph = std::ptr::null_mut();
12095 unsafe {
12096 cu_try(
12097 sys::cuGraphChildGraphNodeGetGraph(child_node, &mut graph),
12098 "fa-site child GetGraph",
12099 )?;
12100 }
12101 let mut count: usize = 0;
12102 unsafe {
12103 cu_try(
12104 sys::cuGraphGetNodes(graph, std::ptr::null_mut(), &mut count),
12105 "fa-site GetNodes(count)",
12106 )?;
12107 }
12108 let mut nodes: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); count];
12109 unsafe {
12110 cu_try(
12111 sys::cuGraphGetNodes(graph, nodes.as_mut_ptr(), &mut count),
12112 "fa-site GetNodes",
12113 )?;
12114 }
12115 nodes.truncate(count);
12116 let node_type =
12117 |node: sys::CUgraphNode| -> Result<sys::CUgraphNodeType, Box<dyn std::error::Error>> {
12118 let mut ty = sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_EMPTY;
12119 unsafe {
12120 cu_try(
12121 sys::cuGraphNodeGetType(node, &mut ty),
12122 "fa-site NodeGetType",
12123 )?;
12124 }
12125 Ok(ty)
12126 };
12127 let memsets: Vec<sys::CUgraphNode> = {
12128 let mut v = Vec::new();
12129 for &node in &nodes {
12130 if node_type(node)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_MEMSET {
12131 v.push(node);
12132 }
12133 }
12134 v
12135 };
12136 if memsets.len() != 3 {
12137 return Ok(None);
12138 }
12139 let dependents =
12141 |node: sys::CUgraphNode| -> Result<Vec<sys::CUgraphNode>, Box<dyn std::error::Error>> {
12142 let mut n: usize = 0;
12143 unsafe {
12144 cu_try(
12145 sys::cuGraphNodeGetDependentNodes_v2(
12146 node,
12147 std::ptr::null_mut(),
12148 std::ptr::null_mut(),
12149 &mut n,
12150 ),
12151 "fa-site GetDependentNodes(count)",
12152 )?;
12153 }
12154 let mut v: Vec<sys::CUgraphNode> = vec![std::ptr::null_mut(); n];
12155 unsafe {
12156 cu_try(
12157 sys::cuGraphNodeGetDependentNodes_v2(
12158 node,
12159 v.as_mut_ptr(),
12160 std::ptr::null_mut(),
12161 &mut n,
12162 ),
12163 "fa-site GetDependentNodes",
12164 )?;
12165 }
12166 v.truncate(n);
12167 Ok(v)
12168 };
12169 let mut fa: Option<sys::CUgraphNode> = None;
12172 let mut last_memset: Option<sys::CUgraphNode> = None;
12173 for &ms in &memsets {
12174 for dep in dependents(ms)? {
12175 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12176 fa = Some(dep);
12177 last_memset = Some(ms);
12178 }
12179 }
12180 }
12181 let (Some(fa), Some(_last)) = (fa, last_memset) else {
12182 return Ok(None);
12183 };
12184 let mut combine: Option<sys::CUgraphNode> = None;
12185 for dep in dependents(fa)? {
12186 if node_type(dep)? == sys::CUgraphNodeType::CU_GRAPH_NODE_TYPE_KERNEL {
12187 combine = Some(dep);
12188 }
12189 }
12190 let Some(combine) = combine else {
12191 return Ok(None);
12192 };
12193 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12196 unsafe {
12197 cu_try(
12198 sys::cuGraphKernelNodeGetParams_v2(fa, &mut params),
12199 "fa-site KernelNodeGetParams",
12200 )?;
12201 }
12202 let arg_i32 =
12203 |slot: usize| -> i32 { unsafe { *(*params.kernelParams.add(slot) as *const i32) } };
12204 let (hd, nh, nhkv, win) = (arg_i32(6), arg_i32(7), arg_i32(8), arg_i32(11));
12205 let width_of = |node: sys::CUgraphNode| -> Result<usize, Box<dyn std::error::Error>> {
12207 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12208 unsafe {
12209 cu_try(
12210 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12211 "fa-site MemsetNodeGetParams",
12212 )?;
12213 }
12214 Ok(mp.width)
12215 };
12216 let mut widest = memsets[0];
12217 for &ms in &memsets[1..] {
12218 if width_of(ms)? > width_of(widest)? {
12219 widest = ms;
12220 }
12221 }
12222 let memset_m: Vec<sys::CUgraphNode> =
12223 memsets.iter().copied().filter(|&m| m != widest).collect();
12224 Ok(Some(TokenGraphFaSite {
12225 ctx,
12226 memset_o: widest,
12227 memset_m: [memset_m[0], memset_m[1]],
12228 fa,
12229 combine,
12230 window: win as usize,
12231 n_head: nh as usize,
12232 n_head_kv: nhkv as usize,
12233 head_dim: hd as usize,
12234 }))
12235}
12236
12237pub struct TokenGraph {
12238 exec: cudarc::driver::sys::CUgraphExec,
12239 parent: cudarc::driver::sys::CUgraph,
12240 _children: Vec<TokenGraphChild>,
12241 fa_sites: Vec<TokenGraphFaSite>,
12242}
12243
12244unsafe impl Send for TokenGraph {}
12245
12246impl TokenGraph {
12247 pub fn retarget_bucket(&mut self, bucket: usize) -> Result<(), Box<dyn std::error::Error>> {
12252 use cudarc::driver::sys;
12253 fn cu_try(r: sys::CUresult, what: &str) -> Result<(), Box<dyn std::error::Error>> {
12254 if r == sys::CUresult::CUDA_SUCCESS {
12255 Ok(())
12256 } else {
12257 Err(format!("{what}: {r:?}").into())
12258 }
12259 }
12260 for site in &self.fa_sites {
12261 let layer_bucket = if site.window > 0 {
12262 bucket.min(site.window)
12263 } else {
12264 bucket
12265 };
12266 let sp = crate::fa_split_keys(layer_bucket, site.n_head_kv);
12267 let nsp = layer_bucket.div_ceil(sp).max(1);
12268 let mut params: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12270 unsafe {
12271 cu_try(
12272 sys::cuGraphKernelNodeGetParams_v2(site.fa, &mut params),
12273 "retarget fa GetParams",
12274 )?;
12275 *(*params.kernelParams.add(13) as *mut i32) = nsp as i32;
12276 *(*params.kernelParams.add(14) as *mut i32) = sp as i32;
12277 params.gridDimY = nsp as u32;
12278 cu_try(
12279 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.fa, ¶ms),
12280 "retarget fa SetParams",
12281 )?;
12282 }
12283 let mut cparams: sys::CUDA_KERNEL_NODE_PARAMS = unsafe { std::mem::zeroed() };
12285 unsafe {
12286 cu_try(
12287 sys::cuGraphKernelNodeGetParams_v2(site.combine, &mut cparams),
12288 "retarget combine GetParams",
12289 )?;
12290 *(*cparams.kernelParams.add(6) as *mut i32) = nsp as i32;
12291 cu_try(
12292 sys::cuGraphExecKernelNodeSetParams_v2(self.exec, site.combine, &cparams),
12293 "retarget combine SetParams",
12294 )?;
12295 }
12296 let set_width =
12298 |node: sys::CUgraphNode, width: usize| -> Result<(), Box<dyn std::error::Error>> {
12299 let mut mp: sys::CUDA_MEMSET_NODE_PARAMS = unsafe { std::mem::zeroed() };
12300 unsafe {
12301 cu_try(
12302 sys::cuGraphMemsetNodeGetParams(node, &mut mp),
12303 "retarget memset GetParams",
12304 )?;
12305 }
12306 mp.width = width;
12307 unsafe {
12308 cu_try(
12309 sys::cuGraphExecMemsetNodeSetParams(self.exec, node, &mp, site.ctx),
12310 "retarget memset SetParams",
12311 )?;
12312 }
12313 Ok(())
12314 };
12315 set_width(site.memset_o, site.n_head * nsp * site.head_dim)?;
12316 set_width(site.memset_m[0], site.n_head * nsp)?;
12317 set_width(site.memset_m[1], site.n_head * nsp)?;
12318 }
12319 Ok(())
12320 }
12321
12322 pub fn launch(&self, e: &Engine) -> Result<(), Box<dyn std::error::Error>> {
12323 use cudarc::driver::sys;
12324 let _main = e.gpu.enter_main()?;
12325 let r = unsafe { sys::cuGraphLaunch(self.exec, e.stream().cu_stream() as sys::CUstream) };
12326 if r != sys::CUresult::CUDA_SUCCESS {
12327 return Err(format!("token graph launch: {r:?}").into());
12328 }
12329 Ok(())
12330 }
12331}
12332
12333impl Drop for TokenGraph {
12334 fn drop(&mut self) {
12335 unsafe {
12336 let _ = cudarc::driver::sys::cuGraphExecDestroy(self.exec);
12337 let _ = cudarc::driver::sys::cuGraphDestroy(self.parent);
12338 }
12339 }
12340}
12341
12342std::thread_local! {
12343 static TOKEN_GRAPH_BUILDER: std::cell::RefCell<Option<TokenGraphBuilder>> =
12344 const { std::cell::RefCell::new(None) };
12345}
12346
12347pub fn token_graph_build_begin() -> Result<(), Box<dyn std::error::Error>> {
12349 let builder = TokenGraphBuilder::new()?;
12350 TOKEN_GRAPH_BUILDER.with(|cell| *cell.borrow_mut() = Some(builder));
12351 Ok(())
12352}
12353
12354pub fn token_graph_build_finish() -> Result<TokenGraph, Box<dyn std::error::Error>> {
12356 let builder = TOKEN_GRAPH_BUILDER
12357 .with(|cell| cell.borrow_mut().take())
12358 .ok_or("token graph build was not begun")?;
12359 builder.finish()
12360}
12361
12362pub fn token_graph_building() -> bool {
12364 TOKEN_GRAPH_BUILDER.with(|cell| cell.borrow().is_some())
12365}
12366
12367pub fn graph_section<F>(
12372 engine: &Engine,
12373 parallel_group: Option<u32>,
12374 f: F,
12375) -> Result<(), Box<dyn std::error::Error>>
12376where
12377 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12378{
12379 graph_section_opts(engine, parallel_group, false, false, f)
12380}
12381
12382pub fn graph_section_absorbing<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
12384where
12385 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12386{
12387 graph_section_opts(engine, None, false, true, f)
12388}
12389
12390pub fn graph_section_detached<F>(engine: &Engine, f: F) -> Result<(), Box<dyn std::error::Error>>
12393where
12394 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12395{
12396 graph_section_opts(engine, None, true, false, f)
12397}
12398
12399pub fn graph_section_opts<F>(
12400 engine: &Engine,
12401 parallel_group: Option<u32>,
12402 detached: bool,
12403 absorb: bool,
12404 f: F,
12405) -> Result<(), Box<dyn std::error::Error>>
12406where
12407 F: FnMut() -> Result<(), Box<dyn std::error::Error>>,
12408{
12409 let building = token_graph_building();
12410 if !building {
12411 let mut f = f;
12412 return f();
12413 }
12414 let (child, ctx) = {
12415 let _main = engine.gpu.enter_main()?;
12416 let mut ctx: cudarc::driver::sys::CUcontext = std::ptr::null_mut();
12417 let r = unsafe { cudarc::driver::sys::cuCtxGetCurrent(&mut ctx) };
12418 if r != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
12419 return Err(format!("graph section ctx query: {r:?}").into());
12420 }
12421 let mut f = f;
12422 let (child, _retained) = engine.capture_graph_retained_nowarm(|_| f())?;
12425 (child, ctx)
12426 };
12427 TOKEN_GRAPH_BUILDER.with(|cell| {
12428 cell.borrow_mut()
12429 .as_mut()
12430 .expect("builder checked above")
12431 .push_child(child, parallel_group, detached, absorb, ctx)
12432 })
12433}