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