use std::collections::{HashMap, HashSet};
use std::time::Instant;
use half::f16;
use crate::real_forward::RealForwardRunner;
use crate::real_forward_types::{PhaseCounters, RealForwardError, ROUTED_BANKS};
#[derive(Debug, Clone)]
pub(crate) struct RoutedSlot {
pub(crate) token: usize,
pub(crate) bank: usize,
pub(crate) protect: std::collections::HashSet<usize>,
}
impl RoutedSlot {
pub(crate) fn sequential() -> Self {
Self {
token: 0,
bank: 0,
protect: std::collections::HashSet::new(),
}
}
}
pub(crate) fn routed_pipeline_banks(expert_cache_slots: usize, top_k: usize) -> usize {
if expert_cache_slots >= 2 * top_k {
ROUTED_BANKS
} else {
1
}
}
impl RealForwardRunner {
pub(crate) fn retire_routed(&mut self, pending: &mut Option<gpu::CommittedPass>) {
if let Some(committed) = pending.take() {
let t_retire = Instant::now();
self.phases.routed_cb_gpu_nanos += (committed.wait_with_gpu_time() * 1e9) as u64;
self.phases.pipeline_wait_nanos += t_retire.elapsed().as_nanos() as u64;
}
}
pub(crate) fn plan_routed_union(
&mut self,
layer: usize,
family: &str,
union: &[usize],
) -> Result<HashMap<usize, usize>, RealForwardError> {
let streamer = self.streamers[layer].as_mut().ok_or_else(|| {
RealForwardError::Unsupported(format!(
"{family} layer {layer} has no packed-expert streamer"
))
})?;
plan_and_stream_union(streamer, &mut self.phases, union)
}
}
pub(crate) struct RoutedSubBatch {
pub(crate) sub_start: usize,
pub(crate) sub_len: usize,
pub(crate) union: Vec<usize>,
}
pub(crate) fn next_routed_sub_batch(
selected_all: &[Vec<usize>],
sub_start: usize,
cap: usize,
layer: usize,
top_k: usize,
) -> Result<RoutedSubBatch, RealForwardError> {
let mut union: Vec<usize> = Vec::new();
let mut seen: HashSet<usize> = HashSet::new();
for &e in &selected_all[sub_start] {
if seen.insert(e) {
union.push(e);
}
}
if union.len() > cap {
return Err(RealForwardError::Unsupported(format!(
"layer {layer}: one token routes {top_k} experts against {cap} slots"
)));
}
let mut sub_len = 1usize;
while sub_start + sub_len < selected_all.len() {
let selected = &selected_all[sub_start + sub_len];
let new = selected.iter().filter(|e| !seen.contains(*e)).count();
if union.len() + new > cap {
break;
}
for &e in selected {
if seen.insert(e) {
union.push(e);
}
}
sub_len += 1;
}
Ok(RoutedSubBatch {
sub_start,
sub_len,
union,
})
}
pub(crate) fn plan_and_stream_union(
streamer: &mut streaming::PreadExpertStreamer,
phases: &mut PhaseCounters,
union: &[usize],
) -> Result<HashMap<usize, usize>, RealForwardError> {
let t_io = Instant::now();
let plan = streamer.plan_experts_cached(union, &HashSet::new());
phases.expert_requests += plan.experts.len() as u64;
phases.expert_hits += plan.hits as u64;
let slots = streamer
.execute_expert_cache_plan(&plan)
.map_err(|e| RealForwardError::Unsupported(format!("expert stream: {e}")))?;
phases.expert_io_nanos += t_io.elapsed().as_nanos() as u64;
Ok(union.iter().copied().zip(slots).collect())
}
pub(crate) fn encode_routes(
selected_all: &[Vec<usize>],
weights_all: &[Vec<f32>],
sub: &RoutedSubBatch,
slot_of: &HashMap<usize, usize>,
top_k: usize,
) -> (Vec<gpu::MoePrefillRoute>, Vec<f16>) {
let mut routes = Vec::with_capacity(sub.sub_len * top_k);
let mut routing16 = Vec::with_capacity(sub.sub_len * top_k);
for i in 0..sub.sub_len {
for r in 0..top_k {
let expert = selected_all[sub.sub_start + i][r];
routes.push(gpu::MoePrefillRoute {
token: i as u32,
rank: r as u32,
slot: *slot_of
.get(&expert)
.expect("plan assigned every union expert a slot") as u32,
});
routing16.push(f16::from_f32(weights_all[sub.sub_start + i][r]));
}
}
(routes, routing16)
}