pub fn prism_f32_to_f16_rne(x: f32) -> u16 {
let bits = x.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let exp = ((bits >> 23) & 0xff) as i32;
let frac = bits & 0x007f_ffff;
if exp == 0xff {
return if frac == 0 {
sign | 0x7c00
} else {
sign | 0x7c00 | 0x0200 | ((frac >> 13) as u16 & 0x01ff)
};
}
let unbiased = exp - 127;
if unbiased < -14 {
let scaled = (x.abs() as f64) * 16_777_216.0;
let floor = scaled.floor();
let frac_part = scaled - floor;
let mut q = floor as u64;
if frac_part > 0.5 || (frac_part == 0.5 && (q & 1) != 0) {
q += 1;
}
return if q >= 1024 {
sign | 0x0400 } else {
sign | q as u16
};
}
if unbiased > 15 {
return sign | 0x7c00;
}
let significand = frac | 0x0080_0000;
let mut half_frac = (significand >> 13) & 0x03ff;
let discarded = significand & 0x1fff;
if discarded > 0x1000 || (discarded == 0x1000 && (half_frac & 1) != 0) {
half_frac += 1;
}
let mut half_exp = unbiased + 15;
if half_frac == 0x0400 {
half_frac = 0;
half_exp += 1;
}
if half_exp >= 31 {
sign | 0x7c00
} else {
sign | ((half_exp as u16) << 10) | (half_frac as u16 & 0x03ff)
}
}
pub fn fwht_f32(values: &mut [f32], block: usize) -> Result<(), String> {
if block == 0 || !block.is_power_of_two() || values.len() % block != 0 {
return Err(format!(
"FWHT block {block} must be a power of two dividing {}",
values.len()
));
}
let mut width = 1usize;
while width < block {
let stride = width * 2;
for base in (0..values.len()).step_by(block) {
for i in (0..block).step_by(stride) {
for j in 0..width {
let a = base + i + j;
let b = a + width;
let x = values[a];
let y = values[b];
values[a] = x + y;
values[b] = x - y;
}
}
}
width = stride;
}
let inv = (block as f32).sqrt().recip();
for x in values {
*x *= inv;
}
Ok(())
}
pub fn signed_fwht_forward(values: &mut [f32], signs: &[f32], block: usize) -> Result<(), String> {
if signs.len() != values.len() {
return Err(format!(
"FWHT signs length {} != activation width {}",
signs.len(),
values.len()
));
}
for (x, &s) in values.iter_mut().zip(signs) {
*x *= s;
}
fwht_f32(values, block)
}
pub fn signed_fwht_inverse(values: &mut [f32], signs: &[f32], block: usize) -> Result<(), String> {
if signs.len() != values.len() {
return Err(format!(
"FWHT signs length {} != embedding width {}",
signs.len(),
values.len()
));
}
fwht_f32(values, block)?;
for (x, &s) in values.iter_mut().zip(signs) {
*x *= s;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn reference_fwht(values: &[f32], block: usize) -> Vec<f32> {
assert!(block.is_power_of_two() && values.len() % block == 0);
let inv = 1.0f64 / (block as f64).sqrt();
let mut out = vec![0.0f32; values.len()];
for base in (0..values.len()).step_by(block) {
for row in 0..block {
let mut sum = 0.0f64;
for col in 0..block {
let sign = if (row & col).count_ones() & 1 == 0 {
1.0
} else {
-1.0
};
sum += sign * values[base + col] as f64;
}
out[base + row] = (sum * inv) as f32;
}
}
out
}
fn patterned(width: usize, salt: usize) -> Vec<f32> {
(0..width)
.map(|i| {
let a = ((i.wrapping_mul(7919) + salt * 313) % 4093) as f32;
let b = ((i.wrapping_mul(3571) + salt * 97) % 1021) as f32;
(a - 2046.0) / 257.0 + (b - 510.0) / 4096.0
})
.collect()
}
fn signed_reference(values: &[f32], signs: &[f32], block: usize) -> Vec<f32> {
let signed: Vec<f32> = values
.iter()
.zip(signs)
.map(|(&x, &s)| x * s)
.collect();
reference_fwht(&signed, block)
}
#[test]
fn normalized_fwht_is_its_own_inverse() {
let signs = [1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0];
let mut x = (0..8).map(|i| i as f32 * 0.25 - 1.0).collect::<Vec<_>>();
let original = x.clone();
signed_fwht_forward(&mut x, &signs, 8).unwrap();
signed_fwht_inverse(&mut x, &signs, 8).unwrap();
for (a, b) in x.iter().zip(original) {
assert!((a - b).abs() < 2e-6, "{a} != {b}");
}
}
#[test]
fn block_boundaries_are_independent() {
let mut x = vec![0.0; 16];
x[3] = 1.0;
fwht_f32(&mut x, 8).unwrap();
assert!(x[8..].iter().all(|v| *v == 0.0));
assert!((x[..8].iter().map(|v| v * v).sum::<f32>() - 1.0).abs() < 1e-6);
}
#[test]
fn f32_fwht_matches_independent_reference_at_prism_widths() {
for &(width, salt) in &[(1024usize, 3usize), (5120, 5), (6144, 7), (17408, 11)] {
let input = patterned(width, salt);
let signs: Vec<f32> = (0..width)
.map(|i| if (i * 17 + i / 7 + salt) & 1 == 0 { 1.0 } else { -1.0 })
.collect();
let want = signed_reference(&input, &signs, 1024);
let mut got = input.clone();
signed_fwht_forward(&mut got, &signs, 1024).unwrap();
let (mut num, mut den, mut max_rel) = (0.0f64, 0.0f64, 0.0f64);
for (&a, &b) in got.iter().zip(&want) {
let d = a as f64 - b as f64;
num += d * d;
den += (b as f64) * (b as f64);
max_rel = max_rel.max(d.abs() / (b.abs() as f64).max(1e-6));
}
let rel_rms = (num / den.max(1e-30)).sqrt();
assert!(
rel_rms <= 1e-5 && max_rel <= 1e-3,
"width {width}: independent FWHT drift rel_rms={rel_rms:.3e} max_rel={max_rel:.3e}"
);
signed_fwht_inverse(&mut got, &signs, 1024).unwrap();
let roundtrip = input
.iter()
.zip(&got)
.map(|(&a, &b)| ((a - b) as f64).powi(2))
.sum::<f64>()
.sqrt();
assert!(roundtrip <= 2e-3, "width {width}: FWHT roundtrip rms={roundtrip:.3e}");
}
}
#[test]
fn signed_fwht_reference_covers_impulses_and_half_boundaries() {
let width = 1024usize;
let signs: Vec<f32> = (0..width)
.map(|i| if (i * 13) & 3 == 0 { -1.0 } else { 1.0 })
.collect();
let mut input = vec![0.0f32; width];
for &i in &[0usize, 1, 31, 511, 1023] {
input[i] = if i & 1 == 0 { 1.0 } else { -0.5 };
}
input[127] = f32::from_bits(1);
input[255] = f32::from_bits(0x3380_0000); input[767] = 65504.0;
let want = signed_reference(&input, &signs, 1024);
let mut got = input.clone();
signed_fwht_forward(&mut got, &signs, 1024).unwrap();
let rms = got
.iter()
.zip(&want)
.map(|(&a, &b)| ((a - b) as f64).powi(2))
.sum::<f64>()
.sqrt();
assert!(rms <= 2e-4, "FWHT impulse/boundary rms={rms:.3e}");
for &v in &want {
let h = crate::quant::f32_to_f16(v);
let back = crate::quant::f16_to_f32(h);
assert!(back.is_finite(), "f16 boundary produced non-finite {v}");
}
}
#[test]
fn prism_f16_rne_covers_zero_ties_subnormals_and_overflow() {
assert_eq!(prism_f32_to_f16_rne(0.0), 0x0000);
assert_eq!(prism_f32_to_f16_rne(-0.0), 0x8000);
assert_eq!(prism_f32_to_f16_rne(2f32.powi(-25)), 0x0000);
assert_eq!(prism_f32_to_f16_rne(-2f32.powi(-25)), 0x8000);
assert_eq!(prism_f32_to_f16_rne(3f32 * 2f32.powi(-25)), 0x0002);
assert_eq!(prism_f32_to_f16_rne(2f32.powi(-24)), 0x0001);
assert_eq!(prism_f32_to_f16_rne(f32::from_bits(1)), 0x0000);
assert_eq!(prism_f32_to_f16_rne(2f32.powi(-14)), 0x0400);
assert_eq!(prism_f32_to_f16_rne(65504.0), 0x7bff);
assert_eq!(prism_f32_to_f16_rne(65520.0), 0x7c00);
assert_eq!(prism_f32_to_f16_rne(-65520.0), 0xfc00);
assert_eq!(prism_f32_to_f16_rne(f32::INFINITY), 0x7c00);
assert_eq!(prism_f32_to_f16_rne(f32::NEG_INFINITY), 0xfc00);
assert!((prism_f32_to_f16_rne(f32::NAN) & 0x7c00) == 0x7c00);
}
}