use crate::model::HostBuf;
use crate::Engine;
use memmap2::Mmap;
use std::sync::Arc;
#[derive(Clone, Copy, Debug)]
pub struct MemBudget {
pub free_vram: usize,
pub free_pinnable_ram: usize,
}
impl MemBudget {
pub fn probe(e: &Engine) -> Result<Self, Box<dyn std::error::Error>> {
let (free_vram, _total) = e.ctx().mem_get_info()?; let avail = read_meminfo_kb("MemAvailable")? * 1024; let frac = std::env::var("MEMRA_SPILL_PINNED_FRAC")
.ok()
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(0.60);
Ok(MemBudget {
free_vram,
free_pinnable_ram: (avail as f64 * frac) as usize,
})
}
}
fn read_meminfo_kb(key: &str) -> Result<usize, Box<dyn std::error::Error>> {
let s = std::fs::read_to_string("/proc/meminfo")?;
for line in s.lines() {
if let Some(rest) = line.strip_prefix(key) {
let rest = rest.trim_start_matches(':').trim();
let kb: usize = rest
.split_whitespace()
.next()
.ok_or("malformed /proc/meminfo line")?
.parse()?;
return Ok(kb);
}
}
Err(format!("/proc/meminfo: key {key} not found").into())
}
#[inline]
pub fn disk_tier_enabled() -> bool {
std::env::var("MEMRA_SPILL_DISK").is_ok()
}
pub struct SpillCtx {
pub file_map: Arc<Mmap>,
pub file: Arc<std::fs::File>,
pub pinned_remaining: usize,
pub n_pinned: usize,
pub n_mmap: usize,
pub mmap_bytes: usize,
}
impl SpillCtx {
pub fn open(
g: &memra_gguf::GgufFile,
budget: &MemBudget,
) -> Result<Self, Box<dyn std::error::Error>> {
let file = g.opened_file().clone();
let map = unsafe { Mmap::map(file.as_ref())? };
let _ = memra_gguf::source::apply_expert_mmap_advice(&map);
Ok(SpillCtx {
file_map: Arc::new(map),
file,
pinned_remaining: budget.free_pinnable_ram,
n_pinned: 0,
n_mmap: 0,
mmap_bytes: 0,
})
}
}
pub fn place_expert(
ctx: &mut SpillCtx,
e: &Engine,
raw: &[u8],
file_off: usize,
) -> Result<HostBuf, Box<dyn std::error::Error>> {
let len = raw.len();
if ctx.pinned_remaining >= len {
ctx.pinned_remaining -= len;
ctx.n_pinned += 1;
let mut p = unsafe { e.ctx().alloc_pinned::<u8>(len)? };
{
let dst = p.as_mut_slice()?;
dst.copy_from_slice(raw);
}
let base = p.as_ptr()? as *const u8;
Ok(HostBuf::Pinned {
slice: std::sync::Arc::new(p),
base,
len,
})
} else {
ctx.n_mmap += 1;
ctx.mmap_bytes += len;
Ok(HostBuf::Mmap {
map: ctx.file_map.clone(),
file: ctx.file.clone(),
off: file_off,
len,
})
}
}
pub struct SpillBlock {
pub host: HostBuf,
pub qtype: i32,
pub in_f: usize,
pub out_f: usize,
pub row_bytes: usize,
}
impl SpillBlock {
#[inline]
pub fn bytes(&self) -> &[u8] {
self.host.as_bytes()
}
}
pub struct Tiered {
pub host: crate::model::HostExps, pub slots: crate::moe_cache::MoeSlotCache, }
#[cfg(all(test, unix))]
mod tests {
use super::{MemBudget, SpillCtx};
use memra_gguf::{GgufFile, GGUF_MAGIC};
#[test]
fn spill_ctx_keeps_parsed_gguf_inode_after_path_replacement() {
let path =
std::env::temp_dir().join(format!("memra-spill-inode-{}.gguf", std::process::id()));
let mut original = Vec::new();
original.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
original.extend_from_slice(&3u32.to_le_bytes());
original.extend_from_slice(&0i64.to_le_bytes());
original.extend_from_slice(&0i64.to_le_bytes());
original.resize(32, 0);
std::fs::write(&path, &original).unwrap();
let gguf = GgufFile::open(&path).unwrap();
std::fs::remove_file(&path).unwrap();
std::fs::write(&path, vec![0xA5u8; original.len()]).unwrap();
let budget = MemBudget {
free_vram: 0,
free_pinnable_ram: 0,
};
let spill = SpillCtx::open(&gguf, &budget).unwrap();
assert!(std::sync::Arc::ptr_eq(&spill.file, gguf.opened_file()));
assert_eq!(&spill.file_map[..], original.as_slice());
assert_eq!(std::fs::read(&path).unwrap(), vec![0xA5u8; original.len()]);
std::fs::remove_file(path).ok();
}
}