FP8: E4M3, E5M2, and Why There Are Two

The Serving Stack
by The Serving Stack··7 min read·
NUMBER FORMATS · 050010101011101000SIGNEXPONENTMANTISSA

An FP8 checkpoint of Qwen3-8B is half the size of the BF16 original, and on GPUs with FP8 tensor cores it computes at twice the rate (NVIDIA lists 3,958 FP8 TFLOPS on the H100 against 1,979 for BF16, both with sparsity). But "FP8" is not one format. It is two, with different bit splits, and both need a helper to work at all. This piece explains both, and measures them on a real model against the other 8-bit option, INT8.

Two encodings

With 8 bits, the split between exponent and mantissa matters a lot, so the standard defines two:

E4M3 E5M2
Bits: sign, exponent, mantissa 1, 4, 3 1, 5, 2
Largest value 448 57,344
Smallest normal value 0.016 (2⁻⁶) 0.000061 (2⁻¹⁴)
Values per power of two 8 4
Infinity none yes

Two number lines on a log scale with every positive value of each format. E4M3: 126 values from 2^-9 to 448, 8 per power of two. E5M2: 123 values from 2^-16 to 57,344, 4 per power of two.Two number lines on a log scale with every positive value of each format. E4M3: 126 values from 2^-9 to 448, 8 per power of two. E5M2: 123 values from 2^-16 to 57,344, 4 per power of two.

E5M2 follows the usual IEEE rules, with infinity and NaN. E4M3 bends them to gain range: it gives up infinity and keeps a single NaN pattern, which frees enough codes to reach 448 instead of 240. The paper that defined both formats (Micikevicius et al., 2022) says how to use them: "E4M3 for weight and activation tensors, and E5M2 for gradient tensors". Gradients span a wider range and tolerate coarser steps; weights and activations need the extra mantissa bit. For inference, that means E4M3 almost everywhere.

FP8 needs a scale

448 is a small maximum, and Qwen3-8B's weights are small numbers, typically a few hundredths, down in the part of E4M3 where it has few values. Cast them to E4M3 directly and the result is poor: of 6.95 billion weights, 352 million (5%) become zero and 2.8 billion (40%) land in the subnormal range below 0.016, where E4M3 has only 7 values in all.

So FP8 always comes with a scale, as integers do: divide the tensor by a scale so that its largest value lands near 448, store the FP8 values and the scale, multiply back when computing. The FP8 paper compares it to the loss scaling of FP16 training, and leaves the choice of scale to software: "the general idea is to choose a scaling factor such that the maximum magnitude in the tensor becomes close to the maximum" of the format.

FP8 against INT8, on a real model

Both formats are 8 bits with a scale. Which is more accurate? I quantized all of Qwen3-8B's linear-layer weights each way, with one scale per tensor and one per row:

Bar chart of error on all of Qwen3-8B's linear-layer weights. INT8: 7.63% with one scale per tensor, 1.05% per row. FP8 E4M3: 2.65% and 2.64%. FP8 E5M2: 5.27% and 5.26%.Bar chart of error on all of Qwen3-8B's linear-layer weights. INT8: 7.63% with one scale per tensor, 1.05% per row. FP8 E4M3: 2.65% and 2.64%. FP8 E5M2: 5.27% and 5.26%.

Two results:

  • FP8 does not care who shares the scale. E4M3 gives 2.65% error with one scale for a whole matrix and 2.64% with one per row. A float's steps grow with the value, so the error stays a roughly fixed fraction of each weight, whatever the scale. INT8's evenly spaced steps are the opposite: one scale per tensor gives 7.6%, one per row 1%.
  • With a fine enough scale, INT8 is more precise. 8 bits of evenly spaced integers resolve a row of weights better than 3 mantissa bits do. E5M2, with only 2 mantissa bits, has twice E4M3's error.

Activations tell a different story. They change with every input, so their scales are computed on the fly, often one per tensor or one per token, and they contain outliers, like the "massive activations" of about 12,000 measured in FP16 vs. BF16. The same measurement on the inputs of every linear layer, over three prompts:

Activations One scale per tensor One scale per token
INT8 5.75% 4.01%
FP8 E4M3 1.54% 1.36%
FP8 E5M2 2.93% 2.59%

Here FP8 wins: E4M3's error is about a quarter of INT8's with one scale per tensor, and a third with one per token. A few huge values set the scale, INT8's evenly spaced steps become coarse for everything else, and FP8's relative precision does not. That robustness is a large part of FP8's appeal when activations are quantized too, even though INT8 is more precise on the weights alone.

FP8 in practice

Checkpoints. Qwen's own Qwen3-8B-FP8 stores weights in E4M3 with one scale per block of 128 × 128 weights, and computes activation scales on the fly ("dynamic"). Red Hat AI's Qwen3-8B-FP8-dynamic uses one scale per output channel for weights and one per token for activations. Red Hat's leaves the output layer (lm_head) in BF16. Either loads in vLLM like any model.

Without a checkpoint. vLLM can quantize a BF16 model to FP8 while loading it, with --quantization fp8, and offers variants by scale granularity (per tensor, per channel, per 128 block). Since the error barely depends on granularity, the simplest one is usually fine, which is a large part of FP8's appeal: no calibration data, no special tooling.

The KV cache. --kv-cache-dtype fp8 stores the attention cache in E4M3 (fp8_e5m2 is the other option), halving its memory. The quantization series measures what it does to quality.

Hardware. FP8 tensor cores arrived with NVIDIA's Hopper and Ada generations and are in every NVIDIA data-center GPU since. On GPUs without FP8 hardware, vLLM falls back to its Marlin kernel for "weight-only FP8 quantization" (compute capability 7.5 or higher): the weights stay FP8 in memory and the math runs in 16 bits, which saves memory but not compute. The hardware piece of this series lists which GPU does what. And recall from TF32 and Accumulation that Hopper's FP8 matmuls accumulate with about 14 bits, which DeepSeek worked around by promoting partial sums to FP32 every 128 products.

What to take away

  • FP8 is two formats: E4M3 (448 max, 3 mantissa bits) for weights and activations, E5M2 (57,344 max, 2 bits) for gradients.
  • FP8 needs a scale: cast directly, 40% of Qwen3-8B's weights would land in E4M3's subnormal range.
  • FP8's error is relative, so it barely depends on how many values share a scale (2.6% on Qwen3-8B's weights either way); INT8 needs a scale per row to reach 1%.
  • Real FP8 checkpoints use E4M3 with block or per-channel scales for weights and dynamic scales for activations.

The next piece goes below 8 bits, where one scale per tensor is no longer enough even for floats: the microscaling formats MXFP8, MXFP6, and MXFP4.

The Serving Stack
The Serving Stack

Inference engineering and AI in production — depth-first, no hype.

Inference Notes

Once a week: what changed in serving AI models, and what it means if you build, run or pay for them, in about five minutes.

Related Articles