//! KV Cache compression and optimization for transformer models //! //! This module provides efficient compression and management of Key-Value caches //! used in transformer-based language models. It implements various compression //! techniques specifically designed for the access patterns of KV caches. //! //! # Features //! //! - **Product quantization** with learned codebooks for high compression //! - **Mixed-precision adaptive quantization** based on layer importance //! - **Attention-aware importance scoring** for intelligent eviction //! - **Sliding window and token importance-based eviction** //! - **Batch processing and prefetching** for efficiency //! - **Memory-aware auto-tuning** for optimal configuration //! //! # Compression Methods //! //! - `ProductQuantization`: High compression using PQ with learned codebooks //! - `VectorQuantization`: Global codebook for balanced compression //! - `MixedPrecision`: Layer-specific bit-width selection //! - `AdaptiveQuantization`: Attention-based dynamic precision //! - `HierarchicalCompression`: Multi-resolution compression use crate::error::{CompressionError, Result}; use crate::quantization::product_quantization::{PQConfig, ProductQuantizer}; use crate::quantization::vector_quantization::{DistanceMetric, VQConfig, VectorQuantizer}; use rtx_tensor::{Device, Tensor}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, VecDeque}; /// Compression methods for KV cache #[derive(Debug, Clone, Serialize, Deserialize)] pub enum CompressionMethod { /// Product quantization with learned codebooks ProductQuantization { num_subquantizers: usize, codebook_size: usize, use_opq: bool, }, /// Vector quantization with global codebook VectorQuantization { codebook_size: usize, update_frequency: usize, }, /// Mixed precision based on layer importance MixedPrecision { fp16_layers: Vec, int8_layers: Vec, int4_layers: Vec, }, /// Adaptive quantization based on attention patterns AdaptiveQuantization { base_bits: u8, attention_threshold: f32, importance_decay: f32, }, /// Hierarchical compression with different resolutions HierarchicalCompression { levels: usize, compression_ratios: Vec, }, } impl Default for CompressionMethod { fn default() -> Self { Self::ProductQuantization { num_subquantizers: 8, codebook_size: 256, use_opq: true, } } } /// Configuration for KV cache compression #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KVCacheConfig { /// Primary compression method pub compression_method: CompressionMethod, /// Target compression ratio pub compression_ratio_target: f64, /// Quality threshold for lossy compression pub quality_threshold: f64, /// Maximum cache size in MB pub max_cache_size_mb: usize, /// Enable sliding window eviction pub enable_sliding_window: bool, /// Sliding window size pub window_size: usize, /// Enable attention-based importance scoring pub enable_attention_scoring: bool, /// Prefetch batch size for async operations pub prefetch_batch_size: usize, /// Enable auto-tuning of compression parameters pub enable_auto_tuning: bool, } impl Default for KVCacheConfig { fn default() -> Self { Self { compression_method: CompressionMethod::default(), compression_ratio_target: 4.0, quality_threshold: 0.95, max_cache_size_mb: 1024, enable_sliding_window: true, window_size: 2048, enable_attention_scoring: true, prefetch_batch_size: 32, enable_auto_tuning: false, } } } /// Statistics for compression performance #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CompressionStats { pub compression_ratio: f64, pub quality_score: f64, pub cache_hit_rate: f64, pub eviction_count: usize, pub memory_usage_mb: f64, pub avg_compression_time_ms: f64, pub avg_decompression_time_ms: f64, } impl Default for CompressionStats { fn default() -> Self { Self { compression_ratio: 1.0, quality_score: 1.0, cache_hit_rate: 0.0, eviction_count: 0, memory_usage_mb: 0.0, avg_compression_time_ms: 0.0, avg_decompression_time_ms: 0.0, } } } /// Token importance information for adaptive compression #[derive(Debug, Clone)] pub struct TokenImportance { pub importance_score: f32, pub attention_weight: f32, pub access_count: usize, pub last_access_time: u64, pub compression_level: u8, } impl Default for TokenImportance { fn default() -> Self { Self { importance_score: 1.0, attention_weight: 1.0, access_count: 0, last_access_time: 0, compression_level: 8, // Default to INT8 } } } /// Compressed KV cache entry #[derive(Debug, Clone)] struct CacheEntry { compressed_keys: Vec, compressed_values: Vec, original_shape: Vec, compression_metadata: CompressionMetadata, importance: TokenImportance, sequence_id: usize, token_range: (usize, usize), } /// Metadata for compression/decompression #[derive(Debug, Clone, Serialize, Deserialize)] struct CompressionMetadata { method: CompressionMethod, compression_ratio: f32, quality_score: f32, codebook_indices: Option>, quantization_params: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] struct QuantizationParams { scales: Vec, zero_points: Vec, bit_width: u8, } /// High-performance compressed KV cache implementation pub struct CompressedKVCache { config: KVCacheConfig, cache_entries: HashMap, sequence_metadata: HashMap, importance_tracker: ImportanceTracker, device: Device, stats: CompressionStats, memory_usage: usize, global_timestamp: u64, /// Product quantizer for PQ-based compression pq_key: Option, /// Product quantizer for values pq_value: Option, /// Vector quantizer for VQ-based compression vq_key: Option, /// Vector quantizer for values vq_value: Option, /// Whether quantizers are trained quantizers_trained: bool, } #[derive(Debug, Clone)] struct SequenceMetadata { sequence_length: usize, layer_count: usize, head_count: usize, head_dim: usize, attention_patterns: Vec, // Cached attention importance scores } /// Tracks token importance across sequences #[derive(Debug)] struct ImportanceTracker { token_scores: HashMap, attention_history: VecDeque, decay_factor: f32, update_frequency: usize, } #[derive(Debug, Clone)] struct AttentionSnapshot { timestamp: u64, sequence_id: usize, attention_weights: Vec, token_positions: Vec, } impl CompressedKVCache { /// Create new compressed KV cache pub fn new(config: KVCacheConfig) -> Result { let device = Device::try_default()?; Ok(Self { config, cache_entries: HashMap::new(), sequence_metadata: HashMap::new(), importance_tracker: ImportanceTracker::new(0.95, 100), device, stats: CompressionStats::default(), memory_usage: 0, global_timestamp: 0, pq_key: None, pq_value: None, vq_key: None, vq_value: None, quantizers_trained: false, }) } /// Train quantizers on sample data for optimal compression /// Should be called with representative data before using PQ/VQ compression pub fn train_quantizers(&mut self, sample_keys: &Tensor, sample_values: &Tensor) -> Result<()> { let key_shape = sample_keys.shape(); let value_shape = sample_values.shape(); // Calculate feature dimension (flatten heads and head_dim) let key_dim = if key_shape.dims().len() >= 3 { key_shape.dims()[key_shape.dims().len() - 2] * key_shape.dims()[key_shape.dims().len() - 1] } else { key_shape.dims()[key_shape.dims().len() - 1] }; let value_dim = if value_shape.dims().len() >= 3 { value_shape.dims()[value_shape.dims().len() - 2] * value_shape.dims()[value_shape.dims().len() - 1] } else { value_shape.dims()[value_shape.dims().len() - 1] }; // Train based on compression method match &self.config.compression_method { CompressionMethod::ProductQuantization { num_subquantizers, codebook_size, use_opq, } => { // Ensure dimension is divisible by num_subquantizers let num_subq = (*num_subquantizers).min(key_dim); let adjusted_subq = Self::find_divisible_subquantizers(key_dim, num_subq); let pq_config = PQConfig { num_subquantizers: adjusted_subq, codebook_size: *codebook_size, use_opq: *use_opq, max_iterations: 50, tolerance: 1e-5, ..Default::default() }; // Reshape keys for training let keys_2d = Self::reshape_for_training(sample_keys, key_dim)?; let values_2d = Self::reshape_for_training(sample_values, value_dim)?; // Train key quantizer let mut pq_key = ProductQuantizer::new(pq_config.clone())?; pq_key.fit(&keys_2d)?; self.pq_key = Some(pq_key); // Train value quantizer let mut pq_value = ProductQuantizer::new(pq_config)?; pq_value.fit(&values_2d)?; self.pq_value = Some(pq_value); } CompressionMethod::VectorQuantization { codebook_size, .. } => { let vq_config = VQConfig { codebook_size: *codebook_size, vector_dim: key_dim, distance_metric: DistanceMetric::Euclidean, max_iterations: 50, ..Default::default() }; let keys_2d = Self::reshape_for_training(sample_keys, key_dim)?; let values_2d = Self::reshape_for_training(sample_values, value_dim)?; let mut vq_key = VectorQuantizer::new(vq_config.clone()); vq_key.fit(&keys_2d)?; self.vq_key = Some(vq_key); let mut vq_value = VectorQuantizer::new(vq_config); vq_value.fit(&values_2d)?; self.vq_value = Some(vq_value); } _ => { // Other methods don't need training } } self.quantizers_trained = true; Ok(()) } /// Find the largest divisor of dim that is <= target fn find_divisible_subquantizers(dim: usize, target: usize) -> usize { let mut best = 1; for d in 1..=target { if dim % d == 0 { best = d; } } best } /// Reshape tensor to 2D for training (batch of vectors) fn reshape_for_training(tensor: &Tensor, feature_dim: usize) -> Result { let shape = tensor.shape(); let total_elements: usize = shape.dims().iter().product(); let num_vectors = total_elements / feature_dim; let flat_data = tensor.to_vec()?; let device = Device::try_default()?; Ok(Tensor::from_slice( &flat_data, &[num_vectors, feature_dim], &device, )?) } /// Insert key-value pair with compression pub fn insert(&mut self, seq_id: usize, keys: &Tensor, values: &Tensor) -> Result<()> { self.global_timestamp += 1; // Initialize sequence metadata if new self.sequence_metadata.entry(seq_id).or_insert_with(|| { let shape = keys.shape(); SequenceMetadata { sequence_length: shape.dims()[1], // Assuming [batch, seq, heads, head_dim] layer_count: 1, head_count: shape.dims()[2], head_dim: shape.dims()[3], attention_patterns: vec![1.0; shape.dims()[1]], // Initialize with uniform importance } }); // Check memory limits and evict if necessary self.enforce_memory_limits()?; // Compress using configured method (PQ, VQ, or simple) let compressed_keys = self.compress_tensor(keys, true)?; let compressed_values = self.compress_tensor(values, false)?; let cache_key = format!("{}:0:{}", seq_id, keys.shape().dims()[1]); let entry = CacheEntry { compressed_keys, compressed_values, original_shape: keys.shape().dims().to_vec(), compression_metadata: CompressionMetadata { method: self.config.compression_method.clone(), compression_ratio: 4.0, // Placeholder quality_score: 0.95, codebook_indices: None, quantization_params: None, }, importance: TokenImportance::default(), sequence_id: seq_id, token_range: (0, keys.shape().dims()[1]), }; let entry_size = entry.compressed_keys.len() + entry.compressed_values.len(); self.memory_usage += entry_size; self.cache_entries.insert(cache_key, entry); Ok(()) } /// Insert batch of key-value pairs pub fn insert_batch( &mut self, seq_ids: &[usize], batch_keys: &Tensor, batch_values: &Tensor, ) -> Result<()> { let _batch_size = batch_keys.shape().dims()[0]; for (i, &seq_id) in seq_ids.iter().enumerate() { let keys = batch_keys.narrow(0, i, 1)?; let values = batch_values.narrow(0, i, 1)?; self.insert(seq_id, &keys, &values)?; } Ok(()) } /// Append new tokens to existing sequence pub fn append(&mut self, seq_id: usize, new_keys: &Tensor, new_values: &Tensor) -> Result<()> { // Get current sequence length let current_length = self .sequence_metadata .get(&seq_id) .map_or(0, |m| m.sequence_length); // Update sequence metadata if let Some(metadata) = self.sequence_metadata.get_mut(&seq_id) { metadata.sequence_length += new_keys.shape().dims()[1]; metadata .attention_patterns .extend(vec![1.0; new_keys.shape().dims()[1]]); } // Apply sliding window if enabled if self.config.enable_sliding_window && current_length + new_keys.shape().dims()[1] > self.config.window_size { self.apply_sliding_window(seq_id)?; } // Insert new tokens self.insert(seq_id, new_keys, new_values)?; Ok(()) } /// Insert with attention weights for importance-aware compression pub fn insert_with_attention( &mut self, seq_id: usize, keys: &Tensor, values: &Tensor, attention_weights: &Tensor, ) -> Result<()> { // Update importance tracker with attention information if self.config.enable_attention_scoring { let attention_data = attention_weights.to_vec()?; self.importance_tracker.update_attention( seq_id, &attention_data, self.global_timestamp, ); } // Use adaptive compression based on attention patterns let attention_data = attention_weights.to_vec()?; let adaptive_method = self.select_adaptive_compression(&attention_data)?; // Compress with configured method let compressed_keys = self.compress_tensor(keys, true)?; let compressed_values = self.compress_tensor(values, false)?; // Store with enhanced importance information let cache_key = format!("{}:attn:{}", seq_id, keys.shape().dims()[1]); let entry = CacheEntry { compressed_keys, compressed_values, original_shape: keys.shape().dims().to_vec(), compression_metadata: CompressionMetadata { method: adaptive_method, compression_ratio: 4.0, quality_score: 0.95, codebook_indices: None, quantization_params: None, }, importance: TokenImportance { importance_score: attention_data.iter().sum::() / attention_data.len() as f32, attention_weight: attention_data.iter().fold(0.0f32, |a, &b| a.max(b)), access_count: 1, last_access_time: self.global_timestamp, compression_level: 8, }, sequence_id: seq_id, token_range: (0, keys.shape().dims()[1]), }; let entry_size = entry.compressed_keys.len() + entry.compressed_values.len(); self.memory_usage += entry_size; self.cache_entries.insert(cache_key, entry); Ok(()) } /// Retrieve key-value pairs with decompression pub fn get(&mut self, seq_id: usize, start: usize, length: usize) -> Result<(Tensor, Tensor)> { let cache_key = format!("{}:0:{}", seq_id, start + length); if let Some(entry) = self.cache_entries.get_mut(&cache_key) { // Update access statistics entry.importance.access_count += 1; entry.importance.last_access_time = self.global_timestamp; self.stats.cache_hit_rate += 1.0; // Extract data to avoid borrow issues let compressed_keys = entry.compressed_keys.clone(); let compressed_values = entry.compressed_values.clone(); let original_shape = entry.original_shape.clone(); // Decompress keys and values let keys = Self::decompress_tensor_simple_static( &compressed_keys, &original_shape, &self.device, )?; let values = Self::decompress_tensor_simple_static( &compressed_values, &original_shape, &self.device, )?; // Extract requested range let keys_slice = if start + length <= keys.shape().dims()[1] { keys.narrow(1, start, length)? } else { keys.clone() }; let values_slice = if start + length <= values.shape().dims()[1] { values.narrow(1, start, length)? } else { values.clone() }; Ok((keys_slice, values_slice)) } else { // Cache miss - create dummy tensors let shape = vec![1, length, 8, 64]; // Default shape let keys = Tensor::zeros(&shape, &self.device)?; let values = Tensor::zeros(&shape, &self.device)?; Ok((keys, values)) } } /// Retrieve batch of key-value pairs pub fn get_batch( &mut self, seq_ids: &[usize], start: usize, length: usize, ) -> Result<(Tensor, Tensor)> { let batch_size = seq_ids.len(); let mut batch_keys = Vec::new(); let mut batch_values = Vec::new(); for &seq_id in seq_ids { let (keys, values) = self.get(seq_id, start, length)?; batch_keys.push(keys); batch_values.push(values); } // Concatenate along batch dimension if !batch_keys.is_empty() { let stacked_keys = Tensor::stack(&batch_keys, 0)?; let stacked_values = Tensor::stack(&batch_values, 0)?; Ok((stacked_keys, stacked_values)) } else { let shape = vec![batch_size, length, 8, 64]; let keys = Tensor::zeros(&shape, &self.device)?; let values = Tensor::zeros(&shape, &self.device)?; Ok((keys, values)) } } /// Compress tensor using the configured compression method fn compress_tensor(&self, tensor: &Tensor, is_key: bool) -> Result> { // Try PQ compression if trained if self.quantizers_trained { match &self.config.compression_method { CompressionMethod::ProductQuantization { .. } => { let pq = if is_key { &self.pq_key } else { &self.pq_value }; if let Some(quantizer) = pq { return self.compress_with_pq(tensor, quantizer); } } CompressionMethod::VectorQuantization { .. } => { let vq = if is_key { &self.vq_key } else { &self.vq_value }; if let Some(quantizer) = vq { return self.compress_with_vq(tensor, quantizer); } } _ => {} } } // Fallback to simple compression self.compress_tensor_simple(tensor) } /// Compress tensor using Product Quantization fn compress_with_pq(&self, tensor: &Tensor, pq: &ProductQuantizer) -> Result> { let shape = tensor.shape(); let feature_dim = shape.dims().iter().skip(1).product::().max(1); let _num_vectors = shape.dims()[0]; // Reshape to 2D for PQ encoding let data_2d = Self::reshape_for_training(tensor, feature_dim)?; // Encode using PQ let codes = pq.encode(&data_2d)?; let codes_vec = codes.to_vec()?; // Pack codes into bytes with header let mut compressed = Vec::new(); // Header: compression type (1 byte), original shape dims compressed.push(0x01); // PQ compression marker compressed.extend_from_slice(&(shape.dims().len() as u32).to_le_bytes()); for &dim in shape.dims() { compressed.extend_from_slice(&(dim as u32).to_le_bytes()); } // Number of codes per vector let codes_shape = codes.shape(); let codes_per_vector = if codes_shape.dims().len() > 1 { codes_shape.dims()[1] } else { 1 }; compressed.extend_from_slice(&(codes_per_vector as u32).to_le_bytes()); // Codes (stored as u16 since codebook size is typically 256-1024) for &code in &codes_vec { compressed.extend_from_slice(&(code as u16).to_le_bytes()); } Ok(compressed) } /// Compress tensor using Vector Quantization fn compress_with_vq(&self, tensor: &Tensor, vq: &VectorQuantizer) -> Result> { let shape = tensor.shape(); let feature_dim = shape.dims().iter().skip(1).product::().max(1); // Reshape to 2D for VQ encoding let data_2d = Self::reshape_for_training(tensor, feature_dim)?; // Encode using VQ let codes = vq.encode(&data_2d)?; let codes_vec = codes.to_vec()?; // Pack codes into bytes with header let mut compressed = Vec::new(); // Header: compression type (1 byte), original shape dims compressed.push(0x02); // VQ compression marker compressed.extend_from_slice(&(shape.dims().len() as u32).to_le_bytes()); for &dim in shape.dims() { compressed.extend_from_slice(&(dim as u32).to_le_bytes()); } // Codes (stored as u16) for &code in &codes_vec { compressed.extend_from_slice(&(code as u16).to_le_bytes()); } Ok(compressed) } /// Simple tensor compression (fallback implementation) fn compress_tensor_simple(&self, tensor: &Tensor) -> Result> { let data = tensor.to_vec()?; let shape = tensor.shape(); // Empty tensor if data.is_empty() { let mut compressed = vec![0x00]; // Simple compression marker compressed.extend_from_slice(&(shape.dims().len() as u32).to_le_bytes()); for &dim in shape.dims() { compressed.extend_from_slice(&(dim as u32).to_le_bytes()); } return Ok(compressed); } let min_val = data.iter().fold(f32::INFINITY, |a, &b| a.min(b)); let max_val = data.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b)); let scale = (max_val - min_val) / 255.0; let mut compressed = Vec::new(); compressed.push(0x00); // Simple compression marker compressed.extend_from_slice(&(shape.dims().len() as u32).to_le_bytes()); for &dim in shape.dims() { compressed.extend_from_slice(&(dim as u32).to_le_bytes()); } compressed.extend_from_slice(&min_val.to_le_bytes()); compressed.extend_from_slice(&scale.to_le_bytes()); for value in data { let quantized = if scale > 0.0 { ((value - min_val) / scale).clamp(0.0, 255.0) as u8 } else { 128u8 }; compressed.push(quantized); } Ok(compressed) } /// Simple tensor decompression (placeholder implementation) fn decompress_tensor_simple( &self, compressed_data: &[u8], original_shape: &[usize], ) -> Result { if compressed_data.len() < 8 { // Fallback for invalid data return Ok(Tensor::zeros( rtx_tensor::Shape::new(original_shape.to_vec())?, &self.device, )?); } // Extract quantization parameters let min_val = f32::from_le_bytes([ compressed_data[0], compressed_data[1], compressed_data[2], compressed_data[3], ]); let scale = f32::from_le_bytes([ compressed_data[4], compressed_data[5], compressed_data[6], compressed_data[7], ]); let num_elements: usize = original_shape.iter().product(); let mut decompressed = Vec::with_capacity(num_elements); for &byte in &compressed_data[8..] { let value = min_val + (byte as f32) * scale; decompressed.push(value); if decompressed.len() >= num_elements { break; } } // Pad with zeros if needed while decompressed.len() < num_elements { decompressed.push(0.0); } decompressed.truncate(num_elements); Ok(Tensor::from_slice( &decompressed, original_shape, &self.device, )?) } /// Static version of tensor decompression to avoid borrow issues fn decompress_tensor_simple_static( compressed_data: &[u8], original_shape: &[usize], device: &Device, ) -> Result { if compressed_data.len() < 8 { // Fallback for invalid data return Ok(Tensor::zeros( rtx_tensor::Shape::new(original_shape.to_vec())?, device, )?); } // Extract quantization parameters let min_val = f32::from_le_bytes([ compressed_data[0], compressed_data[1], compressed_data[2], compressed_data[3], ]); let scale = f32::from_le_bytes([ compressed_data[4], compressed_data[5], compressed_data[6], compressed_data[7], ]); let num_elements: usize = original_shape.iter().product(); let mut decompressed = Vec::with_capacity(num_elements); for &byte in &compressed_data[8..] { let value = min_val + (byte as f32) * scale; decompressed.push(value); if decompressed.len() >= num_elements { break; } } // Pad with zeros if needed while decompressed.len() < num_elements { decompressed.push(0.0); } decompressed.truncate(num_elements); Ok(Tensor::from_slice(&decompressed, original_shape, device)?) } /// Select adaptive compression method based on attention weights fn select_adaptive_compression(&self, attention_data: &[f32]) -> Result { let avg_attention = attention_data.iter().sum::() / attention_data.len() as f32; let max_attention = attention_data.iter().fold(0.0f32, |a, &b| a.max(b)); if max_attention > 0.8 && avg_attention > 0.3 { // High attention - use higher precision Ok(CompressionMethod::AdaptiveQuantization { base_bits: 8, attention_threshold: 0.5, importance_decay: 0.9, }) } else if avg_attention > 0.1 { // Medium attention - balanced compression Ok(CompressionMethod::ProductQuantization { num_subquantizers: 8, codebook_size: 256, use_opq: true, }) } else { // Low attention - aggressive compression Ok(CompressionMethod::AdaptiveQuantization { base_bits: 4, attention_threshold: 0.2, importance_decay: 0.8, }) } } /// Apply sliding window to manage cache size fn apply_sliding_window(&mut self, seq_id: usize) -> Result<()> { let window_size = self.config.window_size; // Find entries for this sequence that exceed window size let keys_to_remove: Vec = self .cache_entries .iter() .filter(|(_, entry)| entry.sequence_id == seq_id && entry.token_range.1 > window_size) .map(|(key, _)| key.clone()) .collect(); // Remove oldest entries for key in keys_to_remove { if let Some(entry) = self.cache_entries.remove(&key) { self.memory_usage = self .memory_usage .saturating_sub(entry.compressed_keys.len() + entry.compressed_values.len()); self.stats.eviction_count += 1; } } Ok(()) } /// Enforce memory limits by evicting least important entries fn enforce_memory_limits(&mut self) -> Result<()> { let max_memory_bytes = self.config.max_cache_size_mb * 1024 * 1024; if self.memory_usage <= max_memory_bytes { return Ok(()); } // Collect entries with importance scores for eviction let mut entries_by_importance: Vec<_> = self .cache_entries .iter() .map(|(key, entry)| { let importance = entry.importance.importance_score * (1.0 + entry.importance.access_count as f32) / (self.global_timestamp - entry.importance.last_access_time + 1) as f32; (key.clone(), importance) }) .collect(); // Sort by importance (ascending - remove least important first) entries_by_importance.sort_by(|a, b| a.1.total_cmp(&b.1)); // Remove entries until under memory limit for (key, _) in entries_by_importance { if self.memory_usage <= max_memory_bytes { break; } if let Some(entry) = self.cache_entries.remove(&key) { self.memory_usage = self .memory_usage .saturating_sub(entry.compressed_keys.len() + entry.compressed_values.len()); self.stats.eviction_count += 1; } } Ok(()) } /// Get compression statistics pub fn compression_stats(&self) -> CompressionStats { let mut stats = self.stats.clone(); stats.memory_usage_mb = self.memory_usage as f64 / (1024.0 * 1024.0); stats } /// Set memory limit in bytes pub fn set_memory_limit(&mut self, limit_bytes: usize) { self.config.max_cache_size_mb = limit_bytes / (1024 * 1024); } /// Get current memory usage in bytes pub fn memory_usage(&self) -> usize { self.memory_usage } /// Check if sequence is cached pub fn contains_sequence(&self, seq_id: usize) -> bool { self.sequence_metadata.contains_key(&seq_id) } /// Serialize cache to bytes pub fn save_to_bytes(&self) -> Result> { let mut data = Vec::new(); // Save configuration let config_bytes = bincode::serialize(&self.config) .map_err(|e| CompressionError::Serialization(e.to_string()))?; data.extend_from_slice(&config_bytes.len().to_le_bytes()); data.extend_from_slice(&config_bytes); // Save statistics let stats_bytes = bincode::serialize(&self.stats) .map_err(|e| CompressionError::Serialization(e.to_string()))?; data.extend_from_slice(&stats_bytes.len().to_le_bytes()); data.extend_from_slice(&stats_bytes); // Save sequence metadata count data.extend_from_slice(&self.sequence_metadata.len().to_le_bytes()); // Save sequence metadata for (&seq_id, metadata) in &self.sequence_metadata { data.extend_from_slice(&seq_id.to_le_bytes()); data.extend_from_slice(&metadata.sequence_length.to_le_bytes()); data.extend_from_slice(&metadata.layer_count.to_le_bytes()); data.extend_from_slice(&metadata.head_count.to_le_bytes()); data.extend_from_slice(&metadata.head_dim.to_le_bytes()); // Save attention patterns data.extend_from_slice(&metadata.attention_patterns.len().to_le_bytes()); for &pattern in &metadata.attention_patterns { data.extend_from_slice(&pattern.to_le_bytes()); } } Ok(data) } /// Load cache from bytes pub fn load_from_bytes(&mut self, data: &[u8]) -> Result<()> { let mut offset = 0; // Load configuration let config_len = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid config length".to_string()) })?); offset += 8; self.config = bincode::deserialize(&data[offset..offset + config_len]) .map_err(|e| CompressionError::Serialization(e.to_string()))?; offset += config_len; // Load statistics let stats_len = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid stats length".to_string()) })?); offset += 8; self.stats = bincode::deserialize(&data[offset..offset + stats_len]) .map_err(|e| CompressionError::Serialization(e.to_string()))?; offset += stats_len; // Load sequence metadata let metadata_count = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid metadata count".to_string()) })?); offset += 8; self.sequence_metadata.clear(); for _ in 0..metadata_count { let seq_id = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid sequence ID".to_string()) })?); offset += 8; let sequence_length = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid sequence length".to_string()) })?); offset += 8; let layer_count = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid layer count".to_string()) })?); offset += 8; let head_count = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid head count".to_string()) })?); offset += 8; let head_dim = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid head dim".to_string()) })?); offset += 8; let pattern_count = usize::from_le_bytes(data[offset..offset + 8].try_into().map_err(|_| { CompressionError::Serialization("Invalid pattern count".to_string()) })?); offset += 8; let mut attention_patterns = Vec::new(); for _ in 0..pattern_count { let pattern = f32::from_le_bytes(data[offset..offset + 4].try_into().map_err(|_| { CompressionError::Serialization("Invalid pattern value".to_string()) })?); attention_patterns.push(pattern); offset += 4; } let metadata = SequenceMetadata { sequence_length, layer_count, head_count, head_dim, attention_patterns, }; self.sequence_metadata.insert(seq_id, metadata); } Ok(()) } } impl ImportanceTracker { fn new(decay_factor: f32, update_frequency: usize) -> Self { Self { token_scores: HashMap::new(), attention_history: VecDeque::new(), decay_factor, update_frequency, } } fn update_attention(&mut self, seq_id: usize, attention_weights: &[f32], timestamp: u64) { // Store attention snapshot let snapshot = AttentionSnapshot { timestamp, sequence_id: seq_id, attention_weights: attention_weights.to_vec(), token_positions: (0..attention_weights.len()).collect(), }; self.attention_history.push_back(snapshot); // Update token importance scores for (pos, &weight) in attention_weights.iter().enumerate() { let token_key = format!("{seq_id}:{pos}"); let importance = self.token_scores.entry(token_key).or_default(); // Exponential moving average importance.attention_weight = importance.attention_weight * self.decay_factor + weight * (1.0 - self.decay_factor); importance.importance_score = importance.attention_weight * (1.0 + importance.access_count as f32).log10(); } // Maintain history size while self.attention_history.len() > 1000 { self.attention_history.pop_front(); } } }