use model_io::{ArchConfig, ResidentIndex};
use crate::real_forward_types::{RealForwardError, MAX_PREFILL_BATCH};
use crate::real_forward_utils::{layer_tensor, read_bf16_host};
pub(crate) struct RealGemmaState {
pub(crate) effective_scale: Vec<gpu::MetalBuffer>,
pub(crate) per_expert_scale: Vec<Vec<f32>>,
pub(crate) layer_scalar: Vec<f32>,
pub(crate) router_logits_f32: gpu::MetalBuffer,
pub(crate) pilot_logits_f32: gpu::MetalBuffer,
pub(crate) dense_x: gpu::MetalBuffer,
pub(crate) routed_x: gpu::MetalBuffer,
pub(crate) router_x: gpu::MetalBuffer,
pub(crate) h1: gpu::MetalBuffer,
pub(crate) h2: gpu::MetalBuffer,
pub(crate) batched: Option<BatchedPrefillScratch>,
}
pub(crate) struct BatchedPrefillScratch {
pub(crate) batch_acts: gpu::MetalBuffer,
pub(crate) batch_y: gpu::MetalBuffer,
pub(crate) batch_h1: gpu::MetalBuffer,
pub(crate) batch_routing_w: gpu::MetalBuffer,
pub(crate) batch_routes: gpu::MetalBuffer,
pub(crate) batch_zero: gpu::MetalBuffer,
pub(crate) wide_blobs: gpu::RoutedBlobsWideBuffer,
pub(crate) batch_normed: gpu::MetalBuffer,
pub(crate) batch_q: gpu::MetalBuffer,
pub(crate) batch_attn_out: gpu::MetalBuffer,
pub(crate) batch_o: gpu::MetalBuffer,
pub(crate) batch_ffn_gate: gpu::MetalBuffer,
pub(crate) batch_ffn_up: gpu::MetalBuffer,
pub(crate) batch_ffn_act: gpu::MetalBuffer,
}
impl RealGemmaState {
pub(crate) fn build(
context: &mut gpu::MetalContext,
weights: &gpu::ResidentGpuWeights,
index: &ResidentIndex,
arch: &ArchConfig,
) -> Result<Self, RealForwardError> {
if !arch.ffn_sandwich_norms || !arch.router_scaled {
return Err(RealForwardError::Unsupported(
"real-checkpoint flow currently implements Gemma 4 only \
(ffn_sandwich_norms + router_scaled)"
.to_string(),
));
}
if arch.num_experts <= 0 || arch.top_k_experts <= 0 {
return Err(RealForwardError::Unsupported(
"real Gemma 4 installs are MoE; num_experts/top_k must be positive".to_string(),
));
}
if arch.top_k_experts as usize > gpu::MAX_STREAMED_EXPERTS {
return Err(RealForwardError::Unsupported(format!(
"top_k {} exceeds the {}-slot MoE kernels",
arch.top_k_experts,
gpu::MAX_STREAMED_EXPERTS
)));
}
let hidden = arch.hidden_size as usize;
let num_layers = arch.num_layers as usize;
let num_experts = arch.num_experts as usize;
let inv_sqrt_d = 1.0f32 / (hidden as f32).sqrt();
let mut effective_scale = Vec::with_capacity(num_layers);
let mut per_expert_scale = Vec::with_capacity(num_layers);
let mut layer_scalar = Vec::with_capacity(num_layers);
for layer in 0..num_layers {
let scale = read_bf16_host(weights, index, &layer_tensor(layer, "router.scale"))?;
if scale.len() != hidden {
return Err(RealForwardError::Unsupported(format!(
"router.scale layer {layer}: {} elements, expected {hidden}",
scale.len()
)));
}
let folded: Vec<u8> = scale
.iter()
.flat_map(|&v| compute::f32_to_bf16(v * inv_sqrt_d).to_le_bytes())
.collect();
let buffer = context.new_output_buffer(folded.len() as u64);
gpu::write_buffer_bytes(&buffer, 0, &folded);
effective_scale.push(buffer);
let pes = read_bf16_host(
weights,
index,
&layer_tensor(layer, "router.per_expert_scale"),
)?;
if pes.len() != num_experts {
return Err(RealForwardError::Unsupported(format!(
"per_expert_scale layer {layer}: {} elements, expected {num_experts}",
pes.len()
)));
}
per_expert_scale.push(pes);
let scalar = read_bf16_host(weights, index, &layer_tensor(layer, "layer_scalar"))?;
layer_scalar.push(*scalar.first().ok_or_else(|| {
RealForwardError::Unsupported(format!("layer_scalar layer {layer} is empty"))
})?);
}
let halfs = |n: usize| context.new_output_buffer((n.max(1) * 2) as u64);
Ok(Self {
router_logits_f32: context
.new_output_buffer((num_experts * 4 * MAX_PREFILL_BATCH) as u64),
pilot_logits_f32: context.new_output_buffer((num_experts * 4) as u64),
dense_x: halfs(hidden * MAX_PREFILL_BATCH),
routed_x: halfs(hidden * MAX_PREFILL_BATCH),
router_x: halfs(hidden),
h1: halfs(hidden),
h2: halfs(hidden),
batched: None,
effective_scale,
per_expert_scale,
layer_scalar,
})
}
pub(crate) fn ensure_batched(
&mut self,
context: &mut gpu::MetalContext,
arch: &ArchConfig,
) -> Result<(), RealForwardError> {
if self.batched.is_some() {
return Ok(());
}
let use_silu = arch.hidden_activation.contains("silu");
let wide_blobs =
gpu::RoutedBlobsWideBuffer::new(context, use_silu).map_err(RealForwardError::Gpu)?;
let top_k = arch.top_k_experts as usize;
let hidden = arch.hidden_size as usize;
let moe_inter = arch.moe_intermediate_size.max(1) as usize;
let inter = arch.intermediate_size as usize;
let q_dim = (arch.num_heads * arch.head_dim.max(arch.full_head_dim)) as usize;
let halfs = |n: usize| context.new_output_buffer((n.max(1) * 2) as u64);
let batch_rows = |per_token: usize| halfs(MAX_PREFILL_BATCH * per_token);
self.batched = Some(BatchedPrefillScratch {
batch_acts: batch_rows(top_k * moe_inter),
batch_y: batch_rows(hidden),
batch_h1: batch_rows(hidden),
batch_routing_w: batch_rows(top_k),
batch_routes: context.new_output_buffer(
(MAX_PREFILL_BATCH * top_k * gpu::MoePrefillRoute::STRIDE_BYTES) as u64,
),
batch_zero: {
let bytes = MAX_PREFILL_BATCH * hidden.max(1) * 2;
let buffer = context.new_output_buffer(bytes as u64);
gpu::write_buffer_bytes(&buffer, 0, &vec![0u8; bytes]);
buffer
},
wide_blobs,
batch_normed: batch_rows(hidden),
batch_q: batch_rows(q_dim),
batch_attn_out: batch_rows(q_dim),
batch_o: batch_rows(hidden),
batch_ffn_gate: batch_rows(inter),
batch_ffn_up: batch_rows(inter),
batch_ffn_act: batch_rows(inter),
});
Ok(())
}
pub(crate) fn batched(&self) -> &BatchedPrefillScratch {
self.batched
.as_ref()
.expect("prefill_chunk_real_gemma4 calls ensure_batched before any layer runs")
}
}