use super::matmul::{float16_matmul, float16_row_dot, float16_row_dot_portable, Float16Kind};
fn values(n: usize, seed: u64) -> Vec<f32> {
let mut s = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
(0..n)
.map(|_| {
s = s
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
((s >> 40) as f32 / (1u64 << 24) as f32) * 4.0 - 2.0
})
.collect()
}
fn encode(kind: Float16Kind, w: &[f32]) -> Vec<u8> {
w.iter()
.flat_map(|&v| match kind {
Float16Kind::F16 => half::f16::from_f32(v).to_bits().to_le_bytes(),
Float16Kind::Bf16 => half::bf16::from_f32(v).to_bits().to_le_bytes(),
})
.collect()
}
fn decode(kind: Float16Kind, b: [u8; 2]) -> f32 {
let bits = u16::from_le_bytes(b);
match kind {
Float16Kind::F16 => half::f16::from_bits(bits).to_f32(),
Float16Kind::Bf16 => half::bf16::from_bits(bits).to_f32(),
}
}
fn reference(kind: Float16Kind, row: &[u8], x: &[f32]) -> (f64, f64) {
row.chunks_exact(2)
.zip(x)
.fold((0.0, 0.0), |(dot, mag), (b, &xv)| {
let p = f64::from(decode(kind, [b[0], b[1]])) * f64::from(xv);
(dot + p, mag + p.abs())
})
}
const LENGTHS: [usize; 16] = [
0, 1, 7, 8, 9, 31, 32, 33, 63, 64, 65, 100, 129, 896, 4864, 4867,
];
fn assert_matches_reference(kind: Float16Kind, dot: fn(Float16Kind, &[u8], &[f32]) -> f32) {
for (k, &n) in LENGTHS.iter().enumerate() {
let row = encode(kind, &values(n, 2 * k as u64 + 1));
let x = values(n, 2 * k as u64 + 2);
let (exact, mag) = reference(kind, &row, &x);
let got = f64::from(dot(kind, &row, &x));
assert!(
(got - exact).abs() <= 1e-5 * mag + 1e-6,
"{kind:?} n={n}: got {got}, exact {exact}, |w*x| sum {mag}"
);
}
}
#[test]
fn test_3076_f16_row_dot_matches_the_f64_reference_at_every_length() {
assert_matches_reference(Float16Kind::F16, float16_row_dot);
}
#[test]
fn test_3076_bf16_row_dot_matches_the_f64_reference_at_every_length() {
assert_matches_reference(Float16Kind::Bf16, float16_row_dot);
}
#[test]
fn test_3076_portable_path_matches_the_f64_reference_at_every_length() {
assert_matches_reference(Float16Kind::F16, float16_row_dot_portable);
assert_matches_reference(Float16Kind::Bf16, float16_row_dot_portable);
}
#[test]
fn test_3076_specials_propagate_through_every_lane_position() {
for kind in [Float16Kind::F16, Float16Kind::Bf16] {
let (nan, inf) = match kind {
Float16Kind::F16 => (0x7E00u16, 0x7C00u16),
Float16Kind::Bf16 => (0x7FC0u16, 0x7F80u16),
};
for pos in [3usize, 37, 42] {
let mut row = encode(kind, &vec![0.5; 43]);
row[2 * pos..2 * pos + 2].copy_from_slice(&nan.to_le_bytes());
assert!(
float16_row_dot(kind, &row, &vec![1.0; 43]).is_nan(),
"{kind:?} NaN at {pos}"
);
row[2 * pos..2 * pos + 2].copy_from_slice(&inf.to_le_bytes());
let got = float16_row_dot(kind, &row, &vec![1.0; 43]);
assert!(
got.is_infinite() && got > 0.0,
"{kind:?} +inf at {pos}: {got}"
);
}
}
let row: Vec<u8> = std::iter::repeat_n(0x0001u16.to_le_bytes(), 40)
.flatten()
.collect();
let got = float16_row_dot(Float16Kind::F16, &row, &vec![(1u32 << 20) as f32; 40]);
assert!((got - 2.5).abs() < 1e-6, "F16 subnormals: {got}");
}
#[test]
fn test_3076_element_count_is_whole_elements_of_the_shorter_operand() {
let kind = Float16Kind::F16;
let row = encode(kind, &[1.0; 20]);
let mut odd = row.clone();
odd.push(0x3C); assert_eq!(float16_row_dot(kind, &odd, &[2.0; 20]), 40.0);
assert_eq!(float16_row_dot(kind, &row, &[2.0; 9]), 18.0);
assert_eq!(float16_row_dot(kind, &row[..10], &[2.0; 20]), 10.0);
}
fn pre_3076_float16_matmul(
kind: Float16Kind,
input: &[f32],
data: &[u8],
in_dim: usize,
out_dim: usize,
seq_len: usize,
) -> Vec<f64> {
let mut out = Vec::with_capacity(seq_len * out_dim);
for s in 0..seq_len {
let x = &input[s * in_dim..(s + 1) * in_dim];
for row in 0..out_dim {
let mut sum = 0.0f64;
for (col, &xv) in x.iter().enumerate() {
let offset = row * in_dim * 2 + col * 2;
if offset + 1 < data.len() {
sum +=
f64::from(decode(kind, [data[offset], data[offset + 1]])) * f64::from(xv);
}
}
out.push(sum);
}
}
out
}
#[test]
fn test_3076_float16_matmul_matches_the_pre_3076_loop_including_a_truncated_buffer() {
let (in_dim, out_dim, seq_len) = (37, 5, 3);
let input = values(in_dim * seq_len, 7);
for kind in [Float16Kind::F16, Float16Kind::Bf16] {
let full = encode(kind, &values(in_dim * out_dim, 8));
for data in [&full[..], &full[..full.len() - 2 * in_dim - 21]] {
let got = float16_matmul(&input, data, in_dim, out_dim, seq_len, kind);
let want = pre_3076_float16_matmul(kind, &input, data, in_dim, out_dim, seq_len);
assert_eq!(got.len(), want.len());
for (i, (&g, &w)) in got.iter().zip(&want).enumerate() {
assert!(
(f64::from(g) - w).abs() <= 1e-5 * w.abs().max(1.0),
"{kind:?} len={} out[{i}]: got {g}, want {w}",
data.len()
);
}
}
}
}