use std::env::{self, VarError};
use std::fmt;
use std::str::FromStr;
use std::sync::OnceLock;
use std::time::Duration;
use derive_builder::Builder;
use rand::Rng;
use serde::{Deserialize, Serialize};
use validator::{Validate, ValidationError};
use crate::protocols::{
BlockHashOptions, LocalBlockHash, compute_block_hash_for_seq, compute_seq_hash_for_block,
};
const fn default_track_prefill_tokens() -> bool {
true
}
pub const DYN_ROUTER_MIN_INITIAL_WORKERS: &str = "DYN_ROUTER_MIN_INITIAL_WORKERS";
pub fn min_initial_workers_from_env() -> anyhow::Result<usize> {
match env::var(DYN_ROUTER_MIN_INITIAL_WORKERS) {
Ok(value) => value.parse::<usize>().map_err(|error| {
anyhow::anyhow!(
"{DYN_ROUTER_MIN_INITIAL_WORKERS} must be a non-negative integer, got {value:?}: {error}"
)
}),
Err(VarError::NotPresent) => Ok(0),
Err(VarError::NotUnicode(_)) => {
anyhow::bail!("{DYN_ROUTER_MIN_INITIAL_WORKERS} must be valid unicode")
}
}
}
const fn default_host_cache_hit_weight() -> f64 {
0.75
}
const fn default_disk_cache_hit_weight() -> f64 {
0.25
}
const fn default_prefill_load_scale() -> f64 {
1.0
}
const fn default_overlap_score_credit_decay() -> f64 {
0.0
}
pub const OVERLAP_SCORE_CREDIT_RANGE_ERROR: &str =
"overlap_score_credit must be between 0.0 and 1.0";
pub const OVERLAP_SCORE_CREDIT_MIGRATION_ERROR: &str = concat!(
"overlap_score_credit must be between 0.0 and 1.0; values above 1.0 are probably not what ",
"you intended. If you want to weigh TTFT/prompt-side prefill load more heavily, keep ",
"overlap_score_credit <= 1.0 and use that larger value for prefill_load_scale instead; ",
"prefill_load_scale is applied after overlap credits."
);
pub fn overlap_score_credit_error_message(value: f64) -> Option<&'static str> {
if (0.0..=1.0).contains(&value) {
None
} else if value > 1.0 {
Some(OVERLAP_SCORE_CREDIT_MIGRATION_ERROR)
} else {
Some(OVERLAP_SCORE_CREDIT_RANGE_ERROR)
}
}
fn validate_overlap_score_credit(value: f64) -> Result<(), ValidationError> {
let Some(message) = overlap_score_credit_error_message(value) else {
return Ok(());
};
let mut error = ValidationError::new("overlap_score_credit_out_of_range");
error.message = Some(message.into());
Err(error)
}
pub fn apply_deprecated_overlap_score_weight_override(
value: f64,
overlap_score_credit: &mut f64,
prefill_load_scale: &mut f64,
) {
*prefill_load_scale = value;
if value == 0.0 {
*overlap_score_credit = 0.0;
}
}
pub fn kv_router_config_from_dynamo_env() -> KvRouterConfig {
let config = kv_router_config_from_lookup(|key| env::var(key).ok());
tracing::info!(
overlap_score_credit = config.overlap_score_credit,
overlap_score_credit_decay = config.overlap_score_credit_decay,
prefill_load_scale = config.prefill_load_scale,
router_temperature = config.router_temperature,
use_kv_events = config.use_kv_events,
router_replica_sync = config.router_replica_sync,
router_track_active_blocks = config.router_track_active_blocks,
router_track_output_blocks = config.router_track_output_blocks,
router_track_prefill_tokens = config.router_track_prefill_tokens,
router_queue_threshold = ?config.router_queue_threshold,
router_policy_config = ?config.router_policy_config,
router_predicted_ttl_secs = ?config.router_predicted_ttl_secs,
"KvRouterConfig initialized (DYN_* env overrides applied)"
);
config
}
fn kv_router_config_from_lookup(get_env: impl Fn(&str) -> Option<String>) -> KvRouterConfig {
fn parse_f64(get_env: &impl Fn(&str) -> Option<String>, key: &str) -> Option<f64> {
get_env(key).and_then(|value| value.parse().ok())
}
fn parse_bool(get_env: &impl Fn(&str) -> Option<String>, key: &str) -> Option<bool> {
get_env(key).and_then(|value| match value.to_ascii_lowercase().as_str() {
"true" | "1" | "yes" | "on" => Some(true),
"false" | "0" | "no" | "off" => Some(false),
_ => None,
})
}
let mut config = KvRouterConfig::default();
if let Some(value) = parse_f64(&get_env, "DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT") {
config.overlap_score_credit = value;
}
if let Some(value) = parse_f64(&get_env, "DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT_DECAY") {
config.overlap_score_credit_decay = value;
}
if let Some(value) = parse_f64(&get_env, "DYN_ROUTER_PREFILL_LOAD_SCALE") {
config.prefill_load_scale = value;
}
for key in [
"DYN_ROUTER_KV_OVERLAP_SCORE_WEIGHT",
"DYN_OVERLAP_SCORE_WEIGHT",
] {
if let Some(value) = parse_f64(&get_env, key) {
tracing::warn!("{key} is deprecated; use DYN_ROUTER_PREFILL_LOAD_SCALE");
apply_deprecated_overlap_score_weight_override(
value,
&mut config.overlap_score_credit,
&mut config.prefill_load_scale,
);
break;
}
}
if let Some(value) = parse_f64(&get_env, "DYN_ROUTER_TEMPERATURE") {
config.router_temperature = value;
}
if let Some(value) = parse_bool(&get_env, "DYN_USE_KV_EVENTS") {
config.use_kv_events = value;
}
if let Some(value) = parse_bool(&get_env, "DYN_ROUTER_REPLICA_SYNC") {
config.router_replica_sync = value;
}
if let Some(value) = parse_bool(&get_env, "DYN_ROUTER_TRACK_ACTIVE_BLOCKS") {
config.router_track_active_blocks = value;
}
if let Some(value) = parse_bool(&get_env, "DYN_ROUTER_TRACK_OUTPUT_BLOCKS") {
config.router_track_output_blocks = value;
}
if let Some(value) = parse_bool(&get_env, "DYN_ROUTER_TRACK_PREFILL_TOKENS") {
config.router_track_prefill_tokens = value;
}
if let Some(value) = parse_f64(&get_env, "DYN_ROUTER_QUEUE_THRESHOLD") {
config.router_queue_threshold = Some(value);
}
if let Some(value) = get_env("DYN_ROUTER_POLICY_CONFIG") {
config.router_policy_config = Some(value);
}
if let Some(value) = parse_f64(&get_env, "DYN_ROUTER_PREDICTED_TTL_SECS") {
config.router_predicted_ttl_secs = Some(value);
}
config
}
fn apply_deprecated_overlap_score_weight_override_option(
value: f64,
overlap_score_credit: &mut Option<f64>,
prefill_load_scale: &mut Option<f64>,
) {
*prefill_load_scale = Some(value);
if value == 0.0 {
*overlap_score_credit = Some(0.0);
}
}
fn validate_and_return<T: Validate>(config: T) -> Result<T, String> {
config.validate().map_err(|error| error.to_string())?;
Ok(config)
}
fn validate_router_config_override(config: &RouterConfigOverride) -> Result<(), ValidationError> {
if let Some(credit) = config.overlap_score_credit {
validate_overlap_score_credit(credit)?;
}
Ok(())
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SharedCacheType {
#[default]
None,
Hicache,
}
impl fmt::Display for SharedCacheType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => f.write_str("none"),
Self::Hicache => f.write_str("hicache"),
}
}
}
impl FromStr for SharedCacheType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"none" => Ok(Self::None),
"hicache" => Ok(Self::Hicache),
_ => Err(format!(
"unknown shared_cache_type: {s:?}, expected 'none' or 'hicache'"
)),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RouterQueuePolicy {
#[default]
Fcfs,
Lcfs,
Wspt,
}
impl fmt::Display for RouterQueuePolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fcfs => f.write_str("fcfs"),
Self::Lcfs => f.write_str("lcfs"),
Self::Wspt => f.write_str("wspt"),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RouterPrefillLoadModel {
#[default]
None,
Aic,
}
impl fmt::Display for RouterPrefillLoadModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => f.write_str("none"),
Self::Aic => f.write_str("aic"),
}
}
}
impl FromStr for RouterPrefillLoadModel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"none" => Ok(Self::None),
"aic" => Ok(Self::Aic),
_ => Err(format!(
"unknown prefill load model: {s:?}, expected 'none' or 'aic'"
)),
}
}
}
impl RouterPrefillLoadModel {
pub fn is_enabled(self) -> bool {
!matches!(self, Self::None)
}
}
impl FromStr for RouterQueuePolicy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"fcfs" => Ok(Self::Fcfs),
"lcfs" => Ok(Self::Lcfs),
"wspt" => Ok(Self::Wspt),
_ => Err(format!(
"unknown queue policy: {s:?}, expected 'fcfs', 'lcfs', or 'wspt'"
)),
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
struct RouterConfigOverrideSerde {
overlap_score_credit: Option<f64>,
prefill_load_scale: Option<f64>,
overlap_score_weight: Option<f64>,
router_temperature: Option<f64>,
assume_kv_reuse: Option<bool>,
track_prefill_tokens: Option<bool>,
shared_cache_multiplier: Option<f64>,
}
#[derive(Debug, Clone, Default, Builder, Serialize, Deserialize, Validate)]
#[serde(try_from = "RouterConfigOverrideSerde")]
#[validate(schema(function = "validate_router_config_override"))]
pub struct RouterConfigOverride {
#[builder(default)]
pub overlap_score_credit: Option<f64>,
#[builder(default)]
#[validate(range(min = 0.0))]
pub prefill_load_scale: Option<f64>,
#[builder(default)]
#[validate(range(min = 0.0))]
pub router_temperature: Option<f64>,
#[builder(default)]
pub assume_kv_reuse: Option<bool>,
#[builder(default)]
pub track_prefill_tokens: Option<bool>,
#[builder(default)]
#[validate(range(min = 0.0, max = 1.0))]
pub shared_cache_multiplier: Option<f64>,
}
impl TryFrom<RouterConfigOverrideSerde> for RouterConfigOverride {
type Error = String;
fn try_from(compat: RouterConfigOverrideSerde) -> Result<Self, Self::Error> {
let mut overlap_score_credit = compat.overlap_score_credit;
let mut prefill_load_scale = compat.prefill_load_scale;
if let Some(overlap_score_weight) = compat.overlap_score_weight {
apply_deprecated_overlap_score_weight_override_option(
overlap_score_weight,
&mut overlap_score_credit,
&mut prefill_load_scale,
);
}
validate_and_return(Self {
overlap_score_credit,
prefill_load_scale,
router_temperature: compat.router_temperature,
assume_kv_reuse: compat.assume_kv_reuse,
track_prefill_tokens: compat.track_prefill_tokens,
shared_cache_multiplier: compat.shared_cache_multiplier,
})
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct KvRouterConfigSerde {
overlap_score_credit: f64,
overlap_score_credit_decay: f64,
prefill_load_scale: f64,
overlap_score_weight: Option<f64>,
host_cache_hit_weight: f64,
disk_cache_hit_weight: f64,
router_temperature: f64,
use_kv_events: bool,
durable_kv_events: bool,
router_replica_sync: bool,
router_track_active_blocks: bool,
router_track_output_blocks: bool,
router_assume_kv_reuse: bool,
router_track_prefill_tokens: bool,
router_prefill_load_model: RouterPrefillLoadModel,
router_snapshot_threshold: Option<u32>,
router_reset_states: bool,
router_ttl_secs: f64,
router_queue_threshold: Option<f64>,
#[serde(default)]
router_policy_config: Option<String>,
router_event_threads: u32,
skip_initial_worker_wait: bool,
router_queue_policy: RouterQueuePolicy,
use_remote_indexer: bool,
serve_indexer: bool,
shared_cache_multiplier: f64,
shared_cache_type: SharedCacheType,
router_predicted_ttl_secs: Option<f64>,
}
impl Default for KvRouterConfigSerde {
fn default() -> Self {
let config = KvRouterConfig::default();
Self {
overlap_score_credit: config.overlap_score_credit,
overlap_score_credit_decay: config.overlap_score_credit_decay,
prefill_load_scale: config.prefill_load_scale,
overlap_score_weight: None,
host_cache_hit_weight: config.host_cache_hit_weight,
disk_cache_hit_weight: config.disk_cache_hit_weight,
router_temperature: config.router_temperature,
use_kv_events: config.use_kv_events,
durable_kv_events: config.durable_kv_events,
router_replica_sync: config.router_replica_sync,
router_track_active_blocks: config.router_track_active_blocks,
router_track_output_blocks: config.router_track_output_blocks,
router_assume_kv_reuse: config.router_assume_kv_reuse,
router_track_prefill_tokens: config.router_track_prefill_tokens,
router_prefill_load_model: config.router_prefill_load_model,
router_snapshot_threshold: config.router_snapshot_threshold,
router_reset_states: config.router_reset_states,
router_ttl_secs: config.router_ttl_secs,
router_queue_threshold: config.router_queue_threshold,
router_policy_config: config.router_policy_config,
router_event_threads: config.router_event_threads,
skip_initial_worker_wait: config.skip_initial_worker_wait,
router_queue_policy: config.router_queue_policy,
use_remote_indexer: config.use_remote_indexer,
serve_indexer: config.serve_indexer,
shared_cache_multiplier: config.shared_cache_multiplier,
shared_cache_type: config.shared_cache_type,
router_predicted_ttl_secs: config.router_predicted_ttl_secs,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[serde(try_from = "KvRouterConfigSerde")]
#[validate(schema(function = "validate_kv_router_config"))]
pub struct KvRouterConfig {
#[validate(custom(function = "validate_overlap_score_credit"))]
pub overlap_score_credit: f64,
#[serde(default = "default_overlap_score_credit_decay")]
#[validate(range(min = 0.0))]
pub overlap_score_credit_decay: f64,
#[validate(range(min = 0.0))]
pub prefill_load_scale: f64,
#[serde(default = "default_host_cache_hit_weight")]
#[validate(range(min = 0.0, max = 1.0))]
pub host_cache_hit_weight: f64,
#[serde(default = "default_disk_cache_hit_weight")]
#[validate(range(min = 0.0, max = 1.0))]
pub disk_cache_hit_weight: f64,
#[validate(range(min = 0.0))]
pub router_temperature: f64,
pub use_kv_events: bool,
pub durable_kv_events: bool,
pub router_replica_sync: bool,
pub router_track_active_blocks: bool,
pub router_track_output_blocks: bool,
pub router_assume_kv_reuse: bool,
#[serde(default = "default_track_prefill_tokens")]
pub router_track_prefill_tokens: bool,
pub router_prefill_load_model: RouterPrefillLoadModel,
#[validate(range(min = 1))]
pub router_snapshot_threshold: Option<u32>,
pub router_reset_states: bool,
#[validate(range(min = 0.0))]
pub router_ttl_secs: f64,
#[validate(range(min = 0.0))]
pub router_queue_threshold: Option<f64>,
#[serde(default)]
pub router_policy_config: Option<String>,
#[serde(skip)]
#[doc(hidden)]
pub policy_model_name: Option<String>,
#[serde(skip)]
#[doc(hidden)]
pub policy_config_cache: OnceLock<super::policy_config::RouterPolicyConfig>,
#[validate(range(min = 1))]
pub router_event_threads: u32,
pub skip_initial_worker_wait: bool,
pub router_queue_policy: RouterQueuePolicy,
#[serde(default)]
pub use_remote_indexer: bool,
#[serde(default)]
pub serve_indexer: bool,
#[validate(range(min = 0.0, max = 1.0))]
pub shared_cache_multiplier: f64,
pub shared_cache_type: SharedCacheType,
#[serde(default)]
#[validate(range(min = 0.0))]
pub router_predicted_ttl_secs: Option<f64>,
}
impl Default for KvRouterConfig {
fn default() -> Self {
Self {
overlap_score_credit: 1.0,
overlap_score_credit_decay: default_overlap_score_credit_decay(),
prefill_load_scale: default_prefill_load_scale(),
host_cache_hit_weight: default_host_cache_hit_weight(),
disk_cache_hit_weight: default_disk_cache_hit_weight(),
router_temperature: 0.0,
use_kv_events: true,
durable_kv_events: false, router_replica_sync: false,
router_track_active_blocks: true,
router_track_output_blocks: false,
router_assume_kv_reuse: true,
router_track_prefill_tokens: default_track_prefill_tokens(),
router_prefill_load_model: RouterPrefillLoadModel::default(),
router_snapshot_threshold: Some(1000000),
router_reset_states: false,
router_ttl_secs: 120.0,
router_queue_threshold: Some(16.0),
router_policy_config: None,
policy_model_name: None,
policy_config_cache: OnceLock::new(),
router_event_threads: 4,
skip_initial_worker_wait: false,
router_queue_policy: RouterQueuePolicy::default(),
use_remote_indexer: false,
serve_indexer: false,
shared_cache_multiplier: 0.0,
shared_cache_type: SharedCacheType::default(),
router_predicted_ttl_secs: None,
}
}
}
impl TryFrom<KvRouterConfigSerde> for KvRouterConfig {
type Error = String;
fn try_from(compat: KvRouterConfigSerde) -> Result<Self, Self::Error> {
let mut overlap_score_credit = compat.overlap_score_credit;
let mut prefill_load_scale = compat.prefill_load_scale;
if let Some(overlap_score_weight) = compat.overlap_score_weight {
apply_deprecated_overlap_score_weight_override(
overlap_score_weight,
&mut overlap_score_credit,
&mut prefill_load_scale,
);
}
validate_and_return(Self {
overlap_score_credit,
overlap_score_credit_decay: compat.overlap_score_credit_decay,
prefill_load_scale,
host_cache_hit_weight: compat.host_cache_hit_weight,
disk_cache_hit_weight: compat.disk_cache_hit_weight,
router_temperature: compat.router_temperature,
use_kv_events: compat.use_kv_events,
durable_kv_events: compat.durable_kv_events,
router_replica_sync: compat.router_replica_sync,
router_track_active_blocks: compat.router_track_active_blocks,
router_track_output_blocks: compat.router_track_output_blocks,
router_assume_kv_reuse: compat.router_assume_kv_reuse,
router_track_prefill_tokens: compat.router_track_prefill_tokens,
router_prefill_load_model: compat.router_prefill_load_model,
router_snapshot_threshold: compat.router_snapshot_threshold,
router_reset_states: compat.router_reset_states,
router_ttl_secs: compat.router_ttl_secs,
router_queue_threshold: compat.router_queue_threshold,
router_policy_config: compat.router_policy_config,
policy_model_name: None,
policy_config_cache: OnceLock::new(),
router_event_threads: compat.router_event_threads,
skip_initial_worker_wait: compat.skip_initial_worker_wait,
router_queue_policy: compat.router_queue_policy,
use_remote_indexer: compat.use_remote_indexer,
serve_indexer: compat.serve_indexer,
shared_cache_multiplier: compat.shared_cache_multiplier,
shared_cache_type: compat.shared_cache_type,
router_predicted_ttl_secs: compat.router_predicted_ttl_secs,
})
}
}
fn validate_kv_router_config(config: &KvRouterConfig) -> Result<(), ValidationError> {
if config.durable_kv_events {
tracing::warn!(
"--durable-kv-events is deprecated and will be removed in a future release. \
The event-plane subscriber (local_indexer mode) is now the recommended path."
);
}
if config.durable_kv_events && !config.use_kv_events {
return Err(ValidationError::new(
"durable_kv_events requires use_kv_events=true",
));
}
if config.router_track_output_blocks && !config.router_track_active_blocks {
return Err(ValidationError::new(
"router_track_output_blocks requires router_track_active_blocks=true",
));
}
if config.router_prefill_load_model.is_enabled() && !config.router_track_prefill_tokens {
return Err(ValidationError::new(
"router_prefill_load_model requires router_track_prefill_tokens=true",
));
}
if config.use_remote_indexer && config.serve_indexer {
return Err(ValidationError::new(
"use_remote_indexer and serve_indexer are mutually exclusive",
));
}
if config.serve_indexer && config.overlap_score_credit == 0.0 {
return Err(ValidationError::new(
"serve_indexer requires overlap_score_credit > 0",
));
}
if config.router_predicted_ttl_secs.is_some() && !config.use_kv_events {
return Err(ValidationError::new(
"router_predicted_ttl_secs requires use_kv_events=true",
));
}
if let Err(error) = config.loaded_policy_config() {
let mut validation_error = ValidationError::new("router_policy_config");
validation_error.message = Some(error.to_string().into());
return Err(validation_error);
}
Ok(())
}
impl KvRouterConfig {
fn loaded_policy_config(
&self,
) -> Result<
Option<&super::policy_config::RouterPolicyConfig>,
super::policy_config::RouterPolicyConfigError,
> {
let Some(path) = self.router_policy_config.as_deref() else {
return Ok(None);
};
if self.policy_config_cache.get().is_none() {
let parsed = super::policy_config::RouterPolicyConfig::from_path(path)?;
let _ = self.policy_config_cache.set(parsed);
}
Ok(self.policy_config_cache.get())
}
pub fn policy_profile(
&self,
model_name: Option<&str>,
) -> Result<super::policy_config::PolicyProfile, super::policy_config::RouterPolicyConfigError>
{
let Some(policy_config) = self.loaded_policy_config()? else {
return Ok(super::policy_config::PolicyProfile::synthetic(
self.router_queue_threshold,
self.router_queue_policy,
));
};
Ok(policy_config.resolve_profile(
model_name,
self.router_queue_threshold,
self.router_queue_policy,
))
}
pub fn with_policy_model_name(mut self, model_name: Option<String>) -> Self {
self.policy_model_name = model_name;
self
}
pub fn configured_policy_profile(
&self,
) -> Result<super::policy_config::PolicyProfile, super::policy_config::RouterPolicyConfigError>
{
self.policy_profile(self.policy_model_name.as_deref())
}
pub fn validate_config(&self) -> Result<(), String> {
self.validate().map_err(|error| error.to_string())
}
pub fn router_queue_recheck_interval(&self) -> Duration {
const DEFAULT_RECHECK_INTERVAL: Duration = Duration::from_secs(60);
const PREFILL_LOAD_RECHECK_INTERVAL: Duration = Duration::from_millis(100);
if self.router_prefill_load_model.is_enabled()
&& (self.router_policy_config.is_some() || self.router_queue_threshold.is_some())
{
return PREFILL_LOAD_RECHECK_INTERVAL;
}
DEFAULT_RECHECK_INTERVAL
}
pub fn predict_on_route_enabled(&self) -> bool {
self.router_predicted_ttl_secs.is_some()
}
pub fn assume_kv_reuse(&self, config_override: Option<&RouterConfigOverride>) -> bool {
config_override
.and_then(|cfg| cfg.assume_kv_reuse)
.unwrap_or(self.router_assume_kv_reuse)
}
pub fn track_prefill_tokens(&self, config_override: Option<&RouterConfigOverride>) -> bool {
config_override
.and_then(|cfg| cfg.track_prefill_tokens)
.unwrap_or(self.router_track_prefill_tokens)
}
pub fn compute_seq_hashes_for_tracking(
&self,
tokens: &[u32],
block_size: u32,
config_override: Option<&RouterConfigOverride>,
hash_options: BlockHashOptions<'_>,
precomputed_block_hashes: Option<&[LocalBlockHash]>,
) -> Option<Vec<u64>> {
if !self.router_track_active_blocks {
return None;
}
let num_blocks = tokens.len() / block_size as usize;
if num_blocks == 0 {
return Some(Vec::new());
}
let assume_kv_reuse = self.assume_kv_reuse(config_override);
if assume_kv_reuse {
let block_hashes = match precomputed_block_hashes {
Some(block_hashes) => block_hashes,
None => {
let computed = compute_block_hash_for_seq(tokens, block_size, hash_options);
return Some(compute_seq_hash_for_block(&computed));
}
};
Some(compute_seq_hash_for_block(block_hashes))
} else {
let mut rng = rand::rng();
Some((0..num_blocks).map(|_| rng.random::<u64>()).collect())
}
}
pub fn should_subscribe_to_kv_events(&self) -> bool {
self.use_kv_events && self.overlap_score_credit > 0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocols::{BlockExtraInfo, BlockMmObjectInfo};
use std::collections::HashMap;
fn config_from_values(values: &[(&str, &str)]) -> KvRouterConfig {
let values: HashMap<&str, &str> = values.iter().copied().collect();
kv_router_config_from_lookup(|key| values.get(key).map(|value| (*value).to_string()))
}
#[test]
fn dynamo_env_config_parses_canonical_settings() {
let config = config_from_values(&[
("DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT", "0.25"),
("DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT_DECAY", "0.75"),
("DYN_ROUTER_PREFILL_LOAD_SCALE", "2.5"),
("DYN_ROUTER_TEMPERATURE", "0.7"),
("DYN_USE_KV_EVENTS", "false"),
("DYN_ROUTER_REPLICA_SYNC", "yes"),
("DYN_ROUTER_TRACK_ACTIVE_BLOCKS", "0"),
("DYN_ROUTER_TRACK_OUTPUT_BLOCKS", "on"),
("DYN_ROUTER_TRACK_PREFILL_TOKENS", "false"),
("DYN_ROUTER_QUEUE_THRESHOLD", "4.5"),
]);
assert_eq!(config.overlap_score_credit, 0.25);
assert_eq!(config.overlap_score_credit_decay, 0.75);
assert_eq!(config.prefill_load_scale, 2.5);
assert_eq!(config.router_temperature, 0.7);
assert!(!config.use_kv_events);
assert!(config.router_replica_sync);
assert!(!config.router_track_active_blocks);
assert!(config.router_track_output_blocks);
assert!(!config.router_track_prefill_tokens);
assert_eq!(config.router_queue_threshold, Some(4.5));
let predicted = config_from_values(&[("DYN_ROUTER_PREDICTED_TTL_SECS", "60")]);
assert_eq!(predicted.router_predicted_ttl_secs, Some(60.0));
assert!(predicted.validate_config().is_ok());
}
#[test]
fn dynamo_env_config_preserves_deprecated_alias_precedence() {
let config = config_from_values(&[
("DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT", "0.25"),
("DYN_ROUTER_PREFILL_LOAD_SCALE", "2"),
("DYN_ROUTER_KV_OVERLAP_SCORE_WEIGHT", "3"),
("DYN_OVERLAP_SCORE_WEIGHT", "4"),
]);
assert_eq!(config.overlap_score_credit, 0.25);
assert_eq!(config.prefill_load_scale, 3.0);
let disabled = config_from_values(&[
("DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT", "0.75"),
("DYN_ROUTER_KV_OVERLAP_SCORE_WEIGHT", "0"),
]);
assert_eq!(disabled.overlap_score_credit, 0.0);
assert_eq!(disabled.prefill_load_scale, 0.0);
}
#[test]
fn dynamo_env_config_ignores_unparseable_values_and_validates_ranges() {
let unparseable = config_from_values(&[
("DYN_ROUTER_TEMPERATURE", "warm"),
("DYN_ROUTER_TRACK_ACTIVE_BLOCKS", "sometimes"),
]);
let default = KvRouterConfig::default();
assert_eq!(unparseable.router_temperature, default.router_temperature);
assert_eq!(
unparseable.router_track_active_blocks,
default.router_track_active_blocks
);
let out_of_range = config_from_values(&[("DYN_ROUTER_KV_OVERLAP_SCORE_CREDIT", "1.5")]);
assert!(out_of_range.validate_config().is_err());
}
#[test]
fn compute_seq_hashes_for_tracking_uses_mm_hashes() {
let cfg = KvRouterConfig::default();
let tokens = vec![1, 2, 3, 4];
let mm_infos = vec![
Some(BlockExtraInfo {
mm_objects: vec![BlockMmObjectInfo {
mm_hash: 42,
offsets: vec![],
}],
}),
None,
];
let without_mm = cfg
.compute_seq_hashes_for_tracking(&tokens, 2, None, BlockHashOptions::default(), None)
.unwrap();
let with_mm = cfg
.compute_seq_hashes_for_tracking(
&tokens,
2,
None,
BlockHashOptions {
block_mm_infos: Some(&mm_infos),
..Default::default()
},
None,
)
.unwrap();
assert_ne!(without_mm, with_mm);
}
#[test]
fn compute_seq_hashes_for_tracking_uses_precomputed_block_hashes() {
let config = KvRouterConfig::default();
let tokens: Vec<u32> = (0..8).collect();
let precomputed = vec![LocalBlockHash(11), LocalBlockHash(29)];
let seq_hashes = config.compute_seq_hashes_for_tracking(
&tokens,
4,
None,
BlockHashOptions::default(),
Some(&precomputed),
);
assert_eq!(seq_hashes, Some(compute_seq_hash_for_block(&precomputed)));
}
#[test]
fn test_kv_router_config_rejects_out_of_range_shared_cache_multiplier() {
let too_small = KvRouterConfig {
shared_cache_multiplier: -0.1,
..Default::default()
};
let too_large = KvRouterConfig {
shared_cache_multiplier: 1.1,
..Default::default()
};
assert!(too_small.validate().is_err());
assert!(too_large.validate().is_err());
}
#[test]
fn test_kv_router_config_rejects_local_approx_with_predicted_ttl() {
let config = KvRouterConfig {
use_kv_events: false,
router_predicted_ttl_secs: Some(5.0),
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_kv_router_config_rejects_remote_approx_with_predicted_ttl() {
let config = KvRouterConfig {
use_kv_events: false,
use_remote_indexer: true,
router_predicted_ttl_secs: Some(5.0),
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_kv_router_config_allows_remote_events_with_predicted_ttl() {
let config = KvRouterConfig {
use_kv_events: true,
use_remote_indexer: true,
router_predicted_ttl_secs: Some(5.0),
..Default::default()
};
assert!(config.validate().is_ok());
}
#[test]
fn test_kv_router_config_allows_served_events_with_predicted_ttl() {
let config = KvRouterConfig {
use_kv_events: true,
serve_indexer: true,
router_predicted_ttl_secs: Some(5.0),
..Default::default()
};
assert!(config.validate().is_ok());
}
#[test]
fn test_kv_router_config_deserializes_policy_path() {
let policy_file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(
policy_file.path(),
"default_policy_family: default\nuncached_isl_buckets:\n - min_tokens: 0\n bucket: all\npolicy_classes:\n - name: default\n policy_family: default\n cache_bucket: all\n quantum: 1\n",
)
.unwrap();
let encoded = serde_json::json!({
"router_policy_config": policy_file.path(),
})
.to_string();
let config: KvRouterConfig = serde_json::from_str(&encoded).unwrap();
assert_eq!(
config.router_policy_config.as_deref(),
Some(policy_file.path().to_str().unwrap())
);
}
#[test]
fn removed_missing_isl_queue_config_is_rejected_as_unknown() {
for value in [
serde_json::json!(null),
serde_json::json!([]),
serde_json::json!([{
"missing_cache_tokens_floor": 0,
"max_queue_depth": 1,
}]),
] {
let encoded = serde_json::json!({
"router_queue_by_incoming_missing_isl": value,
})
.to_string();
let error = serde_json::from_str::<KvRouterConfig>(&encoded).unwrap_err();
let message = error.to_string();
assert!(
message.contains("unknown field `router_queue_by_incoming_missing_isl`"),
"{message}"
);
}
}
#[test]
fn policy_config_is_validated_and_cached_at_startup() {
let policy_file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(
policy_file.path(),
"default_policy_family: stable\nuncached_isl_buckets:\n - min_tokens: 0\n bucket: all\npolicy_classes:\n - name: stable\n policy_family: stable\n cache_bucket: all\n quantum: 7\n",
)
.unwrap();
let config = KvRouterConfig {
router_policy_config: Some(policy_file.path().display().to_string()),
..Default::default()
};
config.validate_config().unwrap();
std::fs::write(policy_file.path(), "not: [valid").unwrap();
let profile = config.policy_profile(None).unwrap();
assert_eq!(profile.default_class().name, "stable");
assert_eq!(profile.default_class().quantum, 7);
}
#[test]
fn invalid_policy_config_fails_config_validation() {
let policy_file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(policy_file.path(), "not: [valid").unwrap();
let config = KvRouterConfig {
router_policy_config: Some(policy_file.path().display().to_string()),
..Default::default()
};
let error = config.validate_config().unwrap_err();
assert!(
error.contains(policy_file.path().to_str().unwrap()),
"{error}"
);
assert!(
error.contains("failed to parse router policy config"),
"{error}"
);
}
#[test]
fn policy_config_uses_fast_recheck_with_prefill_load_model() {
let config = KvRouterConfig {
router_prefill_load_model: RouterPrefillLoadModel::Aic,
router_policy_config: Some("/tmp/policy.yaml".to_string()),
router_queue_threshold: None,
..Default::default()
};
assert_eq!(
config.router_queue_recheck_interval(),
Duration::from_millis(100)
);
}
#[test]
fn prefill_load_model_allows_wspt_policy_classes() {
let config = KvRouterConfig {
router_prefill_load_model: RouterPrefillLoadModel::Aic,
router_queue_policy: RouterQueuePolicy::Wspt,
..Default::default()
};
assert!(config.validate().is_ok());
}
#[test]
fn configured_policy_profile_uses_transient_replay_model_name() {
let path = std::env::temp_dir().join(format!(
"dynamo-router-policy-{}.yaml",
uuid::Uuid::new_v4()
));
std::fs::write(
&path,
r#"
default_policy_family: root
uncached_isl_buckets:
- min_tokens: 0
bucket: all
policy_classes:
- name: root
policy_family: root
cache_bucket: all
quantum: 1
models:
replay-model:
default_policy_family: selected
uncached_isl_buckets:
- min_tokens: 0
bucket: all
policy_classes:
- name: selected
policy_family: selected
cache_bucket: all
quantum: 9
"#,
)
.unwrap();
let config = KvRouterConfig {
router_policy_config: Some(path.display().to_string()),
..Default::default()
}
.with_policy_model_name(Some("replay-model".to_string()));
let profile = config.configured_policy_profile().unwrap();
assert_eq!(profile.default_class().name, "selected");
assert_eq!(profile.default_class().quantum, 9);
assert!(
!serde_json::to_string(&config)
.unwrap()
.contains("replay-model")
);
std::fs::remove_file(path).unwrap();
}
#[test]
fn test_kv_router_config_rejects_out_of_range_overlap_score_credit() {
let too_small = KvRouterConfig {
overlap_score_credit: -0.1,
..Default::default()
};
let too_large = KvRouterConfig {
overlap_score_credit: 1.1,
..Default::default()
};
assert!(too_small.validate().is_err());
let error = too_large.validate().unwrap_err().to_string();
assert!(error.contains("prefill_load_scale"));
}
#[test]
fn test_kv_router_config_maps_deprecated_overlap_weight_alias_to_prefill_scale() {
let config: KvRouterConfig =
serde_json::from_str(r#"{"overlap_score_weight":2.5}"#).unwrap();
assert_eq!(config.overlap_score_credit, 1.0);
assert_eq!(config.prefill_load_scale, 2.5);
}
#[test]
fn test_kv_router_config_maps_deprecated_overlap_weight_zero_to_credit_zero() {
let config: KvRouterConfig =
serde_json::from_str(r#"{"overlap_score_weight":0.0}"#).unwrap();
assert_eq!(config.overlap_score_credit, 0.0);
assert_eq!(config.prefill_load_scale, 0.0);
assert!(!config.should_subscribe_to_kv_events());
}
#[test]
fn test_kv_router_config_deprecated_overlap_weight_overrides_canonical_fields() {
let config: KvRouterConfig = serde_json::from_str(
r#"{"overlap_score_weight":2.5,"overlap_score_credit":0.5,"prefill_load_scale":3.0}"#,
)
.unwrap();
assert_eq!(config.overlap_score_credit, 0.5);
assert_eq!(config.prefill_load_scale, 2.5);
}
#[test]
fn test_kv_router_config_deprecated_overlap_weight_zero_overrides_credit() {
let config: KvRouterConfig = serde_json::from_str(
r#"{"overlap_score_weight":0.0,"overlap_score_credit":0.5,"prefill_load_scale":3.0}"#,
)
.unwrap();
assert_eq!(config.overlap_score_credit, 0.0);
assert_eq!(config.prefill_load_scale, 0.0);
}
#[test]
fn test_kv_router_config_deserialize_rejects_invalid_values() {
let credit_error =
serde_json::from_str::<KvRouterConfig>(r#"{"overlap_score_credit":1.1}"#)
.unwrap_err()
.to_string();
let scale_error = serde_json::from_str::<KvRouterConfig>(r#"{"prefill_load_scale":-0.1}"#)
.unwrap_err()
.to_string();
assert!(credit_error.contains("prefill_load_scale"));
assert!(scale_error.contains("prefill_load_scale"));
}
#[test]
fn test_router_config_override_maps_deprecated_overlap_weight_alias_to_prefill_scale() {
let config: RouterConfigOverride =
serde_json::from_str(r#"{"overlap_score_weight":2.5}"#).unwrap();
assert_eq!(config.overlap_score_credit, None);
assert_eq!(config.prefill_load_scale, Some(2.5));
}
#[test]
fn test_router_config_override_maps_deprecated_overlap_weight_zero_to_credit_zero() {
let config: RouterConfigOverride =
serde_json::from_str(r#"{"overlap_score_weight":0.0}"#).unwrap();
assert_eq!(config.overlap_score_credit, Some(0.0));
assert_eq!(config.prefill_load_scale, Some(0.0));
}
#[test]
fn test_router_config_override_deprecated_overlap_weight_overrides_canonical_fields() {
let config: RouterConfigOverride = serde_json::from_str(
r#"{"overlap_score_weight":2.0,"overlap_score_credit":0.5,"prefill_load_scale":3.0}"#,
)
.unwrap();
assert_eq!(config.overlap_score_credit, Some(0.5));
assert_eq!(config.prefill_load_scale, Some(2.0));
}
#[test]
fn test_router_config_override_deprecated_overlap_weight_zero_overrides_credit() {
let config: RouterConfigOverride = serde_json::from_str(
r#"{"overlap_score_weight":0.0,"overlap_score_credit":0.5,"prefill_load_scale":3.0}"#,
)
.unwrap();
assert_eq!(config.overlap_score_credit, Some(0.0));
assert_eq!(config.prefill_load_scale, Some(0.0));
}
#[test]
fn test_router_config_override_deserialize_rejects_invalid_values() {
let credit_error =
serde_json::from_str::<RouterConfigOverride>(r#"{"overlap_score_credit":1.1}"#)
.unwrap_err()
.to_string();
let scale_error =
serde_json::from_str::<RouterConfigOverride>(r#"{"prefill_load_scale":-0.1}"#)
.unwrap_err()
.to_string();
assert!(credit_error.contains("prefill_load_scale"));
assert!(scale_error.contains("prefill_load_scale"));
}
#[test]
fn test_overlap_credit_zero_skips_kv_event_subscription() {
let config = KvRouterConfig {
overlap_score_credit: 0.0,
use_kv_events: true,
..Default::default()
};
assert!(!config.should_subscribe_to_kv_events());
}
#[test]
fn test_router_config_override_rejects_out_of_range_shared_cache_multiplier() {
let too_small = RouterConfigOverride {
overlap_score_credit: None,
prefill_load_scale: None,
router_temperature: None,
assume_kv_reuse: None,
track_prefill_tokens: None,
shared_cache_multiplier: Some(-0.1),
};
let too_large = RouterConfigOverride {
overlap_score_credit: None,
prefill_load_scale: None,
router_temperature: None,
assume_kv_reuse: None,
track_prefill_tokens: None,
shared_cache_multiplier: Some(1.1),
};
assert!(too_small.validate().is_err());
assert!(too_large.validate().is_err());
}
#[test]
fn test_router_config_override_rejects_out_of_range_overlap_score_credit() {
let too_small = RouterConfigOverride {
overlap_score_credit: Some(-0.1),
prefill_load_scale: None,
router_temperature: None,
assume_kv_reuse: None,
track_prefill_tokens: None,
shared_cache_multiplier: None,
};
let too_large = RouterConfigOverride {
overlap_score_credit: Some(1.1),
prefill_load_scale: None,
router_temperature: None,
assume_kv_reuse: None,
track_prefill_tokens: None,
shared_cache_multiplier: None,
};
assert!(too_small.validate().is_err());
let error = too_large.validate().unwrap_err().to_string();
assert!(error.contains("prefill_load_scale"));
}
#[test]
fn test_kv_router_config_default_shared_cache_multiplier_is_disabled() {
assert_eq!(KvRouterConfig::default().shared_cache_multiplier, 0.0);
}
}