#![allow(clippy::type_complexity)]
mod common;
use nuvec_core::ops;
use nuvec_core::*;
fn bits(a: &Array) -> Vec<u64> {
match a.dtype().category() {
Category::Float => a
.astype(DType::F32)
.to_vec::<f32>()
.unwrap()
.into_iter()
.map(|v| u64::from(v.to_bits()))
.collect(),
_ => a
.astype(DType::I64)
.to_vec::<i64>()
.unwrap()
.into_iter()
.map(|v| v as u64)
.collect(),
}
}
fn devices() -> Vec<Device> {
let mut d = vec![Device::cpu()];
if common::require_gpu().is_some() {
d.push(Device::gpu(0));
}
d
}
#[test]
fn a_copy_survives_in_place_updates_of_the_original() {
for device in devices() {
with_stream(device, || {
for dtype in [DType::F32, DType::F16, DType::BF16, DType::U32] {
let base = || ops::arange(0, 48, 1).astype(dtype).reshape([6, 8]);
let layouts: Vec<(&str, Box<dyn Fn() -> Array>)> = vec![
("contiguous", Box::new(base)),
("transposed", Box::new(move || base().t())),
("sliced", Box::new(move || base().i((1..5, 2..)))),
(
"broadcast",
Box::new(move || ops::arange(0, 8, 1).astype(dtype).broadcast_to([6, 8])),
),
("scalar", Box::new(move || Array::from(5u32).astype(dtype))),
];
for (name, make) in &layouts {
let x = make().contiguous();
x.eval().unwrap();
let want = bits(&x);
let y = make().copy();
let y2 = x.copy();
eval(&[&y, &y2]).unwrap();
let x = if x.ndim() == 0 {
x.at(Ellipsis).set(Array::from(9u32).astype(dtype))
} else {
x.at(0).set(Array::from(9u32).astype(dtype))
};
x.eval().unwrap();
assert_ne!(bits(&x), want, "{device:?} {dtype} {name}: updated");
assert_eq!(bits(&y2), want, "{device:?} {dtype} {name}: the copy");
assert_eq!(bits(&y), bits(&make()), "{device:?} {dtype} {name}");
assert_eq!(y.shape(), make().shape());
assert_eq!(y.dtype(), dtype);
}
}
});
}
}
#[test]
fn copy_derivatives_are_the_identity() {
let x = array![[0.5, -1.0, 2.0], [3.0, 0.25, -0.75]];
let f = |x: &Array| x.copy().multiply(x).sin().sum(..);
let g = grad(f)(&x).unwrap();
let want = grad(|x: &Array| x.multiply(x).sin().sum(..))(&x).unwrap();
assert_eq!(bits(&g), bits(&want));
let t = array![[1.0, 0.0, -1.0], [0.5, 2.0, 1.0]];
let (_, jt) = jvp_flat(
|xs: &[Array]| Ok(vec![xs[0].copy()]),
std::slice::from_ref(&x),
std::slice::from_ref(&t),
)
.unwrap();
assert_eq!(bits(&jt[0]), bits(&t));
let (_, vt) = vjp_flat(
|xs: &[Array]| Ok(vec![xs[0].copy()]),
std::slice::from_ref(&x),
std::slice::from_ref(&t),
)
.unwrap();
assert_eq!(bits(&vt[0]), bits(&t));
let batched = vmap_flat(
|xs: &[Array]| Ok(vec![xs[0].copy() * 2.0]),
&[Some(1)],
&[0],
)(std::slice::from_ref(&x))
.unwrap();
assert_eq!(bits(&batched[0]), bits(&(x.t() * 2.0)));
let h = 1e-3;
let v = x.to_vec::<f32>().unwrap();
let gv = g.to_vec::<f32>().unwrap();
for i in 0..v.len() {
let at = |d: f32| {
let mut w = v.clone();
w[i] += d;
f(&Array::from_slice(&w, [2, 3])).item::<f32>().unwrap()
};
let fd = (at(h) - at(-h)) / (2.0 * h);
assert!((fd - gv[i]).abs() < 1e-2, "{i}: {fd} vs {}", gv[i]);
}
}
#[test]
fn resuming_from_a_snapshot_equals_the_uninterrupted_run() {
for device in devices() {
with_stream(device, || {
let (hk, hv, dk, dv, heads) = (1, 2, 32, 16, 2);
let input = |t0: i32, t: i32| {
let n = t * 64;
let base = ops::arange(t0 * 64, t0 * 64 + n, 1).astype(DType::F32);
(base * 0.0137).sin().reshape([1, t, 64])
};
let chunk =
|x: &Array, state: &Array, cache: &Array, t0: i32| -> (Array, Array, Array) {
let t = x.shape()[1];
let q = x.i((.., .., ..hk * dk)).reshape([1, t, hk, dk]) * 0.1;
let k = x.i((.., .., ..hk * dk)).reshape([1, t, hk, dk]).cos() * 0.1;
let v = x.i((.., .., 32..32 + hv * dv)).reshape([1, t, hv, dv]);
let g = x.i((.., .., ..hv)).sigmoid();
let beta = x.i((.., .., 2..2 + hv)).sigmoid();
let (y, state) = fast::gated_delta(&q, &k, &v, &g, &beta, state);
let h = y.reshape([1, t, heads, dv]).transpose([0, 2, 1, 3]);
let cache = cache.clone().at((.., .., t0..t0 + t)).set(&h);
let keys = cache.i((.., .., ..t0 + t));
let out = fast::scaled_dot_product_attention(
&h,
&keys,
&keys,
0.25,
&fast::AttentionOptions::causal(),
);
(out, state, cache)
};
let state0 = ops::zeros([1, hv, dv, dk], DType::F32);
let cache0 = ops::zeros([1, heads, 40, dv], DType::F32);
let (o1, s1, c1) = chunk(&input(0, 13), &state0, &cache0, 0);
let (o2, _, _) = chunk(&input(13, 11), &s1, &c1, 13);
let want = (bits(&o1), bits(&o2));
let (o1b, s1b, c1b) = chunk(&input(0, 13), &state0, &cache0, 0);
let (snap_state, snap_cache) = (s1b.copy(), c1b.copy());
eval(&[&o1b, &snap_state, &snap_cache]).unwrap();
let first = bits(&o1b);
drop((o1b, s1b, c1b));
let (o2b, _, _) = chunk(&input(13, 11), &snap_state, &snap_cache, 13);
assert_eq!((first, bits(&o2b)), want, "{device:?}");
});
}
}