Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
+339
View File
@@ -0,0 +1,339 @@
//! Eviction policies and cache persistence.
use super::paged_kv_cache::PagedKvCache;
use super::types::{EvictionPolicy, MemoryTier, PageId};
use crate::error::{InferenceError, InferenceResult};
use std::time::Instant;
use tracing::{info, trace, warn};
impl PagedKvCache {
/// Evict a page using configured policy
pub async fn evict_page(&self) -> InferenceResult<()> {
match self.config.eviction_policy {
EvictionPolicy::LRU => self.evict_lru_page().await,
EvictionPolicy::LFU => self.evict_lfu_page().await,
EvictionPolicy::FIFO => self.evict_fifo_page().await,
EvictionPolicy::Random => self.evict_random_page().await,
}
}
/// Evict least recently used page
pub(crate) async fn evict_lru_page(&self) -> InferenceResult<()> {
let lru_page_id = {
let lru_tracker = self.lru_tracker.lock().await;
lru_tracker.get_lru()
};
if let Some(page_id) = lru_page_id {
self.remove_page(page_id).await?;
let mut stats = self.stats.write().await;
stats.evictions_lru += 1;
}
Ok(())
}
/// Evict least frequently used page
pub(crate) async fn evict_lfu_page(&self) -> InferenceResult<()> {
let mut lfu_page_id = None;
let mut min_access_count = usize::MAX;
{
let gpu_pages = self.gpu_pages.read().await;
for (page_id, page) in gpu_pages.iter() {
if page.access_count < min_access_count {
min_access_count = page.access_count;
lfu_page_id = Some(*page_id);
}
}
}
if lfu_page_id.is_none() {
let cpu_pages = self.cpu_pages.read().await;
for (page_id, page) in cpu_pages.iter() {
if page.access_count < min_access_count {
min_access_count = page.access_count;
lfu_page_id = Some(*page_id);
}
}
}
if lfu_page_id.is_none() {
let nvme_pages = self.nvme_pages.read().await;
for (page_id, page) in nvme_pages.iter() {
if page.access_count < min_access_count {
lfu_page_id = Some(*page_id);
}
}
}
if let Some(page_id) = lfu_page_id {
self.remove_page(page_id).await?;
let mut stats = self.stats.write().await;
stats.evictions_lfu += 1;
}
Ok(())
}
/// Evict first in, first out page
pub(crate) async fn evict_fifo_page(&self) -> InferenceResult<()> {
let mut oldest_page_id = None;
let mut oldest_time = Instant::now();
let mut evict_from_tier = MemoryTier::GPU;
{
let gpu_pages = self.gpu_pages.read().await;
for (page_id, page) in gpu_pages.iter() {
if page.created_at < oldest_time {
oldest_time = page.created_at;
oldest_page_id = Some(*page_id);
evict_from_tier = MemoryTier::GPU;
}
}
}
{
let cpu_pages = self.cpu_pages.read().await;
for (page_id, page) in cpu_pages.iter() {
if page.created_at < oldest_time {
oldest_time = page.created_at;
oldest_page_id = Some(*page_id);
evict_from_tier = MemoryTier::CPU;
}
}
}
{
let nvme_pages = self.nvme_pages.read().await;
for (page_id, page) in nvme_pages.iter() {
if page.created_at < oldest_time {
oldest_page_id = Some(*page_id);
evict_from_tier = MemoryTier::NVME;
}
}
}
if let Some(page_id) = oldest_page_id {
match evict_from_tier {
MemoryTier::GPU => {
self.gpu_pages.write().await.remove(&page_id);
}
MemoryTier::CPU => {
self.cpu_pages.write().await.remove(&page_id);
}
MemoryTier::NVME => {
self.nvme_pages.write().await.remove(&page_id);
}
}
let mut stats = self.stats.write().await;
stats.evictions_due_to_pressure += 1;
trace!(
"FIFO evicted page {} from {:?} tier",
page_id, evict_from_tier
);
}
Ok(())
}
/// Evict random page
pub(crate) async fn evict_random_page(&self) -> InferenceResult<()> {
let mut rng = fastrand::Rng::new();
let mut all_pages = Vec::new();
{
let gpu_pages = self.gpu_pages.read().await;
for page_id in gpu_pages.keys() {
all_pages.push((*page_id, MemoryTier::GPU));
}
}
{
let cpu_pages = self.cpu_pages.read().await;
for page_id in cpu_pages.keys() {
all_pages.push((*page_id, MemoryTier::CPU));
}
}
{
let nvme_pages = self.nvme_pages.read().await;
for page_id in nvme_pages.keys() {
all_pages.push((*page_id, MemoryTier::NVME));
}
}
if !all_pages.is_empty() {
let random_idx = rng.usize(0..all_pages.len());
let (page_id, tier) = all_pages[random_idx];
match tier {
MemoryTier::GPU => {
self.gpu_pages.write().await.remove(&page_id);
}
MemoryTier::CPU => {
self.cpu_pages.write().await.remove(&page_id);
}
MemoryTier::NVME => {
self.nvme_pages.write().await.remove(&page_id);
}
}
let mut stats = self.stats.write().await;
stats.evictions_due_to_pressure += 1;
trace!("Random evicted page {} from {:?} tier", page_id, tier);
}
Ok(())
}
/// Remove page from cache
pub(crate) async fn remove_page(&self, page_id: PageId) -> InferenceResult<()> {
let tier = {
let page_info = self.page_info.read().await;
page_info.get(&page_id).map(|info| info.tier)
};
if let Some(tier) = tier {
match tier {
MemoryTier::GPU => {
self.gpu_pages.write().await.remove(&page_id);
let mut stats = self.stats.write().await;
stats.gpu_pages_used = stats.gpu_pages_used.saturating_sub(1);
}
MemoryTier::CPU => {
self.cpu_pages.write().await.remove(&page_id);
let mut stats = self.stats.write().await;
stats.cpu_pages_used = stats.cpu_pages_used.saturating_sub(1);
}
MemoryTier::NVME => {
self.nvme_pages.write().await.remove(&page_id);
let mut stats = self.stats.write().await;
stats.nvme_pages_used = stats.nvme_pages_used.saturating_sub(1);
}
}
{
self.page_info.write().await.remove(&page_id);
}
{
let mut lru_tracker = self.lru_tracker.lock().await;
lru_tracker.remove(page_id);
}
trace!("Removed page {} from tier {}", page_id, tier);
}
Ok(())
}
/// Persist cache to disk
pub async fn persist_to_disk(&self) -> InferenceResult<()> {
if !self.config.persistence_enabled {
return Ok(());
}
use std::fs;
let cache_dir = std::path::Path::new("./cache_persistence");
if !cache_dir.exists() {
fs::create_dir_all(cache_dir).map_err(|e| {
InferenceError::kv_cache_error(
"persist",
format!("Failed to create cache directory: {e}"),
)
})?;
}
{
let gpu_pages = self.gpu_pages.read().await;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let metadata = serde_json::json!({
"page_count": gpu_pages.len(),
"timestamp": timestamp,
"cache_type": "gpu"
});
let metadata_path = cache_dir.join("gpu_pages_metadata.json");
let metadata_str = serde_json::to_string_pretty(&metadata).map_err(|e| {
InferenceError::kv_cache_error("persist", format!("Failed to serialize: {e}"))
})?;
fs::write(metadata_path, metadata_str).map_err(|e| {
InferenceError::kv_cache_error("persist", format!("Failed to write: {e}"))
})?;
}
{
let stats = self.stats.read().await;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let stats_json = serde_json::json!({
"cache_hits": stats.cache_hits,
"cache_misses": stats.cache_misses,
"total_evictions": stats.evictions_lru + stats.evictions_lfu + stats.evictions_due_to_pressure,
"timestamp": timestamp
});
let stats_path = cache_dir.join("cache_stats.json");
let stats_str = serde_json::to_string_pretty(&stats_json).map_err(|e| {
InferenceError::kv_cache_error("persist", format!("Failed to serialize: {e}"))
})?;
fs::write(stats_path, stats_str).map_err(|e| {
InferenceError::kv_cache_error("persist", format!("Failed to write: {e}"))
})?;
}
info!("Cache persistence completed to {:?}", cache_dir);
Ok(())
}
/// Recover cache from disk
pub async fn recover_from_disk(&mut self) -> InferenceResult<()> {
if !self.config.persistence_enabled {
return Ok(());
}
use std::fs;
let cache_dir = std::path::Path::new("./cache_persistence");
if !cache_dir.exists() {
info!("No cache persistence directory found, starting with empty cache");
return Ok(());
}
let stats_path = cache_dir.join("cache_stats.json");
if stats_path.exists() {
match fs::read_to_string(&stats_path) {
Ok(stats_json) => match serde_json::from_str::<serde_json::Value>(&stats_json) {
Ok(stats_data) => {
let mut stats = self.stats.write().await;
if let Some(hits) = stats_data["cache_hits"].as_u64() {
stats.cache_hits = hits as usize;
}
if let Some(misses) = stats_data["cache_misses"].as_u64() {
stats.cache_misses = misses as usize;
}
if let Some(evictions) = stats_data["total_evictions"].as_u64() {
stats.evictions_due_to_pressure = evictions as usize;
}
info!(
"Recovered cache statistics: {} hits, {} misses",
stats.cache_hits, stats.cache_misses
);
}
Err(e) => warn!("Failed to parse cache stats: {}", e),
},
Err(e) => warn!("Failed to read cache stats: {}", e),
}
}
info!("Cache recovery completed from {:?}", cache_dir);
Ok(())
}
}