use crate::{Network, Tensor};
use super::Bf16;
#[test]
fn every_bit_pattern_round_trips_through_f32() {
for bits in 0..=u16::MAX {
let value = Bf16::from_bits(bits);
let round_tripped = Bf16::from_f32(value.to_f32());
if value.to_f32().is_nan() {
assert!(round_tripped.to_f32().is_nan(), "NaN lost at {bits:#06X}");
continue;
}
assert_eq!(
round_tripped.to_bits(),
bits,
"bit pattern {bits:#06X} did not round-trip"
);
}
}
#[allow(clippy::excessive_precision)]
#[test]
fn rounding_is_nearest_with_ties_to_even() {
assert_eq!(Bf16::from_f32(1.003_906_25), Bf16::from_f32(1.0));
assert_eq!(Bf16::from_f32(1.011_718_75), Bf16::from_f32(1.015_625));
assert_eq!(Bf16::from_f32(1.006), Bf16::from_f32(1.007_812_5));
}
#[test]
fn rounding_carries_into_the_extremes() {
assert_eq!(Bf16::from_f32(f32::MAX).to_f32(), f32::INFINITY);
assert_eq!(Bf16::from_f32(f32::MIN).to_f32(), f32::NEG_INFINITY);
assert_eq!(Bf16::from_f32(-0.0).to_bits(), 0x8000);
assert_eq!(Bf16::from_f32(0.0).to_bits(), 0x0000);
assert!(Bf16::from_f32(f32::NAN).to_f32().is_nan());
}
#[test]
fn counted_is_exact_up_to_256() {
use crate::{Differentiable, Shape};
for count in [0, 1, 100, 255, 256] {
assert_eq!(
Bf16::counted(Shape::scalar(), count).to_f32(),
count as f32,
"count {count} must convert exactly"
);
}
assert_eq!(Bf16::counted(Shape::scalar(), 257).to_f32(), 256.0);
assert_eq!(Bf16::counted(Shape::scalar(), 259).to_f32(), 260.0);
}
#[test]
fn each_operation_rounds_the_f32_result_once() {
let third = Bf16::from_f32(1.0) / Bf16::from_f32(3.0);
assert_eq!(third, Bf16::from_f32(1.0_f32 / 3.0));
let large = Bf16::from_f32(256.0);
assert_eq!(large + Bf16::ONE, large);
}
#[test]
fn everyday_numeric_traits_behave_like_floats() {
assert_eq!(format!("{}", Bf16::from_f32(1.5)), "1.5");
assert_eq!(format!("{}", Bf16::from_f32(-2.0)), "-2");
assert!(Bf16::from_f32(1.0) < Bf16::from_f32(2.0));
assert!(Bf16::from_f32(f32::NAN).partial_cmp(&Bf16::ONE).is_none());
assert_eq!(Bf16::default(), Bf16::ZERO);
assert_eq!(Bf16::from(1.0_f64 / 3.0), Bf16::from_f32(1.0_f32 / 3.0));
assert_eq!(f64::from(Bf16::from_f32(1.5)), 1.5_f64);
}
#[test]
fn negation_flips_only_the_sign_bit() {
assert_eq!(-Bf16::from_f32(1.5), Bf16::from_f32(-1.5));
assert_eq!((-Bf16::ZERO).to_bits(), 0x8000);
assert!((-Bf16::from_f32(f32::NAN)).to_f32().is_nan());
}
#[test]
fn maximum_and_step_answer_exactly() {
use crate::Elementary;
let smaller = Bf16::from_f32(1.5);
let larger = Bf16::from_f32(2.5);
assert_eq!(smaller.maximum(&larger), larger);
assert_eq!(larger.step(&smaller), Bf16::ONE);
assert_eq!(smaller.step(&larger), Bf16::ZERO);
assert_eq!(smaller.step(&smaller), Bf16::ONE);
}
#[test]
fn matmul_accumulates_in_f32_and_rounds_once() {
use crate::Tensorial;
let left = Tensor::new([1, 3], [256.0_f32, 1.0, 1.0].map(Bf16::from_f32).to_vec());
let right = Tensor::new([3, 1], [1.0_f32, 1.0, 1.0].map(Bf16::from_f32).to_vec());
let product = left.matmul(&right);
assert_eq!(product.to_vec(), vec![Bf16::from_f32(258.0)]);
}
#[test]
fn matmul_accumulation_is_representation_independent() {
use crate::{Tensor, Tensorial};
let ones_constant = Tensor::filled([1, 3], Bf16::ONE);
let ones_dense = Tensor::new([1, 3], [1.0_f32; 3].map(Bf16::from_f32).to_vec());
let right = Tensor::new([3, 1], [256.0_f32, 1.0, 1.0].map(Bf16::from_f32).to_vec());
assert_eq!(
ones_constant.matmul(&right).to_vec(),
ones_dense.matmul(&right).to_vec(),
);
assert_eq!(
ones_constant.matmul(&right).to_vec(),
vec![Bf16::from_f32(258.0)]
);
}
#[test]
fn reductions_accumulate_in_f32() {
use crate::Tensorial;
let values = Tensor::new([3], [256.0_f32, 1.0, 1.0].map(Bf16::from_f32).to_vec());
assert_eq!(values.sum().to_vec(), vec![Bf16::from_f32(258.0)]);
let rows = Tensor::new([1, 3], [256.0_f32, 1.0, 1.0].map(Bf16::from_f32).to_vec());
assert_eq!(rows.sum_along(1).to_vec(), vec![Bf16::from_f32(258.0)]);
}
#[test]
fn scatter_accumulates_duplicate_rows_in_f32() {
use crate::Tensorial;
let gradient = Tensor::new([3, 1], [256.0_f32, 1.0, 1.0].map(Bf16::from_f32).to_vec());
let selection = Tensor::selection(vec![0, 0, 0], 1, Bf16::ONE);
let folded = gradient.scatter(&selection, 1);
assert_eq!(folded.to_vec(), vec![Bf16::from_f32(258.0)]);
}
#[test]
fn scalar_networks_differentiate_bf16() {
let network = Network::new();
let x = network.parameter(Bf16::from_f32(1.5));
let loss = x * x;
let run = network.forward();
assert_eq!(*run.of(loss), Bf16::from_f32(2.25));
let gradients = run.backward(loss);
assert_eq!(*gradients.of(x), Bf16::from_f32(3.0));
}
#[test]
fn convert_crosses_the_precision_boundary_exactly() {
let singles = Tensor::new([2, 2], vec![1.0_f32, 0.3, -2.5, 300.0]);
let narrowed: Tensor<Bf16> = singles.convert();
let expected: Vec<Bf16> = singles.iter().map(Bf16::from_f32).collect();
assert_eq!(narrowed.to_vec(), expected);
let widened: Tensor<f32> = narrowed.convert();
let round_tripped: Tensor<Bf16> = widened.convert();
assert_eq!(round_tripped.to_vec(), narrowed.to_vec());
let constant = Tensor::filled([1024], Bf16::ONE);
let widened: Tensor<f32> = constant.convert();
assert!(widened.as_slice().is_none());
assert_eq!(widened.to_vec(), vec![1.0_f32; 1024]);
}
#[test]
fn bf16_gradients_track_f32_within_epsilon() {
use crate::Value;
let weights = Tensor::new([2, 2], vec![0.8_f32, -1.3, 0.4, 2.1]);
let x = Tensor::new([2, 2], vec![0.5_f32, -1.1, 1.9, 0.7]);
let oracle = Network::new();
let oracle_weights = oracle.parameter(weights.clone());
let oracle_x = oracle.leaf(x.clone());
let oracle_loss = oracle_x.matmul(oracle_weights).relu().sum();
let oracle_gradients = oracle.forward().backward(oracle_loss);
let expected = oracle_gradients.of(oracle_weights).to_vec();
let network: Network<Tensor<Bf16>> = Network::new();
let narrowed_weights: Value<'_, Tensor<Bf16>> = network.parameter(weights.convert());
let narrowed_x = network.leaf(x.convert());
let loss = narrowed_x.matmul(narrowed_weights).relu().sum();
let gradients = network.forward().backward(loss);
let epsilon = 7.8125e-3_f32;
for (narrow, wide) in gradients.of(narrowed_weights).iter().zip(expected) {
assert!(
(narrow.to_f32() - wide).abs() <= epsilon * (1.0 + wide.abs()),
"bf16 gradient {narrow:?} strays from the f32 oracle {wide}"
);
}
}
#[test]
fn tensor_networks_differentiate_bf16() {
let network = Network::new();
let elements: Vec<Bf16> = [-2.0, 0.0, 3.0].map(Bf16::from_f32).to_vec();
let x = network.leaf(Tensor::new([3], elements));
let loss = x.abs().sum();
let run = network.forward();
assert_eq!(run.of(loss).to_vec(), vec![Bf16::from_f32(5.0)]);
let gradients = run.backward(loss);
let expected: Vec<Bf16> = [-1.0, 1.0, 1.0].map(Bf16::from_f32).to_vec();
assert_eq!(gradients.of(x).to_vec(), expected);
}