use std::ops::Range;
use std::sync::Arc;
use onnx_genai_kv::{
CachePriority, Device, KvCacheConnector, KvCacheKey, KvCacheLocation, KvPayload, KvStoreEntry,
NullConnector, TokenChunk, chunk_tokens,
};
use crate::logits::TokenId;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ConnectorStats {
pub lookups: usize,
pub chunk_hits: usize,
pub would_extend_tokens: usize,
pub fetched_tokens: usize,
pub stores: usize,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ConnectorLookupOutcome {
pub chunk_hits: usize,
pub would_extend_tokens: usize,
pub fetched_tokens: usize,
}
#[derive(Debug, Clone)]
pub(crate) struct FetchedChunk {
pub(crate) start: usize,
pub(crate) num_tokens: usize,
pub(crate) payload: KvPayload,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ConnectorFetchOutcome {
pub(crate) chunk_hits: usize,
pub(crate) fetched_tokens: usize,
pub(crate) chunks: Vec<FetchedChunk>,
}
pub(crate) struct ConnectorBridge {
connector: Arc<dyn KvCacheConnector>,
runtime: Option<tokio::runtime::Runtime>,
active: bool,
model_id: String,
chunk_size: usize,
layer_range: Range<usize>,
store_priority: CachePriority,
recompute_ms_per_token: f64,
stats: ConnectorStats,
}
impl ConnectorBridge {
pub(crate) fn null() -> Self {
Self {
connector: Arc::new(NullConnector),
runtime: None,
active: false,
model_id: String::new(),
chunk_size: onnx_genai_kv::DEFAULT_CHUNK_SIZE,
layer_range: 0..0,
store_priority: CachePriority::Session,
recompute_ms_per_token: 0.0,
stats: ConnectorStats::default(),
}
}
pub(crate) fn new(
connector: Arc<dyn KvCacheConnector>,
model_id: String,
chunk_size: usize,
layer_range: Range<usize>,
store_priority: CachePriority,
recompute_ms_per_token: f64,
) -> anyhow::Result<Self> {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.map_err(|e| anyhow::anyhow!("failed to build KV connector runtime: {e}"))?;
Ok(Self {
connector,
runtime: Some(runtime),
active: true,
model_id,
chunk_size: chunk_size.max(1),
layer_range,
store_priority,
recompute_ms_per_token: recompute_ms_per_token.max(0.0),
stats: ConnectorStats::default(),
})
}
pub(crate) fn is_active(&self) -> bool {
self.active
}
pub(crate) fn stats(&self) -> &ConnectorStats {
&self.stats
}
pub(crate) fn reset_stats(&mut self) {
self.stats = ConnectorStats::default();
}
fn block_on<F: std::future::Future>(&self, fut: F) -> F::Output {
self.runtime
.as_ref()
.expect("active connector bridge always has a runtime")
.block_on(fut)
}
fn key_for(&self, chunk: &TokenChunk) -> KvCacheKey {
chunk.to_key(self.model_id.clone(), self.layer_range.clone())
}
pub(crate) fn lookup_extension(
&mut self,
prompt_tokens: &[TokenId],
in_process_hit: usize,
) -> ConnectorLookupOutcome {
if !self.active || in_process_hit >= prompt_tokens.len() {
return ConnectorLookupOutcome::default();
}
if !in_process_hit.is_multiple_of(self.chunk_size) {
return ConnectorLookupOutcome::default();
}
let chunks = chunk_tokens(prompt_tokens, self.chunk_size);
let start_index = in_process_hit / self.chunk_size;
if start_index >= chunks.len() {
return ConnectorLookupOutcome::default();
}
let candidate_chunks: Vec<&TokenChunk> = chunks[start_index..]
.iter()
.take_while(|c| c.tokens.len() == self.chunk_size)
.collect();
if candidate_chunks.is_empty() {
return ConnectorLookupOutcome::default();
}
let keys: Vec<KvCacheKey> = candidate_chunks.iter().map(|c| self.key_for(c)).collect();
let connector = Arc::clone(&self.connector);
let locations = match self.block_on(connector.lookup_batch(&keys)) {
Ok(locations) => locations,
Err(error) => {
tracing::debug!(%error, "KV connector lookup_batch failed; recomputing prefix");
return ConnectorLookupOutcome::default();
}
};
self.stats.lookups += keys.len();
let mut outcome = ConnectorLookupOutcome::default();
for (location, key) in locations.iter().zip(&keys) {
let Some(load_ms) = location_load_ms(location) else {
break; };
let recompute_ms = key.num_tokens as f64 * self.recompute_ms_per_token;
if load_ms > recompute_ms {
break; }
outcome.chunk_hits += 1;
outcome.would_extend_tokens += key.num_tokens as usize;
}
self.stats.chunk_hits += outcome.chunk_hits;
self.stats.would_extend_tokens += outcome.would_extend_tokens;
if outcome.would_extend_tokens > 0 {
tracing::debug!(
chunk_hits = outcome.chunk_hits,
would_extend_tokens = outcome.would_extend_tokens,
"KV connector could extend prefix reuse (materialization deferred in K3)"
);
}
outcome
}
pub(crate) fn store_prefix_with<F>(
&mut self,
tokens: &[TokenId],
resident_len: usize,
mut extract: F,
) where
F: FnMut(usize, usize) -> anyhow::Result<KvPayload>,
{
if !self.active {
return;
}
let resident_len = resident_len.min(tokens.len());
if resident_len < self.chunk_size {
return;
}
let chunks = chunk_tokens(&tokens[..resident_len], self.chunk_size);
let connector = Arc::clone(&self.connector);
let priority = self.store_priority;
let mut chunk_start = 0usize;
for chunk in &chunks {
let num_tokens = chunk.tokens.len();
if num_tokens != self.chunk_size {
chunk_start += num_tokens;
continue; }
let payload = match extract(chunk_start, num_tokens) {
Ok(payload) => payload,
Err(error) => {
tracing::debug!(%error, chunk_start, num_tokens, "KV connector extract failed; chunk not cached externally");
chunk_start += num_tokens;
continue;
}
};
let entry = KvStoreEntry {
key: self.key_for(chunk),
kv_data: payload,
priority,
ttl: None,
};
match self.block_on(connector.store(entry)) {
Ok(()) => self.stats.stores += 1,
Err(error) => {
tracing::debug!(%error, "KV connector store failed; chunk not cached externally");
}
}
chunk_start += num_tokens;
}
}
pub(crate) fn fetch_extension(
&mut self,
prompt_tokens: &[TokenId],
boundary: usize,
max_tokens: usize,
target: Device,
) -> ConnectorFetchOutcome {
let mut outcome = ConnectorFetchOutcome::default();
if !self.active || boundary >= prompt_tokens.len() {
return outcome;
}
if !boundary.is_multiple_of(self.chunk_size) {
return outcome;
}
let chunks = chunk_tokens(prompt_tokens, self.chunk_size);
let start_index = boundary / self.chunk_size;
if start_index >= chunks.len() {
return outcome;
}
let candidates: Vec<TokenChunk> = chunks
.iter()
.skip(start_index)
.filter(|chunk| chunk.tokens.len() == self.chunk_size)
.cloned()
.collect();
if candidates.is_empty() {
return outcome;
}
let keys: Vec<KvCacheKey> = candidates.iter().map(|chunk| self.key_for(chunk)).collect();
let connector = Arc::clone(&self.connector);
self.stats.lookups += keys.len();
let locations = match self.block_on(connector.lookup_batch(&keys)) {
Ok(locations) => locations,
Err(error) => {
tracing::debug!(%error, "KV connector lookup_batch failed; not extending prefix");
return outcome;
}
};
let mut position = boundary;
for (chunk, location) in candidates.iter().zip(locations.iter()) {
let num_tokens = chunk.tokens.len();
if outcome.fetched_tokens + num_tokens > max_tokens {
break; }
let Some(load_ms) = location_load_ms(location) else {
break; };
let recompute_ms = num_tokens as f64 * self.recompute_ms_per_token;
if load_ms > recompute_ms {
break; }
let key = self.key_for(chunk);
let fetched = match self.block_on(connector.fetch(&key, target)) {
Ok(fetched) => fetched,
Err(error) => {
tracing::debug!(%error, "KV connector fetch failed; ending prefix extension");
break;
}
};
if !fetched.payload.is_well_formed() || fetched.payload.num_tokens != num_tokens {
tracing::debug!(
"KV connector fetch returned mismatched payload; ending prefix extension"
);
break;
}
outcome.chunk_hits += 1;
outcome.fetched_tokens += num_tokens;
outcome.chunks.push(FetchedChunk {
start: position,
num_tokens,
payload: fetched.payload,
});
position += num_tokens;
}
self.stats.chunk_hits += outcome.chunk_hits;
self.stats.would_extend_tokens += outcome.fetched_tokens;
self.stats.fetched_tokens += outcome.fetched_tokens;
outcome
}
}
fn location_load_ms(location: &KvCacheLocation) -> Option<f64> {
match location {
KvCacheLocation::LocalGpu { .. } => Some(0.0),
KvCacheLocation::LocalCpu {
estimated_load_ms, ..
}
| KvCacheLocation::LocalDisk {
estimated_load_ms, ..
}
| KvCacheLocation::Remote {
estimated_load_ms, ..
} => Some(*estimated_load_ms),
KvCacheLocation::NotFound => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use onnx_genai_kv::{
ConnectorCapabilities, ConnectorHealth, ConnectorResult, Device, FetchedKv, KvLayerPayload,
KvPayloadDtype, LocalTieredConfig, LocalTieredConnector,
};
use std::sync::Mutex;
fn fake_payload(num_tokens: usize) -> KvPayload {
let per_layer = num_tokens * 2; KvPayload {
num_tokens,
num_layers: 2,
num_kv_heads: 1,
head_dim: 2,
dtype: KvPayloadDtype::F32,
layers: (0..2)
.map(|_| KvLayerPayload {
key: vec![0.0; per_layer],
value: vec![0.0; per_layer],
})
.collect(),
}
}
#[derive(Default)]
struct SpyConnector {
resident: Mutex<std::collections::HashMap<u64, f64>>,
stored: Mutex<Vec<KvCacheKey>>,
lookups: Mutex<Vec<KvCacheKey>>,
}
impl SpyConnector {
fn resident(entries: &[(u64, f64)]) -> Self {
let spy = SpyConnector::default();
let mut guard = spy.resident.lock().unwrap();
for (hash, ms) in entries {
guard.insert(*hash, *ms);
}
drop(guard);
spy
}
}
#[async_trait::async_trait]
impl KvCacheConnector for SpyConnector {
async fn lookup(&self, key: &KvCacheKey) -> ConnectorResult<KvCacheLocation> {
self.lookups.lock().unwrap().push(key.clone());
Ok(match self.resident.lock().unwrap().get(&key.chunk_hash) {
Some(ms) if *ms == 0.0 => KvCacheLocation::LocalGpu { page_ids: vec![] },
Some(ms) => KvCacheLocation::LocalCpu {
estimated_load_ms: *ms,
size_bytes: 0,
},
None => KvCacheLocation::NotFound,
})
}
async fn store(&self, entry: KvStoreEntry) -> ConnectorResult<()> {
self.stored.lock().unwrap().push(entry.key);
Ok(())
}
async fn fetch(&self, _key: &KvCacheKey, _target: Device) -> ConnectorResult<FetchedKv> {
Err(onnx_genai_kv::ConnectorError::NotFound)
}
fn prefetch(&self, _key: &KvCacheKey, _target: Device) {}
async fn pin(&self, _key: &KvCacheKey) -> ConnectorResult<()> {
Ok(())
}
async fn unpin(&self, _key: &KvCacheKey) -> ConnectorResult<()> {
Ok(())
}
async fn evict(&self, _key: &KvCacheKey) -> ConnectorResult<()> {
Ok(())
}
async fn health(&self) -> ConnectorHealth {
ConnectorHealth::Healthy
}
fn capabilities(&self) -> ConnectorCapabilities {
ConnectorCapabilities {
distributed: false,
prefetch: false,
pinnable: false,
max_chunk_tokens: usize::MAX,
compression: vec![],
}
}
}
fn bridge_over(connector: Arc<dyn KvCacheConnector>, chunk_size: usize) -> ConnectorBridge {
ConnectorBridge::new(
connector,
"test-model".to_string(),
chunk_size,
0..2,
CachePriority::Session,
1.0,
)
.unwrap()
}
#[test]
fn null_bridge_is_inert() {
let mut bridge = ConnectorBridge::null();
assert!(!bridge.is_active());
let tokens: Vec<TokenId> = (0..1000).collect();
let outcome = bridge.lookup_extension(&tokens, 0);
assert_eq!(outcome, ConnectorLookupOutcome::default());
bridge.store_prefix_with(&tokens, tokens.len(), |_, n| Ok(fake_payload(n)));
assert_eq!(bridge.stats(), &ConnectorStats::default());
}
#[test]
fn store_prefix_pushes_only_complete_chunks() {
let spy = Arc::new(SpyConnector::default());
let mut bridge = bridge_over(spy.clone(), 4);
let tokens: Vec<TokenId> = (0..10).collect();
bridge.store_prefix_with(&tokens, tokens.len(), |_, n| Ok(fake_payload(n)));
assert_eq!(bridge.stats().stores, 2);
assert_eq!(spy.stored.lock().unwrap().len(), 2);
}
#[test]
fn store_prefix_respects_resident_len() {
let spy = Arc::new(SpyConnector::default());
let mut bridge = bridge_over(spy.clone(), 4);
let tokens: Vec<TokenId> = (0..10).collect();
bridge.store_prefix_with(&tokens, 4, |_, n| Ok(fake_payload(n)));
assert_eq!(bridge.stats().stores, 1);
}
#[test]
fn lookup_extension_walks_contiguous_hits_from_boundary() {
let tokens: Vec<TokenId> = (0..12).collect();
let chunks = chunk_tokens(&tokens, 4);
let spy = Arc::new(SpyConnector::resident(&[
(chunks[1].hash, 0.0),
(chunks[2].hash, 1.0),
]));
let mut bridge = bridge_over(spy, 4);
let outcome = bridge.lookup_extension(&tokens, 4);
assert_eq!(outcome.chunk_hits, 2);
assert_eq!(outcome.would_extend_tokens, 8);
assert_eq!(outcome.fetched_tokens, 0);
}
#[test]
fn lookup_extension_stops_at_first_miss() {
let tokens: Vec<TokenId> = (0..12).collect();
let chunks = chunk_tokens(&tokens, 4);
let spy = Arc::new(SpyConnector::resident(&[(chunks[1].hash, 0.0)]));
let mut bridge = bridge_over(spy, 4);
let outcome = bridge.lookup_extension(&tokens, 4);
assert_eq!(outcome.chunk_hits, 1);
assert_eq!(outcome.would_extend_tokens, 4);
}
#[test]
fn lookup_extension_prefers_recompute_when_fetch_is_costlier() {
let tokens: Vec<TokenId> = (0..8).collect();
let chunks = chunk_tokens(&tokens, 4);
let spy = Arc::new(SpyConnector::resident(&[(chunks[1].hash, 100.0)]));
let mut bridge = bridge_over(spy, 4);
let outcome = bridge.lookup_extension(&tokens, 4);
assert_eq!(outcome.chunk_hits, 0);
assert_eq!(outcome.would_extend_tokens, 0);
}
#[test]
fn lookup_extension_requires_chunk_aligned_boundary() {
let tokens: Vec<TokenId> = (0..12).collect();
let chunks = chunk_tokens(&tokens, 4);
let spy = Arc::new(SpyConnector::resident(&[(chunks[1].hash, 0.0)]));
let mut bridge = bridge_over(spy, 4);
let outcome = bridge.lookup_extension(&tokens, 3);
assert_eq!(outcome, ConnectorLookupOutcome::default());
assert_eq!(bridge.stats().lookups, 0);
}
#[test]
fn store_then_lookup_roundtrips_through_local_tiered() {
let config = LocalTieredConfig {
chunk_size: 4,
page_size: 4,
..LocalTieredConfig::default()
};
let connector = Arc::new(LocalTieredConnector::new(config).unwrap());
let mut bridge = bridge_over(connector, 4);
let tokens: Vec<TokenId> = (0..12).collect();
bridge.store_prefix_with(&tokens, tokens.len(), |_, n| Ok(fake_payload(n)));
assert_eq!(bridge.stats().stores, 3);
let outcome = bridge.lookup_extension(&tokens, 4);
assert_eq!(outcome.chunk_hits, 2);
assert_eq!(outcome.would_extend_tokens, 8);
}
#[test]
fn fetch_extension_materializes_contiguous_hits() {
let config = LocalTieredConfig {
chunk_size: 4,
page_size: 4,
..LocalTieredConfig::default()
};
let connector = Arc::new(LocalTieredConnector::new(config).unwrap());
let mut bridge = bridge_over(connector, 4);
let tokens: Vec<TokenId> = (0..12).collect();
bridge.store_prefix_with(&tokens, tokens.len(), |_, n| Ok(fake_payload(n)));
let outcome = bridge.fetch_extension(&tokens, 4, tokens.len(), Device::Cpu);
assert_eq!(outcome.chunk_hits, 2);
assert_eq!(outcome.fetched_tokens, 8);
assert_eq!(outcome.chunks.len(), 2);
assert_eq!(outcome.chunks[0].start, 4);
assert_eq!(outcome.chunks[0].num_tokens, 4);
assert_eq!(outcome.chunks[0].payload.num_tokens, 4);
assert_eq!(outcome.chunks[1].start, 8);
assert_eq!(bridge.stats().fetched_tokens, 8);
}
#[test]
fn fetch_extension_requires_chunk_aligned_boundary() {
let config = LocalTieredConfig {
chunk_size: 4,
page_size: 4,
..LocalTieredConfig::default()
};
let connector = Arc::new(LocalTieredConnector::new(config).unwrap());
let mut bridge = bridge_over(connector, 4);
let tokens: Vec<TokenId> = (0..12).collect();
bridge.store_prefix_with(&tokens, tokens.len(), |_, n| Ok(fake_payload(n)));
let outcome = bridge.fetch_extension(&tokens, 3, tokens.len(), Device::Cpu);
assert_eq!(outcome.fetched_tokens, 0);
assert!(outcome.chunks.is_empty());
}
}