use std::collections::HashMap;
use std::sync::{Arc, Once};
use crate::serve::kv_persist::format::ModelFingerprint;
pub trait ByteSized {
fn byte_len(&self) -> u64;
}
pub fn gemma_lcp_snapshot_fits_budget(est_bytes: u64) -> bool {
est_bytes <= default_lcp_byte_budget()
}
pub fn default_lcp_byte_budget() -> u64 {
const GIB: u64 = 1024 * 1024 * 1024;
const FLOOR: u64 = GIB; const CEILING: u64 = 16 * GIB;
static LOG_ONCE: Once = Once::new();
let env_val = std::env::var("HF2Q_KV_LCP_RESUME_CAPACITY").ok();
if let Some(raw) = env_val {
let trimmed = raw.trim();
let (digits, suffix) = if let Some(last) = trimmed.chars().last() {
match last {
'b' | 'B' | 'k' | 'K' | 'm' | 'M' | 'g' | 'G' => {
(&trimmed[..trimmed.len() - 1], Some(last))
}
_ => (trimmed, None),
}
} else {
(trimmed, None)
};
if let Ok(n) = digits.parse::<u64>() {
let budget = if let Some(suffix_char) = suffix {
let multiplier: u64 = match suffix_char {
'b' | 'B' => 1,
'k' | 'K' => 1024,
'm' | 'M' => 1024 * 1024,
'g' | 'G' => 1024 * 1024 * 1024,
_ => unreachable!("suffix already validated above"),
};
n.saturating_mul(multiplier)
} else if n < 4096 {
let bytes = n.saturating_mul(300 * 1024 * 1024);
static LEGACY_ONCE: Once = Once::new();
LEGACY_ONCE.call_once(|| {
eprintln!(
"[hf2q lcp] HF2Q_KV_LCP_RESUME_CAPACITY={n} interpreted as \
legacy entry-count (={bytes} bytes); use {n}g for byte-budget"
);
});
bytes
} else {
n
};
LOG_ONCE.call_once(|| {
eprintln!(
"[hf2q lcp] byte_budget={} MB (source=env HF2Q_KV_LCP_RESUME_CAPACITY={})",
budget / (1024 * 1024),
raw.trim()
);
});
return budget;
}
static PARSE_WARN_ONCE: Once = Once::new();
PARSE_WARN_ONCE.call_once(|| {
eprintln!(
"[hf2q lcp] WARNING: HF2Q_KV_LCP_RESUME_CAPACITY={:?} is not a \
parsable integer (with optional b/k/m/g suffix); falling back to \
sysinfo probe",
raw
);
});
}
let mut sys = sysinfo::System::new();
sys.refresh_memory();
let avail = sys.available_memory();
let budget = if avail == 0 {
static ZERO_AVAIL_ONCE: Once = Once::new();
ZERO_AVAIL_ONCE.call_once(|| {
eprintln!(
"[hf2q lcp] WARNING: sysinfo available_memory() returned 0 bytes; \
using floor budget of 1 GiB"
);
});
FLOOR
} else {
let computed = (avail as f64 * 0.05) as u64;
computed.clamp(FLOOR, CEILING)
};
LOG_ONCE.call_once(|| {
eprintln!(
"[hf2q lcp] byte_budget={} MB (source=sysinfo available_memory={} GB × 5%)",
budget / (1024 * 1024),
avail / (1024 * 1024 * 1024)
);
});
budget
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct LcpKey {
pub model_fingerprint: ModelFingerprint,
pub tenant_id: String,
pub params_hash: u64,
}
#[derive(Clone, Debug)]
pub struct LcpPrefix<T> {
pub k: usize,
pub dense_kvs: Vec<Arc<T>>,
pub sliding_window: usize,
pub linear_capacity: usize,
pub cached_prompt_len: usize,
}
#[derive(Debug, PartialEq, Eq)]
pub enum LcpStoreError {
EmptyPrompt,
EmptyPayload,
EntryExceedsBudget {
entry_bytes: u64,
budget_bytes: u64,
},
}
struct LcpEntry<T> {
prompt: Vec<u32>,
dense_kvs: Vec<Arc<T>>,
sliding_window: usize,
linear_capacity: usize,
bytes: u64,
}
pub struct LcpRegistry<T>
where
T: Send + Sync + 'static + ByteSized,
{
capacity: usize,
lru_order: Vec<LcpKey>,
entries: HashMap<LcpKey, LcpEntry<T>>,
byte_budget: u64,
current_bytes: u64,
}
impl<T> LcpRegistry<T>
where
T: Send + Sync + 'static + ByteSized,
{
pub fn with_byte_budget(byte_budget: u64) -> Self {
assert!(
byte_budget > 0,
"LcpRegistry::with_byte_budget(0) is a misconfiguration — every \
store would immediately evict itself; refusing to construct"
);
Self {
capacity: usize::MAX,
lru_order: Vec::new(),
entries: HashMap::new(),
byte_budget,
current_bytes: 0,
}
}
pub fn new(capacity: usize) -> Self {
assert!(
capacity > 0,
"LcpRegistry::new(0) is a misconfiguration — every store would \
immediately evict itself; refusing to construct"
);
Self {
capacity,
lru_order: Vec::with_capacity(capacity),
entries: HashMap::with_capacity(capacity),
byte_budget: u64::MAX,
current_bytes: 0,
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn current_bytes(&self) -> u64 {
self.current_bytes
}
pub fn byte_budget(&self) -> u64 {
self.byte_budget
}
pub fn store(
&mut self,
key: LcpKey,
prompt_tokens: Vec<u32>,
dense_kvs: Vec<Arc<T>>,
sliding_window: usize,
linear_capacity: usize,
) -> Result<(), LcpStoreError> {
if prompt_tokens.is_empty() {
return Err(LcpStoreError::EmptyPrompt);
}
if dense_kvs.is_empty() {
return Err(LcpStoreError::EmptyPayload);
}
let new_bytes: u64 = dense_kvs.iter().map(|a| a.byte_len()).sum();
if self.entries.contains_key(&key) {
if new_bytes > self.byte_budget {
return Err(LcpStoreError::EntryExceedsBudget {
entry_bytes: new_bytes,
budget_bytes: self.byte_budget,
});
}
let old_bytes = self.entries[&key].bytes;
self.current_bytes = self.current_bytes.saturating_sub(old_bytes);
self.entries.insert(
key.clone(),
LcpEntry {
prompt: prompt_tokens,
dense_kvs,
sliding_window,
linear_capacity,
bytes: new_bytes,
},
);
self.current_bytes += new_bytes;
if let Some(pos) = self.lru_order.iter().position(|k| k == &key) {
let k = self.lru_order.remove(pos);
self.lru_order.push(k);
}
return Ok(());
}
if new_bytes > self.byte_budget {
return Err(LcpStoreError::EntryExceedsBudget {
entry_bytes: new_bytes,
budget_bytes: self.byte_budget,
});
}
while self.current_bytes + new_bytes > self.byte_budget && !self.lru_order.is_empty() {
if let Some(victim) = self.lru_order.first().cloned() {
if let Some(evicted) = self.entries.remove(&victim) {
self.current_bytes = self.current_bytes.saturating_sub(evicted.bytes);
}
self.lru_order.remove(0);
}
}
while self.entries.len() >= self.capacity {
if let Some(victim) = self.lru_order.first().cloned() {
if let Some(evicted) = self.entries.remove(&victim) {
self.current_bytes = self.current_bytes.saturating_sub(evicted.bytes);
}
self.lru_order.remove(0);
} else {
break;
}
}
self.entries.insert(
key.clone(),
LcpEntry {
prompt: prompt_tokens,
dense_kvs,
sliding_window,
linear_capacity,
bytes: new_bytes,
},
);
self.current_bytes += new_bytes;
self.lru_order.push(key);
{
static EMPIRICAL_ONCE: Once = Once::new();
EMPIRICAL_ONCE.call_once(|| {
let budget_mb = if self.byte_budget == u64::MAX {
return;
} else {
self.byte_budget / (1024 * 1024)
};
let entry_mb = new_bytes / (1024 * 1024);
let admits = if new_bytes > 0 {
self.byte_budget / new_bytes
} else {
0
};
eprintln!(
"[hf2q lcp] empirical: budget={budget_mb} MB / \
first_entry={entry_mb} MB → admits ≈{admits} entries"
);
});
}
Ok(())
}
pub fn lookup(&mut self, key: &LcpKey, new_tokens: &[u32]) -> Option<LcpPrefix<T>> {
let entry = self.entries.get(key)?;
let cached = &entry.prompt[..];
let max_compare = cached.len().min(new_tokens.len());
let mut k = 0usize;
while k < max_compare && cached[k] == new_tokens[k] {
k += 1;
}
if k == 0 || k == new_tokens.len() {
return None;
}
let result = LcpPrefix {
k,
dense_kvs: entry.dense_kvs.iter().map(Arc::clone).collect(),
sliding_window: entry.sliding_window,
linear_capacity: entry.linear_capacity,
cached_prompt_len: entry.prompt.len(),
};
if let Some(pos) = self.lru_order.iter().position(|kk| kk == key) {
let touched = self.lru_order.remove(pos);
self.lru_order.push(touched);
}
Some(result)
}
pub fn take_prefix(&mut self, key: &LcpKey, new_tokens: &[u32]) -> Option<LcpPrefix<T>> {
let prefix = self.lookup(key, new_tokens)?;
if let Some(removed) = self.entries.remove(key) {
self.current_bytes = self.current_bytes.saturating_sub(removed.bytes);
}
if let Some(pos) = self.lru_order.iter().position(|kk| kk == key) {
self.lru_order.remove(pos);
}
Some(prefix)
}
pub fn clear(&mut self) {
self.entries.clear();
self.lru_order.clear();
self.current_bytes = 0;
}
pub fn capacity(&self) -> usize {
self.capacity
}
}
impl<T> std::fmt::Debug for LcpRegistry<T>
where
T: Send + Sync + 'static + ByteSized,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LcpRegistry")
.field("capacity", &self.capacity)
.field("byte_budget", &self.byte_budget)
.field("current_bytes", &self.current_bytes)
.field("len", &self.entries.len())
.field("lru_order_len", &self.lru_order.len())
.finish()
}
}
pub fn probe_lcp_opportunity<T>(
registry: &mut LcpRegistry<T>,
key: &LcpKey,
new_tokens: &[u32],
has_soft_tokens: bool,
) -> Option<usize>
where
T: Send + Sync + 'static + ByteSized,
{
if has_soft_tokens {
return None;
}
registry.lookup(key, new_tokens).map(|prefix| prefix.k)
}
pub fn probe_lcp_opportunity_chunk_aligned<T, F>(
registry: &mut LcpRegistry<T>,
new_tokens: &[u32],
stride: usize,
has_soft_tokens: bool,
mut key_for_chunk_pos: F,
) -> Option<usize>
where
T: Send + Sync + 'static + ByteSized,
F: FnMut(usize) -> LcpKey,
{
if has_soft_tokens {
return None;
}
if stride == 0 || new_tokens.is_empty() {
return None;
}
let max_chunk_pos = (new_tokens.len() / stride).saturating_mul(stride);
if max_chunk_pos < stride {
return None;
}
let mut chunk_pos = max_chunk_pos;
loop {
let key = key_for_chunk_pos(chunk_pos);
if let Some(prefix) = registry.lookup(&key, new_tokens) {
if prefix.k == prefix.cached_prompt_len && prefix.k < new_tokens.len() {
return Some(prefix.k);
}
}
if chunk_pos == stride {
break;
}
chunk_pos -= stride;
}
None
}