use rlx_ir::infer::GraphExt;
use rlx_ir::{DType, Graph, Shape};
use rlx_runtime::{Device, Session};
#[test]
fn matmul_on_bf16_activations_is_finite_and_correct() {
let f = DType::F32;
let (m, k, n) = (2usize, 3, 4);
let mut g = Graph::new("t");
let x = g.input("x", Shape::new(&[m, k], f));
let w = g.input("w", Shape::new(&[k, n], f));
let xb = g.cast(x, DType::BF16);
let wb = g.cast(w, DType::BF16);
let y = g.matmul(xb, wb, Shape::new(&[m, n], DType::BF16));
let yf = g.cast(y, f);
g.set_outputs(vec![yf]);
let xv: Vec<f32> = (0..m * k).map(|i| 0.5 + i as f32 * 0.25).collect();
let wv: Vec<f32> = (0..k * n).map(|i| 1.0 - i as f32 * 0.125).collect();
let mut c = Session::new(Device::Cpu).compile(g);
let out = c.run(&[("x", &xv[..]), ("w", &wv[..])]);
assert_eq!(out[0].len(), m * n);
assert!(
out[0].iter().all(|v| v.is_finite()),
"bf16-activation matmul produced non-finite values: {:?}",
out[0]
);
for i in 0..m {
for j in 0..n {
let want: f32 = (0..k).map(|p| xv[i * k + p] * wv[p * n + j]).sum();
let got = out[0][i * n + j];
assert!(
(got - want).abs() <= 1e-2 * want.abs().max(1.0),
"({i},{j}): got {got}, want {want}"
);
}
}
}
#[test]
fn cast_round_trip_through_a_bf16_activation_rounds_rather_than_repacks() {
let f = DType::F32;
let mut g = Graph::new("t");
let x = g.input("x", Shape::new(&[4], f));
let b = g.cast(x, DType::BF16);
let y = g.cast(b, f);
g.set_outputs(vec![y]);
let xv = [1.0f32, 2.0, 3.0, 4.0];
let mut c = Session::new(Device::Cpu).compile(g);
let out = c.run(&[("x", &xv[..])]);
assert_eq!(
out[0], xv,
"these values are exactly representable in bf16, so the round trip \
must be the identity; got {:?}",
out[0]
);
let mut g2 = Graph::new("t2");
let x2 = g2.input("x", Shape::new(&[1], f));
let b2 = g2.cast(x2, DType::BF16);
let z = g2.input("z", Shape::new(&[1], DType::BF16));
let sum = g2.add(b2, z);
let y2 = g2.cast(sum, f);
g2.set_outputs(vec![y2]);
let mut c2 = Session::new(Device::Cpu).compile(g2);
let v = [1.234_567_9f32];
let zero = [0.0f32];
let got = c2.run(&[("x", &v[..]), ("z", &zero[..])])[0][0];
let want = half::bf16::from_f32(v[0]).to_f32();
assert!(
got.is_finite(),
"adding through a bf16 activation produced {got}"
);
assert!(
(got - want).abs() <= 1e-3,
"expected the bf16-rounded value {want} (or close), got {got}"
);
assert!(
(got - v[0]).abs() < 0.01,
"value was corrupted, not rounded: {got} vs {}",
v[0]
);
}
#[test]
fn bf16_param_weights_still_use_the_packed_path() {
let f = DType::F32;
let (m, k, n) = (1usize, 4, 4);
let mut g = Graph::new("t");
let x = g.input("x", Shape::new(&[m, k], f));
let w = g.param("w", Shape::new(&[k, n], DType::BF16));
let y = g.matmul(x, w, Shape::new(&[m, n], f));
g.set_outputs(vec![y]);
let xv = [1.0f32, 2.0, 3.0, 4.0];
let wv: Vec<f32> = (0..k * n).map(|i| 0.25 * (i % 5) as f32).collect();
let mut c = Session::new(Device::Cpu).compile(g);
c.set_param("w", &wv);
let out = c.run(&[("x", &xv[..])]);
assert!(out[0].iter().all(|v| v.is_finite()), "{:?}", out[0]);
for j in 0..n {
let want: f32 = (0..k).map(|p| xv[p] * wv[p * n + j]).sum();
assert!(
(out[0][j] - want).abs() <= 1e-2 * want.abs().max(1.0),
"col {j}: got {}, want {want}",
out[0][j]
);
}
}