1#[allow(clippy::if_same_then_else)]
23pub static FP8_E4M3_LUT: [f32; 256] = {
24 let mut table = [0.0f32; 256];
25 let mut i: u32 = 0;
26 while i < 256 {
27 let bits = i as u8;
28 let sign = (bits >> 7) & 1;
29 let exp = (bits >> 3) & 0x0F;
30 let mantissa = bits & 0x07;
31
32 let val = if exp == 0 && mantissa == 0 {
33 0.0f32
34 } else if exp == 0x0F && mantissa == 0x07 {
35 0.0f32
36 } else if exp == 0 {
37 (mantissa as f32) * (0.015625f32 / 8.0)
39 } else {
40 let f32_exp = (exp as u32 + 120) << 23;
44 let f32_mant = (mantissa as u32) << 20;
45 f32::from_bits(f32_exp | f32_mant)
46 };
47
48 table[i as usize] = if sign == 1 { -val } else { val };
49 i += 1;
50 }
51 table
52};
53
54#[inline(always)]
56pub fn fp8_e4m3_to_f32(bits: u8) -> f32 {
57 FP8_E4M3_LUT[bits as usize]
58}
59
60#[inline(always)]
82pub fn f32_to_bf16(val: f32) -> u16 {
83 static DISABLE_RNE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
84 if *DISABLE_RNE.get_or_init(|| std::env::var("METRALE_DISABLE_RNE").is_ok()) {
85 return (val.to_bits() >> 16) as u16;
86 }
87 let bits = val.to_bits();
88 if val.is_nan() {
89 let sign = ((bits >> 16) & 0x8000) as u16;
90 return sign | 0x7FC0;
91 }
92 let lsb = (bits >> 16) & 1;
93 let rounding_bias = 0x7FFFu32 + lsb;
94 (bits.wrapping_add(rounding_bias) >> 16) as u16
95}
96
97#[inline(always)]
100pub fn bf16_bytes_to_f32(bytes: [u8; 2]) -> f32 {
101 let bits = u16::from_le_bytes(bytes);
102 f32::from_bits((bits as u32) << 16)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn fp8_lut_reference_values() {
111 assert_eq!(fp8_e4m3_to_f32(0x00).to_bits(), 0x0000_0000);
112 assert_eq!(fp8_e4m3_to_f32(0x80).to_bits(), 0x8000_0000);
113 assert_eq!(fp8_e4m3_to_f32(0x38), 1.0);
114 assert_eq!(fp8_e4m3_to_f32(0xB8), -1.0);
115 assert_eq!(fp8_e4m3_to_f32(0x3C), 1.5);
116 assert_eq!(fp8_e4m3_to_f32(0x7E), 448.0);
117 assert_eq!(fp8_e4m3_to_f32(0xFE), -448.0);
118 assert_eq!(fp8_e4m3_to_f32(0x7F).to_bits(), 0x0000_0000);
119 assert_eq!(fp8_e4m3_to_f32(0xFF).to_bits(), 0x8000_0000);
120
121 let eps = 1e-10;
123 assert!((fp8_e4m3_to_f32(0x01) - 0.001953125).abs() < eps);
124 assert!((fp8_e4m3_to_f32(0x07) - 0.013671875).abs() < eps);
125 }
126
127 #[test]
128 #[allow(clippy::if_same_then_else)]
129 fn fp8_lut_matches_ocp_values_and_metrale_nan_policy_for_all_bytes() {
130 for i in 0u16..256 {
134 let bits = i as u8;
135 let sign = (bits >> 7) & 1;
136 let exp = (bits >> 3) & 0x0F;
137 let mant = bits & 0x07;
138
139 let magnitude = if exp == 0x0F && mant == 0x07 {
140 0.0f32
141 } else if exp == 0 && mant == 0 {
142 0.0f32
143 } else if exp == 0 {
144 (mant as f32 / 8.0) * 2.0f32.powi(-6)
145 } else {
146 (1.0 + mant as f32 / 8.0) * 2.0f32.powi(exp as i32 - 7)
147 };
148 let expected = if sign == 1 { -magnitude } else { magnitude };
149 let actual = fp8_e4m3_to_f32(bits);
150 assert_eq!(
151 actual.to_bits(),
152 expected.to_bits(),
153 "LUT mismatch at {i:#04x}: expected {expected:?}, got {actual:?}"
154 );
155 }
156 }
157
158 #[test]
161 fn f32_to_bf16_is_rne_byte_exact() {
162 fn convert(bits: u32) -> u16 {
163 f32_to_bf16(f32::from_bits(bits))
164 }
165
166 assert_eq!(convert(0x3F80_0800), 0x3F80, "1.0 + below-half-ULP -> 1.0");
168 assert_eq!(
171 convert(0x3F80_8000),
172 0x3F80,
173 "1.0 + exact-half-ULP, LSB=0 -> 1.0 (even)"
174 );
175 assert_eq!(
177 convert(0x3F80_8001),
178 0x3F81,
179 "1.0 + above-half-ULP -> next bf16 (truncation would give 0x3F80)"
180 );
181 assert_eq!(
184 convert(0x3F81_8000),
185 0x3F82,
186 "1.0078125 + exact-half-ULP, LSB=1 -> 1.015625"
187 );
188 assert_eq!(convert(0xBF80_8001), 0xBF81, "negative round up");
190 assert_eq!(convert(0x0000_0000), 0x0000, "+0.0");
191 assert_eq!(convert(0x8000_0000), 0x8000, "-0.0");
192 assert_eq!(convert(0x0000_0001), 0x0000, "tiny subnormal -> 0");
195 assert_eq!(convert(0x7F80_0000), 0x7F80, "+inf");
196 assert_eq!(convert(0xFF80_0000), 0xFF80, "-inf");
197 assert_eq!(
199 convert(0x7F7F_FFFF),
200 0x7F80,
201 "max-finite f32 rounds to +inf bf16"
202 );
203 assert_eq!(convert(0x7FC0_0000), 0x7FC0, "qnan +");
206 assert_eq!(convert(0xFFC0_0000), 0xFFC0, "qnan -");
207 assert_eq!(convert(0x7F80_0001), 0x7FC0, "snan + -> qnan +");
208 }
209
210 #[test]
213 fn f32_to_bf16_matches_pytorch() {
214 let cases: &[(u32, u16, &str)] = &[
215 (0x3F80_0000, 0x3F80, "1.0"),
216 (0x4000_0000, 0x4000, "2.0"),
217 (0xC000_0000, 0xC000, "-2.0"),
218 (0x3FC0_0000, 0x3FC0, "1.5"),
219 (
220 0x3DCC_CCCD,
221 0x3DCD,
222 "0.1 -> RNE rounds UP to 0x3DCD (trunc=0x3DCC)",
223 ),
224 (0x3F4C_CCCD, 0x3F4D, "0.8 -> RNE rounds UP to 0x3F4D"),
225 (0x40C9_0FDB, 0x40C9, "pi -> truncates (next bit < half)"),
226 (0x402D_F854, 0x402E, "e -> RNE rounds UP (next bit > half)"),
227 (0x4490_0000, 0x4490, "1152.0"),
228 (0x3727_C5AC, 0x3728, "1e-5 -> RNE rounds UP"),
229 ];
230 for (f32_bits, want, desc) in cases {
231 let got = f32_to_bf16(f32::from_bits(*f32_bits));
232 assert_eq!(
233 got, *want,
234 "f32={f32_bits:#010x} ({desc}): want bf16={want:#06x}, got {got:#06x}"
235 );
236 }
237 }
238
239 #[test]
240 fn disable_rne_presence_uses_truncation() {
241 let this = format!(
242 "{}::disable_rne_presence_uses_truncation",
243 module_path!()
244 .split_once("::")
245 .map_or(module_path!(), |(_, p)| p)
246 );
247 const CHILD_MARKER: &str = "METRALE_NUMERIC_RNE_CHILD";
248
249 if std::env::var_os(CHILD_MARKER).is_some() {
250 assert_eq!(
251 f32_to_bf16(f32::from_bits(0x3F80_8001)),
252 0x3F80,
253 "the escape hatch must truncate an above-half-ULP value"
254 );
255 return;
256 }
257
258 for value in ["0", "1"] {
259 let output = std::process::Command::new(std::env::current_exe().unwrap())
260 .args(["--exact", this.as_str()])
261 .env(CHILD_MARKER, "1")
262 .env("METRALE_DISABLE_RNE", value)
263 .output()
264 .unwrap();
265 assert!(
266 output.status.success(),
267 "METRALE_DISABLE_RNE={value} child failed:\n{}",
268 String::from_utf8_lossy(&output.stdout)
269 );
270 assert!(
271 String::from_utf8_lossy(&output.stdout).contains("1 passed"),
272 "the child must run exactly this test: {}",
273 String::from_utf8_lossy(&output.stdout)
274 );
275 }
276 }
277
278 #[test]
279 fn bf16_widening_is_byte_exact_for_every_pattern() {
280 for bits in 0u32..=0xFFFF {
281 let bf16 = bits as u16;
282 let widened = bf16_bytes_to_f32(bf16.to_le_bytes());
283 assert_eq!(
284 widened.to_bits(),
285 bits << 16,
286 "widening moved bits for bf16 {bf16:#06x}"
287 );
288 }
289 }
290
291 #[test]
292 fn bf16_narrowing_preserves_every_non_nan_bf16_value() {
293 for bits in 0u32..=0xFFFF {
294 let bf16 = bits as u16;
295 let widened = f32::from_bits(bits << 16);
296 if widened.is_nan() {
297 continue;
298 }
299 assert_eq!(
300 f32_to_bf16(widened),
301 bf16,
302 "round trip failed for bf16 {bf16:#06x}"
303 );
304 }
305 }
306}