use crate::common::protocols::PrefillCost;
use crate::common::protocols::SchedulingPolicy;
use crate::common::sequence::ActiveSequence;
use crate::kv_manager::KvManager;
#[derive(Debug)]
pub(super) enum AdmissionDecision {
Admit { prefill_cost: PrefillCost },
Wait,
Reject,
}
pub(super) fn decide_waiting_admission<'a>(
policy: SchedulingPolicy,
sequence: &ActiveSequence,
is_fresh: bool,
running: impl Iterator<Item = &'a ActiveSequence>,
num_gpu_blocks: usize,
block_size: usize,
kv_manager: &KvManager,
) -> AdmissionDecision {
if is_fresh {
match policy {
SchedulingPolicy::Vllm => {
if sequence.current_known_blocks() > num_gpu_blocks {
return AdmissionDecision::Reject;
}
}
SchedulingPolicy::TrtllmGuaranteedNoEvict => {
if sequence.to_completion_blocks() > num_gpu_blocks {
return AdmissionDecision::Reject;
}
}
}
}
let prefill_cost = kv_manager.get_prefill_cost(sequence);
let available = match policy {
SchedulingPolicy::Vllm => num_gpu_blocks.saturating_sub(kv_manager.num_active_blocks()),
SchedulingPolicy::TrtllmGuaranteedNoEvict => {
available_blocks(running, num_gpu_blocks, block_size, kv_manager)
}
};
let needed = match policy {
SchedulingPolicy::Vllm => sequence
.current_known_blocks()
.saturating_sub(prefill_cost.active_cached_tokens / block_size),
SchedulingPolicy::TrtllmGuaranteedNoEvict => {
blocks_needed_to_finish(sequence, block_size, kv_manager, Some(&prefill_cost))
}
};
if needed > available {
AdmissionDecision::Wait
} else {
AdmissionDecision::Admit { prefill_cost }
}
}
fn blocks_needed_to_finish(
sequence: &ActiveSequence,
block_size: usize,
kv_manager: &KvManager,
prefill_cost: Option<&PrefillCost>,
) -> usize {
let full_blocks = sequence.to_completion_blocks();
if sequence.num_allocated_tokens() == 0 {
let reusable_blocks = prefill_cost
.map(|cost| cost.active_cached_tokens)
.unwrap_or_else(|| kv_manager.get_prefill_cost(sequence).active_cached_tokens)
/ block_size;
full_blocks.saturating_sub(reusable_blocks)
} else {
let allocated_blocks = sequence.num_allocated_tokens().div_ceil(block_size);
full_blocks.saturating_sub(allocated_blocks)
}
}
fn available_blocks<'a>(
running: impl Iterator<Item = &'a ActiveSequence>,
num_gpu_blocks: usize,
block_size: usize,
kv_manager: &KvManager,
) -> usize {
let reserved: usize = running
.map(|sequence| blocks_needed_to_finish(sequence, block_size, kv_manager, None))
.sum();
let free = num_gpu_blocks.saturating_sub(kv_manager.num_active_blocks());
free.saturating_sub(reserved)
}
pub(super) fn allows_preemption(policy: SchedulingPolicy) -> bool {
policy == SchedulingPolicy::Vllm
}
pub(super) fn supports_destination_reservation(policy: SchedulingPolicy) -> bool {
policy == SchedulingPolicy::Vllm
}
pub(super) fn normalize_max_output_tokens(
policy: SchedulingPolicy,
prompt_len: usize,
max_output_tokens: usize,
num_gpu_blocks: usize,
block_size: usize,
) -> Option<usize> {
if policy == SchedulingPolicy::Vllm {
return Some(max_output_tokens);
}
let capacity_tokens = num_gpu_blocks.saturating_mul(block_size);
if prompt_len >= capacity_tokens {
return None;
}
Some(max_output_tokens.min(capacity_tokens - prompt_len))
}
pub(super) fn report_no_preemption_violation() {
debug_assert!(
false,
"no-evict invariant violated: trtllm GUARANTEED_NO_EVICT required preemption"
);
tracing::error!(
"trtllm GUARANTEED_NO_EVICT required preemption; reservation under-counted physical KV demand"
);
}
#[cfg(test)]
mod tests;