use serde::{Deserialize, Serialize};
pub const CUDA_NATIVE_ADAPTIVE_V1_MAX_SEQUENCE_TOKENS: u64 = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionResourceAuthority {
LegacyEngine,
PlanRuntime,
}
impl Default for ExecutionResourceAuthority {
fn default() -> Self {
Self::LegacyEngine
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AttentionExecutionPolicy {
Auto,
Portable,
NativeAdaptive,
}
impl AttentionExecutionPolicy {
pub const fn as_runtime_value(self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Portable => "portable",
Self::NativeAdaptive => "native-adaptive",
}
}
pub fn parse_runtime_value(raw: &str) -> Result<Self, String> {
match raw.trim().to_ascii_lowercase().replace('_', "-").as_str() {
"auto" => Ok(Self::Auto),
"portable" => Ok(Self::Portable),
"native-adaptive" => Ok(Self::NativeAdaptive),
_ => Err(format!(
"expected auto, portable, or native-adaptive; got {raw:?}"
)),
}
}
pub fn resolve(self, native_adaptive_supported: bool) -> Result<Self, String> {
match self {
Self::Auto if native_adaptive_supported => Ok(Self::NativeAdaptive),
Self::Auto => Ok(Self::Portable),
Self::NativeAdaptive if !native_adaptive_supported => Err(
"native-adaptive attention was requested but the selected backend composition does not provide it"
.to_owned(),
),
resolved => Ok(resolved),
}
}
pub const fn is_resolved(self) -> bool {
!matches!(self, Self::Auto)
}
}
impl Default for AttentionExecutionPolicy {
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutorAdmissionLimits {
maximum_active_sequences: u32,
maximum_scheduled_tokens: u64,
}
impl ExecutorAdmissionLimits {
pub fn new(
maximum_active_sequences: u32,
maximum_scheduled_tokens: u64,
) -> Result<Self, String> {
if maximum_active_sequences == 0 {
return Err("maximum_active_sequences must be non-zero".to_owned());
}
if maximum_scheduled_tokens == 0 {
return Err("maximum_scheduled_tokens must be non-zero".to_owned());
}
Ok(Self {
maximum_active_sequences,
maximum_scheduled_tokens,
})
}
pub const fn maximum_active_sequences(self) -> u32 {
self.maximum_active_sequences
}
pub const fn maximum_scheduled_tokens(self) -> u64 {
self.maximum_scheduled_tokens
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutorAdmissionSnapshot {
schema_version: u32,
resource_authority: ExecutionResourceAuthority,
#[serde(flatten)]
limits: ExecutorAdmissionLimits,
waiting_requests: u32,
active_sequences: u32,
active_prefill_sequences: u32,
active_decode_sequences: u32,
current_batch_size: Option<u32>,
capacity_blocked_requests: Option<u32>,
}
impl ExecutorAdmissionSnapshot {
pub fn new(
resource_authority: ExecutionResourceAuthority,
limits: ExecutorAdmissionLimits,
waiting_requests: u32,
active_prefill_sequences: u32,
active_decode_sequences: u32,
current_batch_size: Option<u32>,
capacity_blocked_requests: Option<u32>,
) -> Result<Self, String> {
let active_sequences = active_prefill_sequences
.checked_add(active_decode_sequences)
.ok_or_else(|| "active sequence phase counts overflow u32".to_owned())?;
if active_sequences > limits.maximum_active_sequences {
return Err(format!(
"active_sequences {active_sequences} exceeds maximum_active_sequences {}",
limits.maximum_active_sequences
));
}
if current_batch_size.is_some_and(|batch| batch > active_sequences) {
return Err(format!(
"current_batch_size {} exceeds active_sequences {active_sequences}",
current_batch_size.unwrap_or_default()
));
}
let observed_requests = waiting_requests
.checked_add(active_sequences)
.ok_or_else(|| "observed admission request counts overflow u32".to_owned())?;
if capacity_blocked_requests.is_some_and(|blocked| blocked > observed_requests) {
return Err(format!(
"capacity_blocked_requests {} exceeds observed waiting plus active requests {observed_requests}",
capacity_blocked_requests.unwrap_or_default()
));
}
Ok(Self {
schema_version: 2,
resource_authority,
limits,
waiting_requests,
active_sequences,
active_prefill_sequences,
active_decode_sequences,
current_batch_size,
capacity_blocked_requests,
})
}
pub const fn resource_authority(&self) -> ExecutionResourceAuthority {
self.resource_authority
}
pub const fn maximum_active_sequences(&self) -> u32 {
self.limits.maximum_active_sequences
}
pub const fn maximum_scheduled_tokens(&self) -> u64 {
self.limits.maximum_scheduled_tokens
}
pub const fn active_sequences(&self) -> u32 {
self.active_sequences
}
pub const fn waiting_requests(&self) -> u32 {
self.waiting_requests
}
pub const fn active_prefill_sequences(&self) -> u32 {
self.active_prefill_sequences
}
pub const fn active_decode_sequences(&self) -> u32 {
self.active_decode_sequences
}
pub const fn current_batch_size(&self) -> Option<u32> {
self.current_batch_size
}
pub const fn capacity_blocked_requests(&self) -> Option<u32> {
self.capacity_blocked_requests
}
}
#[cfg(test)]
mod tests {
use super::{
AttentionExecutionPolicy, ExecutionResourceAuthority, ExecutorAdmissionLimits,
ExecutorAdmissionSnapshot,
};
#[test]
fn attention_policy_resolves_auto_without_hiding_native_requirements() {
assert_eq!(
AttentionExecutionPolicy::Auto.resolve(true).unwrap(),
AttentionExecutionPolicy::NativeAdaptive
);
assert_eq!(
AttentionExecutionPolicy::Auto.resolve(false).unwrap(),
AttentionExecutionPolicy::Portable
);
assert!(AttentionExecutionPolicy::NativeAdaptive
.resolve(false)
.is_err());
}
#[test]
fn admission_snapshot_rejects_runtime_over_admission_ceiling() {
let limits = ExecutorAdmissionLimits::new(16, 2048).unwrap();
assert!(ExecutorAdmissionSnapshot::new(
ExecutionResourceAuthority::PlanRuntime,
limits,
0,
8,
9,
None,
None,
)
.is_err());
}
#[test]
fn admission_snapshot_reconciles_single_source_phase_counts() {
let snapshot = ExecutorAdmissionSnapshot::new(
ExecutionResourceAuthority::PlanRuntime,
ExecutorAdmissionLimits::new(32, 4096).unwrap(),
3,
7,
11,
Some(8),
Some(2),
)
.unwrap();
assert_eq!(snapshot.active_sequences(), 18);
assert_eq!(snapshot.active_prefill_sequences(), 7);
assert_eq!(snapshot.active_decode_sequences(), 11);
assert_eq!(snapshot.current_batch_size(), Some(8));
}
}