use std::collections::HashMap;
use super::error::TieredError;
use super::lru::LruTracker;
use super::stats::TieredStats;
use std::sync::Arc;
#[derive(Debug)]
pub struct CpuLayerWeights {
pub data: Vec<u8>,
pub layout: LayerLayout,
pub size_bytes: usize,
pub is_pinned: bool,
}
#[derive(Debug, Clone)]
pub struct LayerLayout {
pub q_proj: TensorLayout,
pub k_proj: TensorLayout,
pub v_proj: TensorLayout,
pub o_proj: TensorLayout,
pub gate_proj: TensorLayout,
pub up_proj: TensorLayout,
pub down_proj: TensorLayout,
pub input_norm: TensorLayout,
pub post_attn_norm: TensorLayout,
}
#[derive(Debug, Clone, Copy)]
pub struct TensorLayout {
pub offset: usize,
pub size: usize,
pub shape: [usize; 2],
}
impl TensorLayout {
pub fn new(offset: usize, shape: [usize; 2]) -> Self {
let size = shape[0] * shape[1] * 2;
Self {
offset,
size,
shape,
}
}
pub fn new_1d(offset: usize, size: usize) -> Self {
Self {
offset,
size: size * 2, shape: [size, 1],
}
}
}
impl CpuLayerWeights {
pub fn tensor_data(&self, layout: &TensorLayout) -> &[u8] {
&self.data[layout.offset..layout.offset + layout.size]
}
pub fn q_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.q_proj)
}
pub fn k_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.k_proj)
}
pub fn v_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.v_proj)
}
pub fn o_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.o_proj)
}
pub fn gate_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.gate_proj)
}
pub fn up_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.up_proj)
}
pub fn down_proj_data(&self) -> &[u8] {
self.tensor_data(&self.layout.down_proj)
}
pub fn input_norm_data(&self) -> &[u8] {
self.tensor_data(&self.layout.input_norm)
}
pub fn post_attn_norm_data(&self) -> &[u8] {
self.tensor_data(&self.layout.post_attn_norm)
}
}
pub struct RamCache {
layers: HashMap<usize, CpuLayerWeights>,
lru: LruTracker<usize>,
usage: usize,
budget: u64,
use_pinned: bool,
stats: Arc<TieredStats>,
}
impl RamCache {
pub fn new(budget: u64, use_pinned: bool, stats: Arc<TieredStats>) -> Self {
Self {
layers: HashMap::new(),
lru: LruTracker::new(),
usage: 0,
budget,
use_pinned,
stats,
}
}
pub fn contains(&self, layer_idx: usize) -> bool {
self.layers.contains_key(&layer_idx)
}
pub fn peek(&self, layer_idx: usize) -> Option<&CpuLayerWeights> {
self.layers.get(&layer_idx)
}
pub fn get(&mut self, layer_idx: usize) -> Option<&CpuLayerWeights> {
if self.layers.contains_key(&layer_idx) {
self.lru.touch(layer_idx);
self.stats.record_ram_hit();
self.layers.get(&layer_idx)
} else {
None
}
}
pub fn insert(
&mut self,
layer_idx: usize,
weights: CpuLayerWeights,
priority: f32,
) -> Result<(), TieredError> {
let size = weights.size_bytes;
if self.usage + size > self.budget as usize {
return Err(TieredError::ram_alloc(
format!("layer {} doesn't fit", layer_idx),
size as u64,
(self.budget as usize - self.usage) as u64,
));
}
if let Some(old) = self.layers.remove(&layer_idx) {
self.usage -= old.size_bytes;
self.lru.remove(&layer_idx);
}
self.layers.insert(layer_idx, weights);
self.lru.touch_with_priority(layer_idx, priority);
self.usage += size;
Ok(())
}
pub fn remove(&mut self, layer_idx: usize) -> Option<CpuLayerWeights> {
if let Some(layer) = self.layers.remove(&layer_idx) {
self.usage -= layer.size_bytes;
self.lru.remove(&layer_idx);
Some(layer)
} else {
None
}
}
pub fn usage(&self) -> usize {
self.usage
}
pub fn available(&self) -> usize {
(self.budget as usize).saturating_sub(self.usage)
}
pub fn num_layers(&self) -> usize {
self.layers.len()
}
pub fn has_room_for(&self, size: usize) -> bool {
self.usage + size <= self.budget as usize
}
pub fn evict_for_space(&mut self, bytes_needed: u64) -> u64 {
let mut freed = 0u64;
let sizes: HashMap<usize, usize> = self
.layers
.iter()
.map(|(&idx, layer)| (idx, layer.size_bytes))
.collect();
let candidates = self.lru.eviction_candidates_for_size(bytes_needed, |idx| {
sizes.get(idx).copied().unwrap_or(0) as u64
});
for layer_idx in candidates {
if freed >= bytes_needed {
break;
}
if let Some(layer) = self.layers.remove(&layer_idx) {
freed += layer.size_bytes as u64;
self.usage -= layer.size_bytes;
self.lru.remove(&layer_idx);
self.stats.record_ram_eviction(layer.size_bytes as u64);
tracing::debug!(
layer_idx,
freed_mb = layer.size_bytes / (1024 * 1024),
"Evicted layer from RAM"
);
}
}
freed
}
pub fn evict_lru(&mut self) -> Option<(usize, CpuLayerWeights)> {
if let Some(layer_idx) = self.lru.pop_lru() {
if let Some(layer) = self.layers.remove(&layer_idx) {
self.usage -= layer.size_bytes;
self.stats.record_ram_eviction(layer.size_bytes as u64);
return Some((layer_idx, layer));
}
}
None
}
pub fn clear(&mut self) {
self.layers.clear();
self.lru.clear();
self.usage = 0;
}
pub fn budget(&self) -> u64 {
self.budget
}
pub fn uses_pinned_memory(&self) -> bool {
self.use_pinned
}
pub fn layer_indices(&self) -> impl Iterator<Item = usize> + '_ {
self.layers.keys().copied()
}
}
impl std::fmt::Debug for RamCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RamCache")
.field("num_layers", &self.layers.len())
.field("usage_mb", &(self.usage / (1024 * 1024)))
.field("budget_mb", &(self.budget / (1024 * 1024)))
.field("use_pinned", &self.use_pinned)
.finish()
}
}
#[allow(dead_code)]
pub fn allocate_pinned(size: usize) -> Result<Vec<u8>, TieredError> {
Ok(vec![0u8; size])
}
#[allow(dead_code)]
pub fn free_pinned(_data: Vec<u8>) {
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_cache() -> RamCache {
RamCache::new(1024 * 1024 * 1024, false, Arc::new(TieredStats::new()))
}
fn create_dummy_weights(size: usize) -> CpuLayerWeights {
let layout = LayerLayout {
q_proj: TensorLayout::new(0, [4096, 4096]),
k_proj: TensorLayout::new(0, [1024, 4096]),
v_proj: TensorLayout::new(0, [1024, 4096]),
o_proj: TensorLayout::new(0, [4096, 4096]),
gate_proj: TensorLayout::new(0, [11008, 4096]),
up_proj: TensorLayout::new(0, [11008, 4096]),
down_proj: TensorLayout::new(0, [4096, 11008]),
input_norm: TensorLayout::new_1d(0, 4096),
post_attn_norm: TensorLayout::new_1d(0, 4096),
};
CpuLayerWeights {
data: vec![0u8; size],
layout,
size_bytes: size,
is_pinned: false,
}
}
#[test]
fn test_ram_cache_basic() {
let mut cache = create_test_cache();
let weights = create_dummy_weights(1024);
cache.insert(0, weights, 0.5).unwrap();
assert!(cache.contains(0));
assert_eq!(cache.num_layers(), 1);
assert_eq!(cache.usage(), 1024);
}
#[test]
fn test_ram_cache_eviction() {
let mut cache = RamCache::new(2048, false, Arc::new(TieredStats::new()));
cache.insert(0, create_dummy_weights(1024), 0.5).unwrap();
cache.insert(1, create_dummy_weights(1024), 0.3).unwrap();
let freed = cache.evict_for_space(1024);
assert_eq!(freed, 1024);
assert!(cache.contains(0));
assert!(!cache.contains(1));
}
#[test]
fn test_ram_cache_budget() {
let mut cache = RamCache::new(1024, false, Arc::new(TieredStats::new()));
cache.insert(0, create_dummy_weights(512), 0.5).unwrap();
let result = cache.insert(1, create_dummy_weights(1024), 0.5);
assert!(result.is_err());
}
}