#![cfg(target_os = "macos")]
use nuvec_core::*;
fn gpu_available() -> bool {
let ok = !cfg!(miri) && on(Device::gpu(0), || (&Array::scalar(1.0f32) + 1.0).eval()).is_ok();
if !ok {
eprintln!("skipped: no Metal device");
}
ok
}
fn on<R>(device: Device, f: impl FnOnce() -> R) -> R {
let previous = default_stream();
set_default_stream(Stream::default_for(device));
let r = f();
set_default_stream(previous);
r
}
static PEAK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn gpu_peak_since_reset() -> usize {
nuvec_gpu::Device::shared().map_or(0, |d| {
let peak = d.memory().peak;
d.reset_peak();
peak
})
}
#[test]
fn slice_update_writes_in_place_when_it_owns_its_base() {
if !gpu_available() {
return;
}
let _serial = PEAK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
on(Device::gpu(0), || {
let n = 4 << 20; let row = |i: usize| [SliceArg::from(i as isize..i as isize + 1), SliceArg::full()];
let mut a = zeros(&[64, n / 64], DType::F32).unwrap();
a.eval().unwrap();
gpu_peak_since_reset();
let resident = nuvec_gpu::Device::shared().map_or(0, |d| d.memory().active);
for i in 0..64 {
let update = Array::full(&[1, n / 64], i as f32).unwrap();
a = slice_update(&a, &update, &row(i)).unwrap();
a.eval().unwrap();
}
let peak = gpu_peak_since_reset();
assert!(
peak < resident + n * 4 / 2,
"peak {peak}, before {resident}"
);
let v = a.to_vec::<f32>().unwrap();
assert!((0..64).all(|i| v[i * n / 64] == i as f32 && v[(i + 1) * n / 64 - 1] == i as f32));
});
}
#[test]
fn slice_update_writes_in_place_into_bases_still_in_flight() {
if !gpu_available() {
return;
}
let _serial = PEAK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
on(Device::gpu(0), || {
let n = 4 << 20; let row = |i: usize| [SliceArg::from(i as isize..i as isize + 1), SliceArg::full()];
let mut a = zeros(&[64, n / 64], DType::F32).unwrap();
a.eval().unwrap();
let big = Array::full(&[16 << 20], 1.0f32 / (16 << 20) as f32).unwrap();
big.eval().unwrap();
gpu_peak_since_reset();
let resident = nuvec_gpu::Device::shared().map_or(0, |d| d.memory().active);
let mut token: Option<Array> = None;
for i in 0..64 {
let update = Array::full(&[1, n / 64], i as f32).unwrap();
a = slice_update(&a, &update, &row(i)).unwrap();
let busy = sum(&big, &[], false).unwrap();
let next = add(sum(&slice(&a, &row(i)).unwrap(), &[], false).unwrap(), busy).unwrap();
async_eval(&[a.clone(), next.clone()]).unwrap();
if let Some(t) = token.replace(next) {
let want = (i - 1) as f32 * (n / 64) as f32 + 1.0;
assert!((t.item::<f32>().unwrap() - want).abs() < 1.0);
}
}
let peak = gpu_peak_since_reset();
assert!(
peak < resident + n * 4 / 2,
"peak {peak}, before {resident}"
);
let v = a.to_vec::<f32>().unwrap();
assert!((0..64).all(|i| v[i * n / 64] == i as f32 && v[(i + 1) * n / 64 - 1] == i as f32));
});
}
#[test]
fn slice_update_never_changes_arrays_that_are_still_held() {
if !gpu_available() {
return;
}
on(Device::gpu(0), || {
let base = Array::from_slice(&[0.0f32; 8], &[8]).unwrap();
base.eval().unwrap();
let view = slice(&base, &[SliceArg::from(0..4)]).unwrap();
let one = Array::from_slice(&[1.0f32; 2], &[2]).unwrap();
let b = slice_update(&base, &one, &[SliceArg::from(2..4)]).unwrap();
assert_eq!(b.to_vec::<f32>().unwrap(), [0., 0., 1., 1., 0., 0., 0., 0.]);
assert_eq!(base.to_vec::<f32>().unwrap(), [0.0; 8]);
assert_eq!(view.to_vec::<f32>().unwrap(), [0.0; 4]);
let mut chained = zeros(&[16], DType::F32).unwrap();
for i in 0..16 {
let v = Array::full(&[1], i as f32).unwrap();
chained =
slice_update(&chained, &v, &[SliceArg::from(i as isize..i as isize + 1)]).unwrap();
async_eval(std::slice::from_ref(&chained)).unwrap();
}
let expected: Vec<f32> = (0..16).map(|i| i as f32).collect();
assert_eq!(chained.to_vec::<f32>().unwrap(), expected);
});
}
#[test]
fn slice_update_differentiates_on_the_gpu() {
if !gpu_available() {
return;
}
on(Device::gpu(0), || {
let f = |x: &Array| -> Result<Array> {
let part = multiply(&slice(x, &[SliceArg::from(0..2)])?, 3.0)?;
let y = slice_update(x, &part, &[SliceArg::from(2..4)])?;
sum(&multiply(&y, &y)?, &[], false)
};
let x = Array::from_slice(&[1.0f32, 2.0, 3.0, 4.0], &[4]).unwrap();
let g = grad(f)(&x).unwrap().to_vec::<f32>().unwrap();
assert_eq!(g, [20.0, 40.0, 0.0, 0.0]);
});
}