FP16 began as an interchange format and grew into arithmetic (Zfh’s story). Google Brain went the other way: training in FP32, they noticed the low 16 mantissa bits rarely mattered — so they cut them off. BFloat16 is literally the top half of an FP32:
| Expo bits | Fraction bits | emax / emin | |
|---|---|---|---|
| FP16 | 5 | 10 | 15 / −14 |
| BF16 | 8 | 7 | 127 / −126 |
| TF32 (NVIDIA, 19 bits + 13 zero pads) | 8 | 10 | 127 / −126 |
| FP32 | 8 | 23 | 127 / −126 |
Behavior — deliberately unexciting
Everything follows the established FP rules: full subnormal support (in these three extensions; future BF16 ops may opt out), signed infinities, sNaN-raises-invalid, canonical qNaN results — 0x7fc0, the truncated FP32 canonical NaN — standard NaN-boxing (checked even for vector-scalar reads of f-registers), all five rounding modes plus DYN, IEEE default exception handling, and tininess detected after rounding, with the unbounded-exponent subtlety: the tininess check rounds as if range were infinite, so underflow can legitimately signal for a value that final-rounds back into normal range.
The three extensions
| Depends on | Provides | |
|---|---|---|
| Zfbfmin | F | Scalar interchange: FCVT.BF16.S / FCVT.S.BF16, plus FLH/FSH/FMV.X.H/FMV.H.X borrowed from ch. 24 (16-bit moves don’t care which format they carry). |
| Zvfbfmin | Zve32f (or V) | Vector converts: vfncvtbf16.f.f.w / vfwcvtbf16.f.f.v. |
| Zvfbfwma | Zfbfmin + Zvfbfmin | vfwmaccbf16 — the widening BF16×BF16 + FP32 dot-product workhorse. |
Only BF16 ↔ FP32 conversions exist — the target use case is “BF16 as compressed FP32”: multiply halves, accumulate in single, convert back. Arithmetic emulates faithfully through FP32 (except fused multiply-add’s 1-ulp RNE/RMM caveat, same as Zfhmin’s), and wider-format conversions chain through FP32 — exactly when widening; via round-to-odd halving steps when narrowing.
Hardware Designer Notes
BF16’s pitch to a hardware designer is the multiplier: 8×8 significand arrays are ~¼ the area of FP16’s 11×11 and ~1/9 of FP32’s 24×24 — which is why a vector unit can afford twice the BF16 lanes. The instructions themselves are on the next page.
Minimal Linux-boot hart MUST
- Treat BF16 values under the full FP contract: subnormals both directions, 0x7fc0 canonicalization, NaN-box checks, all five rounding modes
- Get tininess-after-rounding right — it needs the separate unbounded-exponent rounding in your FP32→BF16 converter
MAY simplify / trap-and-emulate
- Implement FP32→BF16 as round-to-nearest on bit 16 of the FP32 encoding (plus flag logic) — the format was designed so truncation hardware is nearly free
- Skip all three extensions on a Linux-boot core; they matter when the SoC hosts ML inference without a dedicated NPU
Check yourself — the BF16 format
1.What trade does BF16 make relative to FP16, and why did it win in ML training?
2.What is the canonical BF16 NaN, and where does the pattern come from?
3.RISC-V detects tininess AFTER rounding. Why does that require a second, different rounding?