use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Mutex, RwLock};
use super::{HoloInferenceError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MemoryTier {
Vram,
Ram,
Disk,
}
impl MemoryTier {
pub fn priority(&self) -> u8 {
match self {
MemoryTier::Vram => 3,
MemoryTier::Ram => 2,
MemoryTier::Disk => 1,
}
}
}
#[derive(Debug, Clone)]
pub enum FragmentLocation {
Vram {
offset: usize,
size: usize,
device_id: usize,
},
Ram {
ptr: usize,
size: usize,
pinned: bool,
numa_node: i32,
},
Disk {
path: String,
offset: u64,
size: usize,
},
}
impl FragmentLocation {
pub fn tier(&self) -> MemoryTier {
match self {
FragmentLocation::Vram { .. } => MemoryTier::Vram,
FragmentLocation::Ram { .. } => MemoryTier::Ram,
FragmentLocation::Disk { .. } => MemoryTier::Disk,
}
}
pub fn size(&self) -> usize {
match self {
FragmentLocation::Vram { size, .. } => *size,
FragmentLocation::Ram { size, .. } => *size,
FragmentLocation::Disk { size, .. } => *size,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FragmentId {
pub layer: usize,
pub weight_type: u8,
pub fragment_index: u16,
}
impl FragmentId {
pub fn new(layer: usize, weight_type: u8, fragment_index: u16) -> Self {
Self {
layer,
weight_type,
fragment_index,
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryConfig {
pub vram_budget: usize,
pub ram_budget: usize,
pub numa_node: i32,
pub use_pinned_memory: bool,
pub eviction_threshold: f32,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
vram_budget: 20 * 1024 * 1024 * 1024, ram_budget: 64 * 1024 * 1024 * 1024, numa_node: -1,
use_pinned_memory: true,
eviction_threshold: 0.9,
}
}
}
#[derive(Debug, Clone)]
struct LruEntry {
fragment_id: FragmentId,
last_access: u64,
size: usize,
}
pub struct HoloMemoryManager {
config: MemoryConfig,
locations: RwLock<HashMap<FragmentId, FragmentLocation>>,
vram_lru: Mutex<Vec<LruEntry>>,
vram_used: AtomicUsize,
ram_used: AtomicUsize,
access_counter: AtomicU64,
stats: RwLock<MemoryStats>,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryStats {
pub vram_fragments: usize,
pub ram_fragments: usize,
pub disk_fragments: usize,
pub evictions: usize,
pub promotions: usize,
pub bytes_promoted: usize,
pub bytes_evicted: usize,
}
impl HoloMemoryManager {
pub fn new(config: MemoryConfig) -> Self {
Self {
config,
locations: RwLock::new(HashMap::new()),
vram_lru: Mutex::new(Vec::new()),
vram_used: AtomicUsize::new(0),
ram_used: AtomicUsize::new(0),
access_counter: AtomicU64::new(0),
stats: RwLock::new(MemoryStats::default()),
}
}
pub fn vram_used(&self) -> usize {
self.vram_used.load(Ordering::Relaxed)
}
pub fn ram_used(&self) -> usize {
self.ram_used.load(Ordering::Relaxed)
}
pub fn vram_available(&self) -> usize {
self.config.vram_budget.saturating_sub(self.vram_used())
}
pub fn ram_available(&self) -> usize {
self.config.ram_budget.saturating_sub(self.ram_used())
}
pub fn get_location(&self, id: &FragmentId) -> Option<FragmentLocation> {
let locations = self.locations.read().ok()?;
locations.get(id).cloned()
}
pub fn get_tier(&self, id: &FragmentId) -> Option<MemoryTier> {
self.get_location(id).map(|loc| loc.tier())
}
pub fn is_in_vram(&self, id: &FragmentId) -> bool {
self.get_tier(id) == Some(MemoryTier::Vram)
}
pub fn register_fragment(&self, id: FragmentId, location: FragmentLocation) -> Result<()> {
let size = location.size();
let tier = location.tier();
match tier {
MemoryTier::Vram => {
self.vram_used.fetch_add(size, Ordering::Relaxed);
self.add_to_lru(id, size);
},
MemoryTier::Ram => {
self.ram_used.fetch_add(size, Ordering::Relaxed);
},
MemoryTier::Disk => {},
}
let mut locations =
self.locations
.write()
.map_err(|e| HoloInferenceError::MemoryAlloc {
tier,
message: format!("lock poisoned: {}", e),
})?;
locations.insert(id, location);
let mut stats = self
.stats
.write()
.map_err(|_| HoloInferenceError::MemoryAlloc {
tier,
message: "stats lock poisoned".to_string(),
})?;
match tier {
MemoryTier::Vram => stats.vram_fragments += 1,
MemoryTier::Ram => stats.ram_fragments += 1,
MemoryTier::Disk => stats.disk_fragments += 1,
}
Ok(())
}
pub fn touch(&self, id: &FragmentId) {
let timestamp = self.access_counter.fetch_add(1, Ordering::Relaxed);
if let Ok(mut lru) = self.vram_lru.lock() {
if let Some(entry) = lru.iter_mut().find(|e| e.fragment_id == *id) {
entry.last_access = timestamp;
}
}
}
pub fn needs_eviction(&self) -> bool {
let used = self.vram_used() as f32;
let budget = self.config.vram_budget as f32;
used / budget >= self.config.eviction_threshold
}
pub fn get_eviction_candidates(&self, needed_size: usize) -> Vec<FragmentId> {
let mut candidates = Vec::new();
let mut freed = 0usize;
if let Ok(lru) = self.vram_lru.lock() {
let mut sorted: Vec<_> = lru.iter().cloned().collect();
sorted.sort_by_key(|e| e.last_access);
for entry in sorted {
if freed >= needed_size {
break;
}
candidates.push(entry.fragment_id);
freed += entry.size;
}
}
candidates
}
pub fn evict_to_ram(&self, id: &FragmentId) -> Result<FragmentLocation> {
let old_location =
self.get_location(id)
.ok_or_else(|| HoloInferenceError::FragmentNotFound {
layer: id.layer,
fragment_index: id.fragment_index,
})?;
let size = old_location.size();
if self.ram_available() < size {
return Err(HoloInferenceError::InsufficientMemory {
tier: MemoryTier::Ram,
required: size,
available: self.ram_available(),
});
}
let new_location = FragmentLocation::Ram {
ptr: 0, size,
pinned: self.config.use_pinned_memory,
numa_node: self.config.numa_node,
};
self.vram_used.fetch_sub(size, Ordering::Relaxed);
self.ram_used.fetch_add(size, Ordering::Relaxed);
self.remove_from_lru(id);
if let Ok(mut locations) = self.locations.write() {
locations.insert(*id, new_location.clone());
}
if let Ok(mut stats) = self.stats.write() {
stats.vram_fragments = stats.vram_fragments.saturating_sub(1);
stats.ram_fragments += 1;
stats.evictions += 1;
stats.bytes_evicted += size;
}
Ok(new_location)
}
pub fn promote_to_vram(&self, id: &FragmentId) -> Result<FragmentLocation> {
let old_location =
self.get_location(id)
.ok_or_else(|| HoloInferenceError::FragmentNotFound {
layer: id.layer,
fragment_index: id.fragment_index,
})?;
let size = old_location.size();
while self.vram_available() < size {
let candidates = self.get_eviction_candidates(size - self.vram_available());
if candidates.is_empty() {
return Err(HoloInferenceError::InsufficientMemory {
tier: MemoryTier::Vram,
required: size,
available: self.vram_available(),
});
}
for candidate in candidates {
self.evict_to_ram(&candidate)?;
}
}
let new_location = FragmentLocation::Vram {
offset: 0, size,
device_id: 0,
};
self.ram_used.fetch_sub(size, Ordering::Relaxed);
self.vram_used.fetch_add(size, Ordering::Relaxed);
self.add_to_lru(*id, size);
if let Ok(mut locations) = self.locations.write() {
locations.insert(*id, new_location.clone());
}
if let Ok(mut stats) = self.stats.write() {
stats.ram_fragments = stats.ram_fragments.saturating_sub(1);
stats.vram_fragments += 1;
stats.promotions += 1;
stats.bytes_promoted += size;
}
Ok(new_location)
}
fn add_to_lru(&self, id: FragmentId, size: usize) {
let timestamp = self.access_counter.fetch_add(1, Ordering::Relaxed);
if let Ok(mut lru) = self.vram_lru.lock() {
lru.push(LruEntry {
fragment_id: id,
last_access: timestamp,
size,
});
}
}
fn remove_from_lru(&self, id: &FragmentId) {
if let Ok(mut lru) = self.vram_lru.lock() {
lru.retain(|e| e.fragment_id != *id);
}
}
pub fn stats(&self) -> MemoryStats {
self.stats.read().map(|s| s.clone()).unwrap_or_default()
}
pub fn optimal_distribution(
&self,
total_fragments: usize,
fragment_size: usize,
_min_quality: f32,
) -> (usize, usize) {
let max_vram_frags = self.config.vram_budget / fragment_size;
let vram_frags = max_vram_frags.min(total_fragments);
let ram_frags = total_fragments.saturating_sub(vram_frags);
(vram_frags, ram_frags)
}
pub fn get_priority_order(&self, layer: usize, num_fragments: u16) -> Vec<FragmentId> {
(0..num_fragments)
.map(|i| FragmentId::new(layer, 0, i))
.collect()
}
pub fn clear(&self) {
if let Ok(mut locations) = self.locations.write() {
locations.clear();
}
if let Ok(mut lru) = self.vram_lru.lock() {
lru.clear();
}
self.vram_used.store(0, Ordering::Relaxed);
self.ram_used.store(0, Ordering::Relaxed);
if let Ok(mut stats) = self.stats.write() {
*stats = MemoryStats::default();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_config_default() {
let config = MemoryConfig::default();
assert_eq!(config.vram_budget, 20 * 1024 * 1024 * 1024);
assert_eq!(config.ram_budget, 64 * 1024 * 1024 * 1024);
}
#[test]
fn test_fragment_registration() {
let manager = HoloMemoryManager::new(MemoryConfig::default());
let id = FragmentId::new(0, 0, 0);
let location = FragmentLocation::Vram {
offset: 0,
size: 1024,
device_id: 0,
};
manager.register_fragment(id, location).unwrap();
assert!(manager.is_in_vram(&id));
assert_eq!(manager.vram_used(), 1024);
}
#[test]
fn test_lru_tracking() {
let manager = HoloMemoryManager::new(MemoryConfig::default());
for i in 0..5 {
let id = FragmentId::new(0, 0, i);
let location = FragmentLocation::Vram {
offset: i as usize * 1024,
size: 1024,
device_id: 0,
};
manager.register_fragment(id, location).unwrap();
}
manager.touch(&FragmentId::new(0, 0, 2));
let candidates = manager.get_eviction_candidates(1024);
assert!(!candidates.contains(&FragmentId::new(0, 0, 2)));
}
#[test]
fn test_optimal_distribution() {
let config = MemoryConfig {
vram_budget: 10 * 1024 * 1024, ram_budget: 100 * 1024 * 1024, ..Default::default()
};
let manager = HoloMemoryManager::new(config);
let (vram, ram) = manager.optimal_distribution(32, 1024 * 1024, 0.7);
assert_eq!(vram, 10); assert_eq!(ram, 22); }
#[test]
fn test_tier_priority() {
assert!(MemoryTier::Vram.priority() > MemoryTier::Ram.priority());
assert!(MemoryTier::Ram.priority() > MemoryTier::Disk.priority());
}
}