use std::time::Instant;
use model_io::ResidentIndex;
use crate::families::qwen::batched_scratch::BatchedScratch;
use crate::families::qwen::{layer_tensor, RealQwenState};
use crate::moe_prefill_pipeline::{encode_routes, next_routed_sub_batch, plan_and_stream_union};
use crate::real_forward_dispatch::{encode_gemv_any, router_topk_gemma4};
use crate::real_forward_init::MappedResidency;
use crate::real_forward_layout::{RoutedBlobLayout, RoutedLayerLayout};
use crate::real_forward_types::{DecodeScratch, PhaseCounters, RealForwardError};
use crate::real_forward_utils::f16_slice_to_le_bytes;
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_qwen_layer_moe_batched(
context: &mut gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
index: &ResidentIndex,
scratch: &DecodeScratch,
qwen: &RealQwenState,
batched: &BatchedScratch,
streamers: &mut [Option<streaming::PreadExpertStreamer>],
slot_buffers: &[Vec<gpu::MetalBuffer>],
mapped: &MappedResidency,
moe_offsets: &[gpu::MoeExpertOffsets],
routed_layouts: &[RoutedLayerLayout],
router_hist: &mut Option<crate::router_hist::RouterHistogram>,
phases: &mut PhaseCounters,
expert_cache_slots: usize,
layer: usize,
hidden: usize,
inter: usize,
moe_inter: u32,
num_experts: usize,
top_k: usize,
use_silu: bool,
batch: usize,
) -> Result<gpu::CommittedPass, RealForwardError> {
let gpu_err = RealForwardError::Gpu;
let routed = batched.routed.as_ref().ok_or_else(|| {
RealForwardError::Unsupported(
"batched routed half on an install with no routed scratch".to_string(),
)
})?;
let layout = routed_layouts[layer];
if layout.phase1 != RoutedBlobLayout::Affine || layout.phase2 != RoutedBlobLayout::Affine {
return Err(RealForwardError::Unsupported(format!(
"batched routed verify is wired for INT4-affine blobs only; layer {layer} \
phase1 {:?} phase2 {:?}",
layout.phase1, layout.phase2
)));
}
if mapped.buffers.get(layer).is_some_and(Option::is_some) {
return Err(RealForwardError::Unsupported(format!(
"the batched verify pass and mapped expert residency \
(TURBOSPARK_EXPERT_RESIDENCY=mapped) cannot be combined: this pass binds one \
buffer per CACHE SLOT and mapped residency has no slot cache (layer {layer}). \
Pick one"
)));
}
if slot_buffers[layer].len() > gpu::MAX_PREFILL_EXPERT_BINDINGS {
return Err(RealForwardError::Unsupported(format!(
"batched routed verify needs slot indices below {}; this install has {} slots",
gpu::MAX_PREFILL_EXPERT_BINDINGS,
slot_buffers[layer].len()
)));
}
if router_hist
.as_ref()
.is_some_and(crate::router_hist::RouterHistogram::pilot_enabled)
{
return Err(RealForwardError::Unsupported(format!(
"batched routed verify and the router pilot probe (TURBOSPARK_PILOT_PROBE) cannot be combined (layer {layer})"
)));
}
let t_router = Instant::now();
let router_logits =
gpu::read_f32_buffer_at(&routed.batch_router_logits_f32, 0, batch * num_experts);
let mut selected_all = Vec::with_capacity(batch);
let mut weights_all = Vec::with_capacity(batch);
for t in 0..batch {
let (selected, route_weights) = router_topk_gemma4(
&router_logits[t * num_experts..(t + 1) * num_experts],
top_k,
&qwen.per_expert_ones,
);
if let Some(hist) = router_hist.as_mut() {
hist.record(layer, &selected);
}
selected_all.push(selected);
weights_all.push(route_weights);
}
phases.router_nanos += t_router.elapsed().as_nanos() as u64;
let blob_refs: Vec<(gpu::MetalBuffer, u64)> = slot_buffers[layer]
.iter()
.map(|b| (b.clone(), 0u64))
.collect();
let t_bind = Instant::now();
let bind_refs: Vec<(&gpu::MetalBuffer, u64)> = blob_refs.iter().map(|(b, o)| (b, *o)).collect();
routed
.wide_blobs
.bind(context, use_silu, &bind_refs)
.map_err(gpu_err)?;
phases.bind_nanos += t_bind.elapsed().as_nanos() as u64;
let cap = expert_cache_slots.min(gpu::MAX_PREFILL_EXPERT_BINDINGS);
let moe_inter_us = moe_inter as usize;
let offsets = &moe_offsets[layer];
let mut committed: Option<gpu::CommittedPass> = None;
let mut sub_start = 0usize;
while sub_start < batch {
if let Some(prev) = committed.take() {
let t_wait = Instant::now();
prev.wait();
phases.pipeline_wait_nanos += t_wait.elapsed().as_nanos() as u64;
}
let sub = next_routed_sub_batch(&selected_all, sub_start, cap, layer, top_k)?;
let streamer = streamers[layer].as_mut().ok_or_else(|| {
RealForwardError::Unsupported(format!("real Qwen layer {layer} has no expert streamer"))
})?;
let slot_of = plan_and_stream_union(streamer, phases, &sub.union)?;
let (routes, routing16) = encode_routes(&selected_all, &weights_all, &sub, &slot_of, top_k);
let sub_len = sub.sub_len;
let x_off = (sub_start * hidden * 2) as u64;
let acts_off = (sub_start * top_k * moe_inter_us * 2) as u64;
let rw_off = (sub_start * top_k * 2) as u64;
let routes_off = (sub_start * top_k * gpu::MoePrefillRoute::STRIDE_BYTES) as u64;
gpu::write_buffer_bytes(
&routed.batch_routing_w,
rw_off as usize,
&f16_slice_to_le_bytes(&routing16),
);
gpu::write_buffer_bytes(
&routed.batch_routes,
routes_off as usize,
&gpu::MoePrefillRoute::bytes(&routes),
);
let pass = context.begin_pass_labeled("routed cb (batched verify)");
for (blob, _) in &blob_refs {
pass.use_read_buffer(blob);
}
for i in 0..sub_len {
let t = sub_start + i;
let x_row = (t * hidden * 2) as u64;
let inter_row = (t * inter * 2) as u64;
encode_gemv_any(
context,
&pass,
weights,
index,
&layer_tensor(layer, "mlp.shared_expert.gate_proj.weight"),
inter,
hidden,
(&batched.moe_x, x_row),
(&batched.ffn_gate, inter_row),
)?;
encode_gemv_any(
context,
&pass,
weights,
index,
&layer_tensor(layer, "mlp.shared_expert.up_proj.weight"),
inter,
hidden,
(&batched.moe_x, x_row),
(&batched.ffn_up, inter_row),
)?;
let act = if use_silu {
gpu::encode_silu_mul
} else {
gpu::encode_gelu_mul
};
act(
context,
&pass,
(&batched.ffn_gate, inter_row),
(&batched.ffn_up, inter_row),
(&batched.ffn_act, inter_row),
inter as u32,
)
.map_err(gpu_err)?;
encode_gemv_any(
context,
&pass,
weights,
index,
&layer_tensor(layer, "mlp.shared_expert.down_proj.weight"),
hidden,
inter,
(&batched.ffn_act, inter_row),
(&routed.batch_h1, x_row),
)?;
encode_gemv_any(
context,
&pass,
weights,
index,
&layer_tensor(layer, "mlp.shared_expert_gate.weight"),
1,
hidden,
(&batched.moe_x, x_row),
(&routed.batch_gate_logit, (t * 2) as u64),
)?;
gpu::encode_sigmoid_scalar_mul(
context,
&pass,
(&routed.batch_h1, x_row),
(&routed.batch_gate_logit, (t * 2) as u64),
hidden as u32,
)
.map_err(gpu_err)?;
}
gpu::encode_moe_prefill_phase1(
context,
&pass,
&routed.wide_blobs,
offsets,
(&batched.moe_x, x_off),
(&routed.batch_acts, acts_off),
(&routed.batch_routes, routes_off),
hidden as u32,
moe_inter,
top_k as u32,
(sub_len * top_k) as u32,
use_silu,
)
.map_err(gpu_err)?;
gpu::encode_moe_prefill_phase2_fused(
context,
&pass,
&routed.wide_blobs,
offsets,
(&routed.batch_acts, acts_off),
(&routed.batch_routing_w, rw_off),
(&routed.batch_routes, routes_off),
(&routed.batch_h1, x_off),
(&routed.batch_y, x_off),
hidden as u32,
moe_inter,
top_k as u32,
sub_len as u32,
use_silu,
)
.map_err(gpu_err)?;
for i in 0..sub_len {
let row = ((sub_start + i) * hidden * 2) as u64;
gpu::encode_residual_add(
context,
&pass,
(&scratch.x, row),
(&routed.batch_y, row),
hidden as u32,
)
.map_err(gpu_err)?;
}
sub_start += sub_len;
committed = Some(pass.commit());
}
Ok(committed.expect("batch >= 1 guarantees at least one sub-batch"))
}