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