use std::time::Instant;
use foundation::{LogitValue, LogitsView, TokenId};
use selection::select;
use tokenizer::MfTokenizer;
use crate::config::GenerationConfig;
use crate::error::RuntimeError;
use crate::pacing::{Pacer, THERMAL_POLL_TOKENS};
use crate::power::{stepped_cap, MemoryPressure, ThermalLevel};
use crate::producer::LogitProducer;
pub use crate::raw_completion_chunked::{
run_raw_completion_chunked, run_raw_completion_chunked_cancellable,
};
pub(crate) use crate::token_sink::TokenSink;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StopReason {
EndOfTurn,
ToolCalls,
Eos,
StopString,
MaxTokens,
Cancelled,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RawDecodeProgress {
Prefill {
done: usize,
total: usize,
},
Token {
index: usize,
id: TokenId,
delta: String,
},
Tail(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RawDecodeResult {
pub prompt_tokens: usize,
pub new_tokens: usize,
pub prefill_seconds: f64,
pub decode_seconds: f64,
pub reason: StopReason,
pub kv_position: usize,
pub kv_backed_token_ids: Vec<TokenId>,
pub reused_prefix_tokens: usize,
pub session_slot_evicted: bool,
pub peak_memory_pressure: MemoryPressure,
}
pub(crate) fn check_admission(
prompt_ids: &[TokenId],
config: &GenerationConfig,
max_context: u32,
) -> Result<(), RuntimeError> {
if prompt_ids.is_empty() {
return Err(RuntimeError::EmptyPrompt);
}
if config.max_new_tokens == 0 {
return Err(RuntimeError::ZeroBudget);
}
let required = prompt_ids.len() as u64 + config.max_new_tokens as u64;
if required > max_context as u64 {
return Err(RuntimeError::ContextOverflow {
prompt: prompt_ids.len(),
max_new: config.max_new_tokens,
max_context,
});
}
Ok(())
}
pub(crate) fn cancelled_during_prefill(
history: Vec<TokenId>,
position: usize,
prompt_tokens: usize,
prefill_start: Instant,
reused: usize,
session_slot_evicted: bool,
) -> RawDecodeResult {
RawDecodeResult {
prompt_tokens,
new_tokens: 0,
prefill_seconds: prefill_start.elapsed().as_secs_f64(),
decode_seconds: 0.0,
reason: StopReason::Cancelled,
kv_position: position,
kv_backed_token_ids: history,
reused_prefix_tokens: reused,
session_slot_evicted,
peak_memory_pressure: MemoryPressure::Normal,
}
}
pub type CancelFlag<'a> = &'a dyn Fn() -> bool;
pub(crate) const NEVER: &dyn Fn() -> bool = &|| false;
#[allow(clippy::too_many_arguments)]
pub fn run_raw_completion(
producer: &mut dyn LogitProducer,
tokenizer: &MfTokenizer,
prompt_ids: &[TokenId],
config: &GenerationConfig,
max_context: u32,
vocab_size: usize,
on_progress: impl FnMut(RawDecodeProgress),
) -> Result<RawDecodeResult, RuntimeError> {
run_raw_completion_cancellable(
producer,
tokenizer,
prompt_ids,
config,
max_context,
vocab_size,
NEVER,
on_progress,
)
}
#[allow(clippy::too_many_arguments)]
pub fn run_raw_completion_cancellable(
producer: &mut dyn LogitProducer,
tokenizer: &MfTokenizer,
prompt_ids: &[TokenId],
config: &GenerationConfig,
max_context: u32,
vocab_size: usize,
cancel: CancelFlag<'_>,
mut on_progress: impl FnMut(RawDecodeProgress),
) -> Result<RawDecodeResult, RuntimeError> {
check_admission(prompt_ids, config, max_context)?;
let reused = producer
.try_reuse_prefix(prompt_ids)
.min(prompt_ids.len() - 1);
if reused == 0 {
producer.reset();
}
let mut history: Vec<TokenId> =
Vec::with_capacity(prompt_ids.len() + config.max_new_tokens as usize);
let mut logits = vec![LogitValue::from_f32(0.0); vocab_size];
let prefill_start = Instant::now();
let mut position = reused;
history.extend_from_slice(&prompt_ids[..reused]);
let last = prompt_ids.len() - 1;
for (i, &token) in prompt_ids.iter().enumerate().skip(reused) {
if i == last {
producer.produce(token, position, &mut logits)
} else {
producer.produce_prefill(token, position, &mut logits)
}
.map_err(RuntimeError::Producer)?;
position += 1;
history.push(token);
on_progress(RawDecodeProgress::Prefill {
done: position,
total: prompt_ids.len(),
});
if cancel() {
return Ok(cancelled_during_prefill(
history,
position,
prompt_ids.len(),
prefill_start,
reused,
producer.session_slot_evicted(),
));
}
}
let prefill_seconds = prefill_start.elapsed().as_secs_f64();
let mut result = decode(
producer,
tokenizer,
config,
&mut logits,
history,
position,
prompt_ids.len(),
prefill_seconds,
cancel,
on_progress,
)?;
result.reused_prefix_tokens = reused;
result.session_slot_evicted = producer.session_slot_evicted();
Ok(result)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn decode<P: LogitProducer + ?Sized>(
producer: &mut P,
tokenizer: &MfTokenizer,
config: &GenerationConfig,
logits: &mut [LogitValue],
history: Vec<TokenId>,
mut position: usize,
prompt_tokens: usize,
prefill_seconds: f64,
cancel: CancelFlag<'_>,
mut on_progress: impl FnMut(RawDecodeProgress),
) -> Result<RawDecodeResult, RuntimeError> {
let decode_start = Instant::now();
let mut sink = TokenSink::new(tokenizer, config, history);
let reason;
let mut peak_memory_pressure = MemoryPressure::Normal;
let mut pacer = config.rate.is_active().then(|| {
let level = config
.rate
.thermal_probe
.map_or(ThermalLevel::Nominal, |probe| probe());
let memory = config
.rate
.memory_probe
.map_or(MemoryPressure::Normal, |probe| probe());
peak_memory_pressure = peak_memory_pressure.max(memory);
Pacer::new(
stepped_cap(config.rate.max_tokens_per_sec, level, memory),
decode_start,
)
});
loop {
let token_id = select(
LogitsView::new(logits),
&config.shaping,
&sink.history,
sink.generated as u64,
)?;
if let Some(stop) = sink.commit(token_id, cancel, &mut on_progress) {
reason = stop;
break;
}
if let Some(pacer) = pacer.as_mut() {
pacer.note_token();
let watching =
config.rate.thermal_probe.is_some() || config.rate.memory_probe.is_some();
if watching && sink.generated % THERMAL_POLL_TOKENS == 0 {
let level = config
.rate
.thermal_probe
.map_or(ThermalLevel::Nominal, |probe| probe());
let memory = config
.rate
.memory_probe
.map_or(MemoryPressure::Normal, |probe| probe());
peak_memory_pressure = peak_memory_pressure.max(memory);
let cap = stepped_cap(config.rate.max_tokens_per_sec, level, memory);
pacer.apply_cap(cap, Instant::now());
}
if let Some(wait) = pacer.due_in(Instant::now()) {
std::thread::sleep(wait);
}
}
producer
.produce(token_id, position, logits)
.map_err(RuntimeError::Producer)?;
position += 1;
}
Ok(RawDecodeResult {
reused_prefix_tokens: 0,
session_slot_evicted: false,
prompt_tokens,
new_tokens: sink.generated,
prefill_seconds,
decode_seconds: decode_start.elapsed().as_secs_f64(),
reason,
kv_position: position,
kv_backed_token_ids: sink.history,
peak_memory_pressure,
})
}