atlas_core/
numeric.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Host-side numeric conversions shared by every weight loader: the FP8
4//! E4M3 decode table and the f32 -> BF16 cast.
5//!
6//! This is the single copy. It used to exist twice — once in
7//! `spark-model/src/weight_map/fp8_lut.rs` (live) and once in
8//! `atlas-quant/src/fp8.rs` (unreachable, zero dependents) — with the
9//! byte-exactness tests attached to the copy that never ran. Both crates
10//! already depended on `atlas-core`, so the fix was to move the arithmetic
11//! down here and bring the tests with it.
12//!
13//! Pure arithmetic: no CUDA, no allocation, compiles under every feature
14//! combination. The CUDA-side mirrors are `E4M3_LUT_GMOE` and
15//! `__float2bfloat16_rn` in `kernels/gb10/common/moe_fp8_grouped_gemm.cu`;
16//! they must agree with this file element for element.
17
18/// FP8 E4M3 -> f32 lookup table (256 entries, one per byte value).
19///
20/// OCP FP8 E4M3FN: sign(1) | exponent(4) | mantissa(3), bias = 7. There
21/// are no infinities; `0x7F` / `0xFF` are NaN and max finite is +/-448.0
22/// (exp = 15, mant = 6).
23///
24/// NaN entries decode to `0.0`. A NaN weight should not exist in a
25/// checkpoint, and zero stops one bad byte from poisoning an entire
26/// dequanted tensor — which is what propagating NaN through the loader
27/// would do.
28///
29/// Built at compile time so the hot dequant loop is a single indexed load
30/// with no branches.
31#[allow(clippy::if_same_then_else)]
32pub static FP8_E4M3_LUT: [f32; 256] = {
33    let mut table = [0.0f32; 256];
34    let mut i: u32 = 0;
35    while i < 256 {
36        let bits = i as u8;
37        let sign = (bits >> 7) & 1;
38        let exp = (bits >> 3) & 0x0F;
39        let mantissa = bits & 0x07;
40
41        let val = if exp == 0 && mantissa == 0 {
42            0.0f32
43        } else if exp == 0x0F && mantissa == 0x07 {
44            0.0f32 // NaN -> 0.0
45        } else if exp == 0 {
46            // Subnormal: 2^(-6) * (mantissa / 8).
47            (mantissa as f32) * (0.015625f32 / 8.0)
48        } else {
49            // Normal: 2^(exp-7) * (1 + mantissa/8), assembled directly in
50            // f32 bits — f32 exponent = fp8_exp - 7 + 127 = fp8_exp + 120,
51            // f32 mantissa = fp8_mant << 20 (3 bits left-aligned into 23).
52            let f32_exp = (exp as u32 + 120) << 23;
53            let f32_mant = (mantissa as u32) << 20;
54            f32::from_bits(f32_exp | f32_mant)
55        };
56
57        table[i as usize] = if sign == 1 { -val } else { val };
58        i += 1;
59    }
60    table
61};
62
63/// Decode one FP8 E4M3 byte to f32 (branchless, single array lookup).
64#[inline(always)]
65pub fn fp8_e4m3_to_f32(bits: u8) -> f32 {
66    FP8_E4M3_LUT[bits as usize]
67}
68
69/// Convert f32 to BF16 with IEEE-754 round-to-nearest-even.
70///
71/// Must stay byte-identical to PyTorch's `torch.float32 -> torch.bfloat16`
72/// cast: reference activations and the dequanted-weight snapshots Atlas is
73/// scored against are produced that way, so any drift here shows up as an
74/// accuracy regression with no other symptom.
75///
76/// Phase 2b (FP8 dequant audit, 2026-05-24) replaced truncation
77/// (`bits >> 16`) with ties-to-even. Truncation is biased toward zero and
78/// the bias accumulated across the 31745 dequanted tensors of
79/// Qwen3.6-35B-FP8 to a mean per-layer cosine of 0.969.
80///
81/// NaN maps to the canonical quiet-NaN pattern with the sign preserved,
82/// which is also what PyTorch does.
83///
84/// `ATLAS_DISABLE_RNE` is a bisect escape hatch that reverts to
85/// truncation. It is a PRESENCE check, not a value check — `=0` disables
86/// RNE just as `=1` does.
87#[inline(always)]
88pub fn f32_to_bf16(val: f32) -> u16 {
89    if std::env::var("ATLAS_DISABLE_RNE").is_ok() {
90        return (val.to_bits() >> 16) as u16;
91    }
92    let bits = val.to_bits();
93    if val.is_nan() {
94        let sign = ((bits >> 16) & 0x8000) as u16;
95        return sign | 0x7FC0;
96    }
97    let lsb = (bits >> 16) & 1;
98    let rounding_bias = 0x7FFFu32 + lsb;
99    (bits.wrapping_add(rounding_bias) >> 16) as u16
100}
101
102/// Widen little-endian BF16 bytes to f32. Exact — BF16 is the top 16 bits
103/// of an f32, so this is a shift, never a rounding.
104#[inline(always)]
105pub fn bf16_bytes_to_f32(bytes: [u8; 2]) -> f32 {
106    let bits = u16::from_le_bytes(bytes);
107    f32::from_bits((bits as u32) << 16)
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn fp8_lut_reference_values() {
116        assert_eq!(fp8_e4m3_to_f32(0x00).to_bits(), 0x0000_0000); // +0
117        assert_eq!(fp8_e4m3_to_f32(0x80).to_bits(), 0x8000_0000); // -0
118        assert_eq!(fp8_e4m3_to_f32(0x38), 1.0); // exp=7, mant=0
119        assert_eq!(fp8_e4m3_to_f32(0xB8), -1.0);
120        assert_eq!(fp8_e4m3_to_f32(0x3C), 1.5); // exp=7, mant=4
121        assert_eq!(fp8_e4m3_to_f32(0x7E), 448.0); // max finite
122        assert_eq!(fp8_e4m3_to_f32(0xFE), -448.0); // min finite
123        assert_eq!(fp8_e4m3_to_f32(0x7F).to_bits(), 0x0000_0000); // NaN -> +0
124        assert_eq!(fp8_e4m3_to_f32(0xFF).to_bits(), 0x8000_0000); // -NaN -> -0
125
126        // Subnormals: 2^(-6) * mant/8.
127        let eps = 1e-10;
128        assert!((fp8_e4m3_to_f32(0x01) - 0.001953125).abs() < eps);
129        assert!((fp8_e4m3_to_f32(0x07) - 0.013671875).abs() < eps);
130    }
131
132    #[test]
133    #[allow(clippy::if_same_then_else)]
134    fn fp8_lut_matches_ocp_values_and_atlas_nan_policy_for_all_bytes() {
135        // Re-derived from the OCP finite-value definition with float math,
136        // independently of the table's bit assembly. Atlas deliberately maps
137        // the two OCP NaN encodings to signed zero, matching its CUDA decoder.
138        for i in 0u16..256 {
139            let bits = i as u8;
140            let sign = (bits >> 7) & 1;
141            let exp = (bits >> 3) & 0x0F;
142            let mant = bits & 0x07;
143
144            let magnitude = if exp == 0x0F && mant == 0x07 {
145                0.0f32
146            } else if exp == 0 && mant == 0 {
147                0.0f32
148            } else if exp == 0 {
149                (mant as f32 / 8.0) * 2.0f32.powi(-6)
150            } else {
151                (1.0 + mant as f32 / 8.0) * 2.0f32.powi(exp as i32 - 7)
152            };
153            let expected = if sign == 1 { -magnitude } else { magnitude };
154            let actual = fp8_e4m3_to_f32(bits);
155            assert_eq!(
156                actual.to_bits(),
157                expected.to_bits(),
158                "LUT mismatch at {i:#04x}: expected {expected:?}, got {actual:?}"
159            );
160        }
161    }
162
163    /// The assertions that separate round-to-nearest-even from
164    /// truncation-toward-zero. Truncation FAILS every "round up" case here.
165    #[test]
166    fn f32_to_bf16_is_rne_byte_exact() {
167        fn convert(bits: u32) -> u16 {
168            f32_to_bf16(f32::from_bits(bits))
169        }
170
171        // Below half-ULP: round DOWN. Truncation agrees.
172        assert_eq!(convert(0x3F80_0800), 0x3F80, "1.0 + below-half-ULP -> 1.0");
173        // Exactly half-ULP, LSB=0: tie -> round to EVEN (down). Does not
174        // distinguish RNE from truncation; kept for the tie coverage.
175        assert_eq!(
176            convert(0x3F80_8000),
177            0x3F80,
178            "1.0 + exact-half-ULP, LSB=0 -> 1.0 (even)"
179        );
180        // Above half-ULP: round UP. Truncation would give 0x3F80.
181        assert_eq!(
182            convert(0x3F80_8001),
183            0x3F81,
184            "1.0 + above-half-ULP -> next bf16 (truncation would give 0x3F80)"
185        );
186        // Exactly half-ULP, LSB=1: tie -> round to EVEN (up). Truncation
187        // would give 0x3F81.
188        assert_eq!(
189            convert(0x3F81_8000),
190            0x3F82,
191            "1.0078125 + exact-half-ULP, LSB=1 -> 1.015625"
192        );
193        // Negative parity: magnitude grows the same way.
194        assert_eq!(convert(0xBF80_8001), 0xBF81, "negative round up");
195        // Zero: exact, no rounding, sign preserved.
196        assert_eq!(convert(0x0000_0000), 0x0000, "+0.0");
197        assert_eq!(convert(0x8000_0000), 0x8000, "-0.0");
198        // Smallest f32 subnormal (2^-149) -> nearest bf16 is 0 (LSB=0 tie).
199        assert_eq!(convert(0x0000_0001), 0x0000, "tiny subnormal -> 0");
200        // Infinities pass through.
201        assert_eq!(convert(0x7F80_0000), 0x7F80, "+inf");
202        assert_eq!(convert(0xFF80_0000), 0xFF80, "-inf");
203        // Max-finite f32 rounds UP to +inf in bf16 — the closest
204        // representable value. PyTorch does the same.
205        assert_eq!(
206            convert(0x7F7F_FFFF),
207            0x7F80,
208            "max-finite f32 rounds to +inf bf16"
209        );
210        // NaN -> canonical quiet NaN, sign preserved; signalling NaN is
211        // quieted rather than passed through.
212        assert_eq!(convert(0x7FC0_0000), 0x7FC0, "qnan +");
213        assert_eq!(convert(0xFFC0_0000), 0xFFC0, "qnan -");
214        assert_eq!(convert(0x7F80_0001), 0x7FC0, "snan + -> qnan +");
215    }
216
217    /// Byte-exact match against values captured from PyTorch 2.9 via
218    /// `torch.tensor([x], dtype=torch.float32).bfloat16()`. If this fails
219    /// after a math change, the converter has drifted from PyTorch's RNE
220    /// and every dequanted weight in the engine is off by a bit.
221    #[test]
222    fn f32_to_bf16_matches_pytorch() {
223        let cases: &[(u32, u16, &str)] = &[
224            (0x3F80_0000, 0x3F80, "1.0"),
225            (0x4000_0000, 0x4000, "2.0"),
226            (0xC000_0000, 0xC000, "-2.0"),
227            (0x3FC0_0000, 0x3FC0, "1.5"),
228            (
229                0x3DCC_CCCD,
230                0x3DCD,
231                "0.1 -> RNE rounds UP to 0x3DCD (trunc=0x3DCC)",
232            ),
233            (0x3F4C_CCCD, 0x3F4D, "0.8 -> RNE rounds UP to 0x3F4D"),
234            (0x40C9_0FDB, 0x40C9, "pi -> truncates (next bit < half)"),
235            (0x402D_F854, 0x402E, "e -> RNE rounds UP (next bit > half)"),
236            (0x4490_0000, 0x4490, "1152.0"),
237            (0x3727_C5AC, 0x3728, "1e-5 -> RNE rounds UP"),
238        ];
239        for (f32_bits, want, desc) in cases {
240            let got = f32_to_bf16(f32::from_bits(*f32_bits));
241            assert_eq!(
242                got, *want,
243                "f32={f32_bits:#010x} ({desc}): want bf16={want:#06x}, got {got:#06x}"
244            );
245        }
246    }
247
248    #[test]
249    fn disable_rne_presence_uses_truncation() {
250        const THIS_TEST: &str = "numeric::tests::disable_rne_presence_uses_truncation";
251        const CHILD_MARKER: &str = "ATLAS_NUMERIC_RNE_CHILD";
252
253        if std::env::var_os(CHILD_MARKER).is_some() {
254            assert_eq!(
255                f32_to_bf16(f32::from_bits(0x3F80_8001)),
256                0x3F80,
257                "the escape hatch must truncate an above-half-ULP value"
258            );
259            return;
260        }
261
262        for value in ["0", "1"] {
263            let output = std::process::Command::new(std::env::current_exe().unwrap())
264                .args(["--exact", THIS_TEST])
265                .env(CHILD_MARKER, "1")
266                .env("ATLAS_DISABLE_RNE", value)
267                .output()
268                .unwrap();
269            assert!(
270                output.status.success(),
271                "ATLAS_DISABLE_RNE={value} child failed:\n{}",
272                String::from_utf8_lossy(&output.stdout)
273            );
274        }
275    }
276
277    #[test]
278    fn bf16_widening_is_byte_exact_for_every_pattern() {
279        for bits in 0u32..=0xFFFF {
280            let bf16 = bits as u16;
281            let widened = bf16_bytes_to_f32(bf16.to_le_bytes());
282            assert_eq!(
283                widened.to_bits(),
284                bits << 16,
285                "widening moved bits for bf16 {bf16:#06x}"
286            );
287        }
288    }
289
290    #[test]
291    fn bf16_narrowing_preserves_every_non_nan_bf16_value() {
292        for bits in 0u32..=0xFFFF {
293            let bf16 = bits as u16;
294            let widened = f32::from_bits(bits << 16);
295            if widened.is_nan() {
296                continue;
297            }
298            assert_eq!(
299                f32_to_bf16(widened),
300                bf16,
301                "round trip failed for bf16 {bf16:#06x}"
302            );
303        }
304    }
305}