use zyx::{DType, ReduceOp, Scalar, Tape, Tensor, ZyxError};
#[test]
fn sin() -> Result<(), ZyxError> {
let data: [f32; 10] = [-3.285, 0.001, 1.780, 5.675, -8.521, -0.456, 1.215, -3.474, -4.128, -7.657];
let x = Tensor::from(data);
let tape = Tape::new([&x])?;
let z = x.sin();
tape.realize([&z])?;
let zdata: Vec<f32> = z.try_into()?;
for (x, y) in data.iter().zip(zdata) {
assert!(x.sin().is_equal(y), "{} != {y}", x.sin());
}
Ok(())
}
#[test]
fn relu() -> Result<(), ZyxError> {
let data: [f32; 10] = [-3.285, 0.001, 1.780, 5.675, -8.521, -0.456, 1.215, -3.474, -4.128, -7.657];
let x = Tensor::from(data);
let tape = Tape::new([&x])?;
let z = x.relu();
tape.realize([&z])?;
assert_eq!(z, [0.0f32, 0.001, 1.780, 5.675, 0.0, 0.0, 1.215, 0.0, 0.0, 0.0]);
Ok(())
}
#[test]
fn matmul() -> Result<(), ZyxError> {
let x = Tensor::from([[2, 4, 3], [1, 5, 1]]);
let y = Tensor::from([[2, 4], [3, 1], [5, 1]]);
let tape = Tape::new([&x, &y])?;
let z = x.dot(y)?;
tape.realize([&z])?;
assert_eq!(z, [[31, 15], [22, 10]]);
Ok(())
}
#[test]
fn matmul_f32() -> Result<(), ZyxError> {
let x = Tensor::from([[2f32, 4., 3.], [1., 5., 1.]]);
let y = Tensor::from([[2f32, 4.], [3., 1.], [5., 1.]]);
let tape = Tape::new([&x, &y])?;
let z = x.dot(y)?;
tape.realize([&z])?;
assert_eq!(z, [[31f32, 15.], [22., 10.]]);
Ok(())
}
#[test]
fn softmax() -> Result<(), ZyxError> {
let x = Tensor::from([2f32, 4., 3.]);
let tape = Tape::new([&x])?;
let y = x.softmax([])?;
tape.realize([&y])?;
assert_eq!(y, [0.09003056585788726807f32, 0.66524088382720947266, 0.24472846090793609619]);
Ok(())
}
#[test]
fn causal_self_attention() -> Result<(), ZyxError> {
let dtype = DType::F32;
let n_embd = 4;
let n_head = 4;
let c_attn_weight = Tensor::from([
[3, 1, 2, 3, 1, 2, 5, 4, 2, 3, 1, 3],
[1, 1, 2, 3, 1, 2, 5, 4, 2, 3, 1, 3],
[3, 1, 5, 3, 1, 2, 5, 4, 2, 3, 1, 3],
[3, 1, 2, 3, 1, 2, 5, 8, 2, 3, 1, 3],
])
.t()
.cast(dtype);
let x = Tensor::from([[[1, 0, 4, 2], [2, 5, 0, 1], [0, 8, 1, 0], [5, 1, 0, 0]]]).cast(dtype);
let [b, t, c] = x.shape()[..] else {
return Err(ZyxError::ShapeError("x must have exactly 3 dims, b, t, c".into()));
};
let tape = Tape::new([&x, &c_attn_weight])?;
let mut splits = x.dot(c_attn_weight.t())?.split([n_embd, n_embd, n_embd], 2)?;
let mut v = splits.pop().unwrap();
let mut k = splits.pop().unwrap();
let mut q = splits.pop().unwrap();
k = k.reshape([b, t, n_head, c / n_head])?.transpose(1, 2)?;
q = q.reshape([b, t, n_head, c / n_head])?.transpose(1, 2)?;
v = v.reshape([b, t, n_head, c / n_head])?.transpose(1, 2)?;
let mut att = q.dot(k.t())? * (1f32 / (*k.shape().last().unwrap() as f32).sqrt());
att = att.softmax([-1])?;
let mut y = att.dot(v)?;
y = y.transpose(1, 2)?.reshape([b, t, c])?;
tape.realize([&y])?;
assert_eq!(
y,
[[
[18f32, 27., 9., 24.],
[18., 27., 9., 24.],
[18., 27., 9., 24.],
[18., 27., 9., 24.]
]]
);
Ok(())
}
#[test]
fn gather() -> Result<(), ZyxError> {
let x = Tensor::from([10, 20, 30, 40, 50]);
let indices = Tensor::from([0u32, 2, 4, 1]);
let tape = Tape::new([&x, &indices])?;
let gathered = x.gather(0, &indices)?;
tape.realize([&gathered])?;
assert_eq!(gathered, [10, 30, 50, 20]);
Ok(())
}
#[test]
fn promote_and_gradient() -> Result<(), ZyxError> {
let gt = Tensor::randn([2, 4, 1, 1], DType::F32)?;
let tape = Tape::new([>])?;
let a = Tensor::ones([4], DType::F32);
let shape = [2, 4, 1, 1];
let b = a.reshape([1, 4, 1, 1])?;
let c = b.expand(shape)?;
let d = &c + 1e-5f32;
let e = d.rsqrt();
let result = &e + >
let _g = tape.gradient(&result, [>]);
Ok(())
}
#[test]
fn small_net() -> Result<(), ZyxError> {
let w1 = Tensor::randn([3, 4], DType::F32)?;
let b1 = Tensor::randn([3], DType::F32)?;
let w2 = Tensor::randn([2, 3], DType::F32)?;
let b2 = Tensor::randn([2], DType::F32)?;
for _ in 0..3 {
let tape = Tape::new([&w1, &b1, &w2, &b2])?;
let x = Tensor::randn([2, 4], DType::F32)?;
let y = Tensor::from([0u32, 1]);
let h = (x.dot(&w1.t())? + &b1).relu();
let logits = h.dot(&w2.t())? + &b2;
let loss = logits.cross_entropy(y, ReduceOp::Mean)?;
let grads = tape.gradient(&loss, [&w1, &b1, &w2, &b2]);
let lr = 0.01f32;
let new_w1 = &w1 - &grads[0] * lr;
let new_b1 = &b1 - &grads[1] * lr;
let new_w2 = &w2 - &grads[2] * lr;
let new_b2 = &b2 - &grads[3] * lr;
tape.realize([&new_w1, &new_b1, &new_w2, &new_b2])?;
}
Ok(())
}
#[test]
fn tape_caching() -> Result<(), ZyxError> {
let x = Tensor::from([1.0f32, 2.0, 3.0]);
let y = Tensor::randn([3, 3], DType::F32)?;
for _ in 0..10 {
let tape = Tape::new([&x])?;
let z = &x + y.t().sin();
tape.realize([&z])?;
}
Ok(())
}
#[test]
fn tape_matmul() -> Result<(), ZyxError> {
let x = Tensor::from([[1.0f32, 2.0], [3.0, 4.0], [5.0, 6.0]]);
let w = Tensor::from([0.5f32, 1.5, 2.5, 3.5]).reshape([2, 2])?.relu();
for _ in 0..3 {
let tape = Tape::new([&x])?;
let z = x.dot(&w)?.relu();
tape.realize([&z])?;
}
Ok(())
}
#[test]
fn big_matmul() -> Result<(), ZyxError> {
let x = Tensor::rand([256, 392], DType::F32)?;
let w = Tensor::rand([392, 296], DType::F32)?;
let tape = Tape::new([&x, &w])?;
let z = x.dot(&w)?.relu();
tape.realize([&z])?;
let shape = z.shape();
assert_eq!(shape, [256, 296]);
Ok(())
}
#[test]
fn drop_without_realize_params_eager() -> Result<(), ZyxError> {
let x = Tensor::from([1.0f32, 2.0, 3.0]);
{
let _tape = Tape::new([&x])?;
let _z = x.sin();
}
let y = x + 1.0f32;
let data: Vec<f32> = y.try_into()?;
assert_eq!(data, [2.0f32, 3.0, 4.0]);
Ok(())
}
#[test]
#[should_panic(expected = "tape scope has ended")]
fn use_intermediate_after_drop_panics() {
let x = Tensor::from([1.0f32, 2.0, 3.0]);
let z;
{
let _tape = Tape::new([&x]).unwrap();
z = x.sin();
}
let _ = z + 1.0f32;
}
#[test]
fn realize_outputs_eager_leaves_eager_after_drop() -> Result<(), ZyxError> {
let x = Tensor::from([1.0f32, 2.0, 3.0]);
let z;
{
let tape = Tape::new([&x])?;
z = x.sin();
tape.realize([&z])?;
}
let zdata: Vec<f32> = z.try_into()?;
for (a, b) in [1.0f32, 2.0, 3.0].iter().zip(zdata) {
assert!(a.sin().is_equal(b));
}
let y = x + 1.0f32;
let data: Vec<f32> = y.try_into()?;
assert_eq!(data, [2.0f32, 3.0, 4.0]);
Ok(())
}
#[test]
fn realized_tensor_promotes_as_leaf() -> Result<(), ZyxError> {
let x = Tensor::from([1.0f32, 2.0, 3.0]);
let tape = Tape::new([&x])?;
let z = x.relu();
tape.realize([&z])?;
let data: Vec<f32> = z.try_into()?;
assert_eq!(data, [1.0f32, 2.0, 3.0]);
Ok(())
}
#[test]
#[should_panic(expected = "tape scope has ended")]
fn use_frozen_output_panics() {
let x = Tensor::from([1.0f32, 2.0, 3.0]);
let z;
{
let tape = Tape::new([&x]).unwrap();
z = x.relu();
let _frozen = tape.freeze([&z]).unwrap();
}
let _ = z + 1.0f32;
}