use std::sync::{Mutex, MutexGuard, PoisonError};
use nuvec::{DType, Device, Stream, memory, metal, ops, with_stream};
fn serial() -> MutexGuard<'static, ()> {
static LOCK: Mutex<()> = Mutex::new(());
LOCK.lock().unwrap_or_else(PoisonError::into_inner)
}
#[test]
fn memory_functions_track_the_allocator() {
let _serial = serial();
assert!(memory::physical_memory() > 0);
if let Some(info) = metal::device_info() {
assert_eq!(memory::physical_memory(), info.physical_memory);
}
if !metal::is_available() {
assert_eq!(memory::stats(), memory::MemoryStats::default());
assert_eq!(memory::set_memory_limit(1 << 20), 0);
assert_eq!(memory::set_wired_limit(1 << 20), Ok(0));
assert!(metal::device_info().is_none());
assert!(!metal::nax().is_active());
return;
}
let info = metal::device_info().unwrap();
assert!(!info.name.is_empty() && info.recommended_working_set > 0);
let before = memory::active_memory();
with_stream(Stream::default_for(Device::gpu(0)), || {
let x = ops::zeros([64 << 20], DType::U8);
x.eval().unwrap();
assert!(memory::active_memory() >= before + (64 << 20));
assert!(memory::peak_memory() >= memory::active_memory());
assert_eq!(memory::active_memory(), memory::stats().active_with_mapped);
drop(x);
});
assert!(memory::cache_memory() >= 64 << 20);
memory::clear_cache();
assert_eq!(memory::cache_memory(), 0);
memory::reset_peak_memory();
assert_eq!(memory::peak_memory(), memory::active_memory());
let limit = memory::stats().memory_limit;
assert_eq!(memory::set_memory_limit(limit / 2), limit);
assert_eq!(memory::set_memory_limit(limit), limit / 2);
let cache = memory::set_cache_limit(1 << 20);
assert_eq!(memory::set_cache_limit(cache), 1 << 20);
let e = memory::set_wired_limit(info.recommended_working_set as usize + 1).unwrap_err();
assert!(matches!(e, nuvec::Error::InvalidArgument(_)), "{e}");
}
#[test]
fn an_allocation_over_the_limit_is_out_of_memory() {
let _serial = serial();
if !metal::is_available() {
return;
}
let previous = memory::set_memory_limit(memory::active_memory() + (1 << 20));
let e = with_stream(Stream::default_for(Device::gpu(0)), || {
ops::zeros([256 << 20], DType::U8).eval()
})
.unwrap_err();
memory::set_memory_limit(previous);
assert!(e.is_oom(), "{e}");
}
#[test]
fn work_after_clear_cache_reuses_its_own_intermediates() {
let _serial = serial();
if !metal::is_available() {
return;
}
with_stream(Stream::default_for(Device::gpu(0)), || {
let chain = || {
let mut x = ops::ones([1024], DType::F32);
for _ in 0..600 {
x = x.astype(DType::F16).astype(DType::F32);
}
x
};
let busy = || {
let a = ops::ones([2048, 2048], DType::F32);
let b = a.matmul(&a).matmul(&a).matmul(&a);
nuvec::async_eval(std::slice::from_ref(&b)).unwrap();
b
};
let b = busy();
chain().eval().unwrap();
b.eval().unwrap();
memory::clear_cache();
let b = busy();
let before = memory::stats().system_allocations;
chain().eval().unwrap();
let made = memory::stats().system_allocations - before;
assert!(made < 180, "{made} new buffers for 1,200 casts");
b.eval().unwrap();
});
}