use std::collections::HashSet;
use std::time::Instant;
use foundation::LogitValue;
use super::attn::{encode_full_attention_block, encode_linear_block};
use super::hc::encode_hyper_connection;
use super::moe;
use super::ple::encode_ple_layer;
use super::{layer_tensor, TRUNK_PREFIX};
use crate::moe_prefill_pipeline::routed_pipeline_banks;
use crate::real_forward::RealForwardRunner;
use crate::real_forward_dispatch::{encode_embed_any, encode_gemv_any};
use crate::real_forward_types::{RealForwardError, MAX_PREFILL_BATCH};
impl RealForwardRunner {
pub(crate) fn prefill_chunk_real_qwen4(
&mut self,
tokens: &[i32],
start_position: usize,
logits: &mut [LogitValue],
) -> Result<(), RealForwardError> {
if tokens.is_empty() {
return Err(RealForwardError::Unsupported(
"prefill_chunk called with an empty chunk".to_string(),
));
}
if self.routed_batch_prefill {
return Err(RealForwardError::Unsupported(
"TURBOSPARK_ROUTED_BATCH is not wired for this family: the batched \
routed pair exists in the gemma4 (INT4-affine) and gpt-oss (MXFP4) \
chunked drivers alone, and this driver keeps its routed half as a \
per-token loop"
.to_string(),
));
}
if self.batched_gemv_prefill {
return Err(RealForwardError::Unsupported(
"TURBOSPARK_BATCHED_GEMV is not wired for this family: the M-row \
resident GEMM exists in the gemma4 and dense-qwen chunked drivers \
alone (INT4-affine), and this driver keeps every resident GEMV per \
token"
.to_string(),
));
}
let mut offset = 0usize;
while offset < tokens.len() {
let take = (tokens.len() - offset).min(MAX_PREFILL_BATCH);
let last = offset + take == tokens.len();
self.prefill_micro_batch_qwen4(
&tokens[offset..offset + take],
start_position + offset,
last,
logits,
)?;
offset += take;
}
Ok(())
}
fn prefill_micro_batch_qwen4(
&mut self,
tokens: &[i32],
start_position: usize,
want_head: bool,
logits: &mut [LogitValue],
) -> Result<(), RealForwardError> {
let started = Instant::now();
let result =
self.prefill_micro_batch_qwen4_inner(tokens, start_position, want_head, logits);
self.phases.calls += tokens.len() as u64;
self.phases.total_nanos += started.elapsed().as_nanos() as u64;
result
}
fn prefill_micro_batch_qwen4_inner(
&mut self,
tokens: &[i32],
start_position: usize,
want_head: bool,
logits: &mut [LogitValue],
) -> Result<(), RealForwardError> {
let arch = self.arch.clone();
let hidden = arch.hidden_size as usize;
let inter = arch.intermediate_size as usize;
let moe_inter = arch.moe_intermediate_size as u32;
let vocab = arch.vocab_size as usize;
let num_experts = arch.num_experts as usize;
let top_k = arch.top_k_experts as usize;
let heads_per_ngram = arch.ple.heads_per_ngram as usize;
let use_silu = arch.hidden_activation.contains("silu");
let gpu_err = RealForwardError::Gpu;
let m = tokens.len();
if start_position != self.kv.position() {
return Err(RealForwardError::Unsupported(format!(
"non-sequential chunk start {start_position}; KV cache is at {}",
self.kv.position()
)));
}
if let Some(&bad) = tokens.iter().find(|&&t| (t as usize) >= vocab) {
return Err(RealForwardError::Unsupported(format!(
"token id {bad} outside vocab {vocab}"
)));
}
let banks = if self.routed_pipeline {
routed_pipeline_banks(self.expert_cache_slots, top_k)
} else {
1
};
let embed_name = "language_model.model.embed_tokens.weight";
let hc_count = self
.real_qwen4
.as_ref()
.expect("real qwen4 state present")
.hc_count;
self.real_qwen4
.as_ref()
.expect("real qwen4 state present")
.qsa
.clear_selection_errors();
let wide_dim = hidden * hc_count;
let mut pass = self.context.begin_pass_labeled("chunk cb1 (attn+router)");
for (t, &token) in tokens.iter().enumerate() {
let qwen4 = self.real_qwen4.as_ref().expect("checked above");
let row_off = (t * wide_dim * 2) as u64;
for c in 0..hc_count {
encode_embed_any_qwen4(
&mut self.context,
&pass,
&self.weights,
&self.index,
embed_name,
(&qwen4.wide_x, row_off + (c * hidden * 2) as u64),
token as u32,
hidden as u32,
vocab,
)?;
}
}
let mut pending_routed: Option<gpu::CommittedPass> = None;
for layer in 0..arch.num_layers as usize {
{
let (context, weights, index, scratch, kv, qwen4) = (
&mut self.context,
&self.weights,
&self.index,
&self.scratch,
&mut self.kv,
self.real_qwen4.as_mut().expect("checked above"),
);
if layer == qwen4.ple_layer {
let wide_buf = qwen4.wide_x.clone();
for (t, &token) in tokens.iter().enumerate() {
let x_off = (t * wide_dim * 2) as u64;
let emb_off = (t * hidden * 2) as u64;
encode_ple_layer(
context,
&pass,
weights,
index,
qwen4,
(&wide_buf, x_off),
hidden,
heads_per_ngram,
token,
emb_off,
)?;
}
}
for t in 0..m {
let x_off = (t * wide_dim * 2) as u64;
let hc_row = (t * hc_count * 2) as u64;
let position = start_position + t;
encode_hyper_connection(
context,
&pass,
weights,
index,
qwen4,
(&qwen4.wide_x, x_off),
&layer_tensor(layer, "attn_hyper_connection"),
hidden,
(&scratch.normed, 0),
true,
hc_row,
)?;
if arch.layer_is_linear(layer) {
encode_linear_block(
context,
&pass,
weights,
index,
&arch,
qwen4,
(&scratch.normed, 0),
scratch,
layer,
position,
false,
)?;
} else {
encode_full_attention_block(
context,
&pass,
weights,
index,
&arch,
qwen4,
(&scratch.normed, 0),
scratch,
kv,
layer,
position,
)?;
}
gpu::encode_hc_inject_add(
context,
&pass,
(&qwen4.wide_x, x_off),
(&scratch.o, 0),
(&qwen4.hc_inject, hc_row),
hc_count as u32,
hidden as u32,
)
.map_err(gpu_err)?;
let moe_x_off = (t * hidden * 2) as u64;
encode_hyper_connection(
context,
&pass,
weights,
index,
qwen4,
(&qwen4.wide_x, x_off),
&layer_tensor(layer, "mlp_hyper_connection"),
hidden,
(&qwen4.moe_x, moe_x_off),
true,
hc_row,
)?;
let logits_row_offset = (t * num_experts * 4) as u64;
moe::encode_moe_router(
context,
&pass,
weights,
index,
qwen4,
(&qwen4.moe_x, moe_x_off),
layer,
hidden,
num_experts,
logits_row_offset,
)?;
}
}
let cb1 = pass.commit();
let t_wait = Instant::now();
self.phases.cb1_gpu_nanos += (cb1.wait_with_gpu_time() * 1e9) as u64;
self.phases.gpu_wait_nanos += t_wait.elapsed().as_nanos() as u64;
self.retire_routed(&mut pending_routed);
let mut previous_slots: HashSet<usize> = HashSet::new();
for t in 0..m {
if banks == 1 {
self.retire_routed(&mut pending_routed);
}
let slot = moe::RoutedSlot {
token: t,
bank: t % banks,
protect: if banks == 1 {
HashSet::new()
} else {
previous_slots.clone()
},
};
let routed_pass = self.context.begin_pass_labeled("routed cb");
let used = {
let (
context,
weights,
index,
scratch,
qwen4,
streamers,
slot_buffers,
mapped,
routed_blobs,
routed_blobs_banks,
moe_offsets,
routed_layouts,
router_hist,
phases,
) = (
&mut self.context,
&self.weights,
&self.index,
&self.scratch,
self.real_qwen4.as_mut().expect("checked above"),
&mut self.streamers,
&self.slot_buffers,
&self.mapped,
&self.routed_blobs,
&self.routed_blobs_banks,
&self.moe_offsets,
&self.routed_layouts,
&mut self.router_hist,
&mut self.phases,
);
let moe_x_off = (t * hidden * 2) as u64;
let used = moe::encode_moe_layer(
context,
&routed_pass,
weights,
index,
scratch,
qwen4,
(&qwen4.moe_x, moe_x_off),
streamers,
slot_buffers,
mapped,
routed_blobs.as_ref(),
routed_blobs_banks,
moe_offsets,
routed_layouts,
router_hist,
phases,
layer,
hidden,
inter,
moe_inter,
num_experts,
top_k,
use_silu,
&slot,
)?;
let x_off = (t * wide_dim * 2) as u64;
let hc_row = (t * hc_count * 2) as u64;
gpu::encode_hc_inject_add(
context,
&routed_pass,
(&qwen4.wide_x, x_off),
(&qwen4.h2, 0),
(&qwen4.hc_inject, hc_row),
hc_count as u32,
hidden as u32,
)
.map_err(gpu_err)?;
used
};
if banks > 1 {
self.retire_routed(&mut pending_routed);
}
debug_assert!(pending_routed.is_none(), "routed pipeline depth is 1");
pending_routed = Some(routed_pass.commit());
previous_slots = used.into_iter().collect();
}
pass = self.context.begin_pass_labeled("chunk cb1 (attn+router)");
}
self.retire_routed(&mut pending_routed);
if want_head {
pass.relabel("final cb (head)");
let last_off = ((m - 1) * wide_dim * 2) as u64;
let (context, weights, index, scratch, qwen4) = (
&mut self.context,
&self.weights,
&self.index,
&self.scratch,
self.real_qwen4.as_ref().expect("checked above"),
);
encode_hyper_connection(
context,
&pass,
weights,
index,
qwen4,
(&qwen4.wide_x, last_off),
&format!("{TRUNK_PREFIX}.hyper_connection_mixer"),
hidden,
(&scratch.normed, 0),
false,
0,
)?;
let head_name = "language_model.lm_head.weight";
encode_gemv_any(
context,
&pass,
weights,
index,
head_name,
vocab,
hidden,
(&scratch.normed, 0),
(&scratch.logits, 0),
)?;
} else {
pass.relabel("final cb (chunk, no head)");
}
let t_wait = Instant::now();
self.phases.final_cb_gpu_nanos += (pass.commit_and_wait_with_gpu_time() * 1e9) as u64;
self.phases.final_wait_nanos += t_wait.elapsed().as_nanos() as u64;
let qwen4 = self.real_qwen4.as_ref().expect("checked above");
if let Some(layer) = qwen4.qsa.first_nan_score_layer() {
return Err(RealForwardError::Unsupported(format!(
"layer {layer}: QSA indexer produced a NaN block score"
)));
}
self.kv.advance_by(m);
if !want_head {
return Ok(());
}
if vocab != logits.len() {
return Err(RealForwardError::Unsupported(format!(
"vocab mismatch: model has {}, caller expected {}",
vocab,
logits.len()
)));
}
gpu::read_buffer_f16_into(&self.scratch.logits, 0, logits);
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
fn encode_embed_any_qwen4(
context: &mut gpu::MetalContext,
pass: &gpu::PassEncoder,
weights: &gpu::ResidentGpuWeights,
index: &model_io::ResidentIndex,
name: &str,
out: (&gpu::MetalBuffer, u64),
token: u32,
hidden: u32,
vocab: usize,
) -> Result<(), RealForwardError> {
encode_embed_any(
context, pass, weights, index, name, out, token, hidden, vocab, 1.0,
)
}