use metrale_gpu_runtime::gpu::DevicePtr;
use metrale_model_engine::traits::{Model, PrefillSlice};
use super::super::types::PrefillInProgress;
use super::super::{FirstTokenPolicy, sample_first_token};
use super::prefill_waves::{WaveGeom, plan_prefill_waves};
pub(super) fn stream_chunk_len(
model: &dyn Model,
p: &PrefillInProgress,
max_prefill_tokens: usize,
) -> usize {
let remaining = p.prompt_tokens.len() - p.chunk_offset;
let effective_max = if model.is_mla() {
remaining
} else {
max_prefill_tokens
};
metrale_model_engine::prefill_plan::plan_chunk_len(
p.chunk_offset,
p.prompt_tokens.len(),
remaining.min(effective_max),
model.kv_block_size(),
model.prefill_tail_split(&p.prompt_tokens),
)
}
pub(super) fn run_batched_prefill_step(
model: &dyn Model,
sched: &crate::scheduler::sched_ctx::SchedCtx,
prefilling: &mut [PrefillInProgress],
completed_indices: &mut Vec<(usize, Option<u32>)>,
max_prefill_tokens: usize,
max_batch_tokens: usize,
prefill_stream: u64,
prefill_event: u64,
think_end_token: Option<u32>,
tool_call_start_token: Option<u32>,
) {
super::poll_innerq(model);
let n = prefilling.len();
let mut chunk_lens: Vec<usize> = Vec::with_capacity(n);
let mut is_last_flags: Vec<bool> = Vec::with_capacity(n);
let varlen = sched.levers.prefill_varlen;
let shared_geom: Option<(usize, bool)> = if !varlen
&& sched.levers.prefill_codispatch
&& !model.is_mla()
&& n >= 2
&& prefilling.iter().all(|p| p.chunk_offset == 0)
&& prefilling
.iter()
.all(|p| p.prompt_tokens.len() == prefilling[0].prompt_tokens.len())
{
let total = prefilling[0].prompt_tokens.len();
let cl = metrale_model_engine::prefill_plan::plan_chunk_len(
0,
total,
total.min(max_prefill_tokens),
model.kv_block_size(),
model.prefill_tail_split(&prefilling[0].prompt_tokens),
);
let is_last = cl >= total;
Some((cl, is_last))
} else {
None
};
for p in prefilling.iter() {
let (chunk_len, is_last) = if let Some((cl, il)) = shared_geom {
(cl, il)
} else {
let chunk_len = stream_chunk_len(model, p, max_prefill_tokens);
let is_last = p.chunk_offset + chunk_len >= p.prompt_tokens.len();
(chunk_len, is_last)
};
chunk_lens.push(chunk_len);
is_last_flags.push(is_last);
}
let wave_cap = max_prefill_tokens.min(max_batch_tokens).max(1);
let geoms: Vec<WaveGeom> = prefilling
.iter()
.enumerate()
.map(|(i, p)| WaveGeom {
chunk_start: p.chunk_offset,
chunk_len: chunk_lens[i],
is_last: is_last_flags[i],
})
.collect();
let waves = plan_prefill_waves(&geoms, varlen, wave_cap);
let n_waves = waves.len();
if varlen {
let wave_m: Vec<usize> = waves
.iter()
.map(|w| w.iter().map(|&i| chunk_lens[i]).sum())
.collect();
tracing::info!(
"Varlen prefill waves: {n} streams -> {n_waves} wave(s), M per wave {wave_m:?} \
(cap {wave_cap})"
);
}
let t0_batch = sched.io.clock.now();
for wave in waves {
let mut in_wave = vec![false; n];
for &i in &wave {
in_wave[i] = true;
}
let mut slices: Vec<PrefillSlice<'_>> = prefilling
.iter_mut()
.enumerate()
.filter(|(i, _)| in_wave[*i])
.map(|(i, p)| PrefillSlice {
prompt_tokens: &p.prompt_tokens,
seq: &mut p.seq,
chunk_start: p.chunk_offset,
chunk_len: chunk_lens[i],
is_last_chunk: is_last_flags[i],
})
.collect();
let logits_per_stream = match model.prefill_batch_chunk(&mut slices, prefill_stream) {
Ok(v) => v,
Err(e) => {
tracing::error!(
"Batched prefill error (wave of {} streams, {n} prefilling): {e:#}",
wave.len()
);
for &i in &wave {
completed_indices.push((i, None));
}
return;
}
};
drop(slices);
let _ = model.record_event(prefill_event, prefill_stream);
let _ = model.stream_wait_event(model.default_stream(), prefill_event);
debug_assert_eq!(
logits_per_stream.len(),
wave.len(),
"prefill_batch_chunk returned wrong logit count"
);
for (k, &i) in wave.iter().enumerate() {
let p = &mut prefilling[i];
p.chunk_offset += chunk_lens[i];
if !is_last_flags[i] {
continue;
}
let logits = logits_per_stream[k];
if logits == DevicePtr::NULL {
tracing::error!(
"Batched prefill: stream {i} marked is_last but model returned NULL logits",
);
completed_indices.push((i, None));
continue;
}
match sample_first_token(
model,
logits,
p.temperature,
p.top_k,
p.top_p,
p.min_p,
&p.eos_tokens,
p.grammar_state.as_mut(),
FirstTokenPolicy::for_birth(
p.enable_thinking,
think_end_token,
tool_call_start_token,
p.min_tokens,
p.require_tool_call,
),
&sched.levers.sampling(),
sched.io.tel.dumps(),
) {
Ok(first) => {
tracing::info!(
"Batched prefill[{i}/{n}] first token: {first} (chunk_len={}, total_tokens={})",
chunk_lens[i],
p.prompt_tokens.len(),
);
completed_indices.push((i, Some(first)));
}
Err(e) => {
tracing::error!("Batched prefill[{i}] sampling: {e:#}");
completed_indices.push((i, None));
}
}
}
}
let elapsed = sched
.io
.clock
.now()
.saturating_duration_since(t0_batch)
.as_micros();
if elapsed > 1000 {
tracing::debug!("Batched prefill step: {n} streams, {n_waves} waves, {elapsed}µs total");
}
}