FP8 and NVFP4 for Image Generation: What Changes and When It Pays
FP8 makes diffusion transformers 1.2–1.9x faster with about 40% less memory from Ada GPUs on; NVFP4 adds 1.1–1.7x on Blackwell only. Costs, quality, tooling.
In published benchmarks, FP8 makes the diffusion transformer 1.2–1.9x faster than BF16 and cuts its memory by about 40%, on any NVIDIA GPU from Ada (L40S, RTX 4090) onward. NVFP4 adds another 1.1–1.7x on top of FP8, but only on Blackwell. Using NVIDIA’s end-to-end FLUX.1 [dev] timings on an RTX 5090 at RunPod’s $1.19 an hour, that is $2.21 per 1,000 images in FP8 and $1.27 in FP4. The catch is quality. 4-bit post-training quantization visibly hurts small text and fine detail unless you correct it, so FP4 pays only after it passes an eval on your own prompts.
Key takeaways
- FP8 is the default on Ada, Hopper and Blackwell. The PyTorch team saw “very negligible differences” on FLUX.1 [dev].
- NVFP4 is Blackwell-only, and B200 (compute capability 10.0) and RTX 5090 (12.0) are different targets with different tooling support.
- Without hardware support you save memory, not time. The weights are dequantized back to BF16 for every matmul.
- Quantize the transformer. It takes 96–98% of the time in NVIDIA’s FLUX profiles.
- Many headline speed-ups mix in a compiler change or an offloading baseline. Use like-for-like numbers.
- FP4 post-training quantization degrades small text first. QAT or SVDQuant recover most of it.
- Measure quality with paired, same-seed metrics and targeted prompt sets, not FID on a few hundred images.
This post goes deep on one lever from our pillar guide, How to Cut GPU Inference Costs for Image Generation, which covers the cost model, distillation, compilation and utilization.
What the formats are
Low-precision formats give up bits, so they need a scale factor that maps each group of values into the small range the format can hold. The size of that group, and the format of its scale, is most of what separates the options.
| Format | Element bits | Scale | Largest value | Where it runs in hardware |
|---|---|---|---|---|
| FP8 E4M3 | 4 exponent, 3 mantissa | Per tensor or per row, chosen by software | ±448 | Ada, Hopper, Blackwell |
| FP8 E5M2 | 5 exponent, 2 mantissa | Per tensor or per row | ±57,344 | Ada, Hopper, Blackwell |
| MXFP8 | E4M3 or E5M2 | One E8M0 (power-of-two) scale per 32 values | Same as element | Blackwell (data center, in torchao today) |
| MXFP4 | E2M1 | One E8M0 scale per 32 values | ±6 per element | Blackwell |
| NVFP4 | E2M1 | One FP8 E4M3 scale per 16 values, plus an FP32 per-tensor scale | ±6 per element | Blackwell |
Sources: the FP8 Formats paper, the OCP Microscaling (MX) spec (AMD, Arm, Intel, Meta, Microsoft, NVIDIA, Qualcomm) for MX formats and the value limits, and NVIDIA’s NVFP4 explainer.
What matters in practice:
- E4M3 is the inference format. The FP8 paper notes that inference uses E4M3, and E5M2’s extra range is for training gradients.
- FP4 accuracy lives in the scales. E2M1 has one mantissa bit. NVFP4’s smaller blocks and finer FP8 scale mean one outlier distorts fewer neighbours. At about 4.5 bits per value, NVIDIA puts it 3.5x smaller than FP16 and 1.8x smaller than FP8.
- Speed needs weights and activations quantized (W8A8, W4A4). Weight-only schemes (W4A16, NF4, most GGUF files) shrink the model but compute in BF16, and the Diffusers torchao guide notes they keep “the memory peaks for activation computation”.
Which GPUs support them
| GPU | Architecture | Compute capability | FP8 tensor cores | FP4 tensor cores |
|---|---|---|---|---|
| A100 | Ampere | 8.0 | No | No |
| L4, L40S, RTX 4090 | Ada | 8.9 | Yes | No |
| H100, H200 | Hopper | 9.0 | Yes | No |
| B200, GB200 (B300 is 10.3) | Blackwell data center | 10.0 | Yes | Yes |
| RTX 5090, RTX PRO 6000 Blackwell | Blackwell client and workstation | 12.0 | Yes | Yes |
Compute capabilities are from NVIDIA’s CUDA GPU list. torchao’s FP8 needs compute capability 8.9 or higher, and the Diffusers NVFP4 kernels for Black Forest Labs checkpoints need 10.0 or higher. FP4 comes from Blackwell’s second-generation Transformer Engine.
Three consequences catch teams out:
- No FP8 on A100. Without native support, the weights are “dequantized to
compute_dtypein each forward pass” and activations stay in 16-bit. You save VRAM, not time. - No FP4 on H100. For 4-bit before Blackwell, SVDQuant’s INT4 path is the main option, and the Diffusers Nunchaku Lite kernels don’t support Hopper yet.
- sm_100 and sm_120 differ. torchao’s prototype NVFP4 and MXFP8 configs require SM100+ and don’t mention consumer Blackwell. The BFL path supports 12.0 cards with CUDA 12.9 or newer. Check your exact GPU against your exact library.
What gets quantized in a diffusion pipeline
A pipeline has text encoders, a denoising backbone (a UNet in SDXL, a diffusion transformer in FLUX and SD3.5) and a VAE decoder. Only the backbone runs once per step, so it is the target. NVIDIA measured the transformer at about 96% of FLUX.1 Kontext’s processing time and 98% of FLUX.1 [dev] latency on an RTX 5090.
Inside the transformer, not everything gets the same treatment:
- Linear layers go to FP8 or FP4. That is where the tensor-core speed-up lives.
- Attention mostly stays in FP8, even in “FP4” builds. NVIDIA’s FLUX.1 [dev] FP4 recipe runs multi-head attention in FP8, and its Kontext recipe keeps softmax in FP32. That is why FP4 gains less over FP8 than the bit count suggests.
- Embeddings, output layers and norms stay in higher precision in NVIDIA’s FLUX.1 recipe. NVIDIA’s FLUX.2 example retains layers by name pattern (
embedder,norm_out,proj_out). - Convolutions are awkward. Diffusers’ ModelOpt integration has a
disable_conv_quantizationflag because block quantization “generally don’t work well with convolutional layers”, which matters for UNets and VAEs.
Text encoders are reasonable FP8 candidates for memory: NVIDIA found FLUX.2 robust with an FP8 text encoder. The VAE usually stays in BF16, as in Stability’s SD3.5 Large TensorRT release, which ships only the MMDiT in FP8.
Quantizing only the transformer shrinks the whole pipeline less than you expect. Black Forest Labs measured peak memory for FLUX 3 Image Klein, a 5.6B transformer with a separate 4B text encoder, on a B200 at 1024x1024: 31 GB in BF16, 26 GB in FP8 and 25 GB in NVFP4. Peak memory dropped by less than a fifth. On larger models the picture is better: NVIDIA reports SD3.5 Large going from over 18 GB to 11 GB with FP8, and FLUX.2 [dev] needing 40% less VRAM.
The tooling, as of November 2026
- NVIDIA Model Optimizer plus TensorRT is the most mature path to real FP8 and FP4 kernels. The library was renamed from TensorRT Model Optimizer in December 2025. It does PTQ, QAT and SVDQuant, then you export ONNX and build a TensorRT engine per GPU. For FLUX.2 on B200, NVIDIA used a prototype TensorRT-LLM
visual_genruntime. - torchao keeps you in PyTorch with
torch.compile. Its FP8 config is stable; MXFP8, MXFP4 and NVFP4 are marked prototype. - Diffusers v0.42.1 supports torchao, bitsandbytes, GGUF, Quanto, ModelOpt, SDNQ, AutoRound, Nunchaku Lite and Black Forest Labs’
fp8r/nvfp4checkpoints. Several are load-only, most are memory tools, and the ModelOpt page warns the speed-up “depends on the deployment framework”. - SVDQuant and Nunchaku. SVDQuant (ICLR 2025) absorbs outliers into a small 16-bit low-rank branch so the rest runs in W4A4. Its engine, Nunchaku, runs INT4 on older cards and NVFP4 on RTX 50 for FLUX.1, Qwen-Image and Z-Image.
- Vendor checkpoints. Black Forest Labs ships FP8 and NVFP4 variants of FLUX.2 [klein] and FLUX 3 Image Klein. Start there when they exist.
The quickest real FP8 test on an L40S or H100, which the Diffusers docs recommend pairing with torch.compile:
import torch
from diffusers import FluxPipeline, PipelineQuantizationConfig, TorchAoConfig
from torchao.quantization import Float8DynamicActivationFloat8WeightConfig, PerRow
quant = PipelineQuantizationConfig(
quant_mapping={
"transformer": TorchAoConfig(
Float8DynamicActivationFloat8WeightConfig(granularity=PerRow())
)
}
)
pipe = FluxPipeline.from_pretrained(
"black-forest-labs/FLUX.1-dev",
quantization_config=quant,
dtype=torch.bfloat16,
).to("cuda")
# FP8 matmuls only pay off once the graph is compiled and fused.
pipe.transformer.compile(mode="max-autotune", fullgraph=True)
Published speed and memory numbers
| Model and setup | GPU | 16-bit | FP8 | FP4 | Source |
|---|---|---|---|---|---|
| FLUX.1 [dev], 30 steps, end to end, TensorRT | RTX 5090 | 10.93 s (FP16, low-VRAM mode) | 6.68 s | 3.85 s | NVIDIA |
| FLUX.1 [dev], VRAM, default mode | RTX 5090 | 35.7 GB (BF16) | 24.6 GB | 21.67 GB | same |
| FLUX.1 Kontext [dev], per step, transformer | RTX PRO 6000 | 607 ms | 317 ms | 254 ms | NVIDIA |
| SD3.5 Large, 30 steps, end to end, TensorRT both | H100 | 8.10 s (BF16) | 5.71 s | n/a | Stability model card |
| FLUX.1 [schnell], batch 1, torchao | not stated | 0.418 s | 0.346 s | 0.320 s | torchao docs |
| FLUX.2 [klein], 1024x1024 | RTX 5080/5090 | baseline | up to 1.6x faster | up to 2.7x faster | Black Forest Labs |
These are vendor or maintainer numbers, so treat them as upper bounds. Three things explain the spread:
- The runtime matters as much as the format. NVIDIA’s “2.3x” for SD3.5 Large compares FP8 TensorRT with BF16 PyTorch. With TensorRT on both sides, Stability’s card shows the MMDiT going from 7,945 ms to 5,605 ms: 1.42x for precision, the rest from TensorRT.
- Some baselines were offloading. NVIDIA’s 16-bit runs on the 32 GB RTX 5090 needed low-VRAM mode, so its FLUX.1 [schnell] “speed-up” from 4.43 s to 0.91 s mostly measures not offloading. FP8 to FP4 (1.73x on [dev]) is the clean comparison.
- Fused kernels beat generic ones. torchao’s generic NVFP4 is only 1.08x over its FP8 on FLUX.1 [schnell], and its roofline shows dynamic NVFP4 at 0.46x of BF16 speed on 1024-sized matmuls. Nunchaku’s fused engine reports NVFP4 FLUX.1 [dev] 3.1x faster than BF16 on an RTX 5090. Small models and small resolutions gain less.
Quality: where quantization hurts
- FP4 PTQ hurts small text first. NVIDIA says plain FP4 post-training quantization of FLUX.1 [dev] caused “minor image quality degradation (particularly in small text and figures)”. Image Reward fell from 1.118 to 1.096. FP4 QAT (a distillation fine-tune) scored 1.119, and training-free SVDQuant 1.108.
- SVDQuant keeps NVFP4 close to BF16. On 5,000 MJHQ prompts, FLUX.1 [dev] scored an FID of 20.4 in NVFP4 against 20.3 in BF16.
- NVFP4 drifts about twice as far as FP8. In torchao’s LPIPS-vs-BF16 test, float8 scored 0.124 and NVFP4 0.269, with simple, not best-effort, recipes.
- Composition can change, not just texture. In NVIDIA’s FLUX.2 side-by-sides, the NVFP4 image added umbrellas and changed a subject’s smile. If your product promises fixed-seed consistency, that is a regression.
Most vendor posts show a handful of side-by-sides, not metrics. Run your own eval.
How to measure it
- Pair every image: same prompt, seed and scheduler, BF16 against the candidate. That separates the precision change from seed-to-seed variation.
- LPIPS against the BF16 output (Zhang et al.) measures how far pixels moved, not whether the change is worse.
- CLIP score (Hessel et al.) and ImageReward (Xu et al.) check prompt adherence and preference. CLIP metrics carry CLIP’s biases, and neither checks spelling.
- FID only at scale. FID is biased at finite sample sizes, in a model-dependent way. SVDQuant used 5,000 prompts. With 300 images, skip it.
- Targeted failure-mode sets: rendered text (PartiPrompts has a “Writing & Symbols” category), faces, hands, hair, fabric, foliage and logos. For text, OCR both outputs and compare strings.
- Blind human A/B with people who know the product. Fix the pass threshold before anyone sees the speed numbers.
import lpips, torch
from torchmetrics.functional.multimodal import clip_score
lp = lpips.LPIPS(net="alex").cuda()
CLIP = "openai/clip-vit-large-patch14"
def to_lpips(x): # uint8 NCHW in [0, 255] -> float in [-1, 1]
return x.float().div(127.5).sub(1).cuda()
def compare(base, cand, prompts):
# base, cand: uint8 tensors (N, 3, H, W) generated with identical seeds
drift = lp(to_lpips(base), to_lpips(cand)).mean().item()
clip_b = clip_score(base, prompts, model_name_or_path=CLIP).item()
clip_c = clip_score(cand, prompts, model_name_or_path=CLIP).item()
return {"lpips": drift, "clip_base": clip_b, "clip_cand": clip_c}
The cost math per 1,000 images
Same formula as the pillar post: price per hour divided by images per hour, at batch size 1 and full utilization. On-demand prices are from RunPod (updated 4 November 2026) and Modal (per-second rate times 3,600), checked 5 November 2026. Latencies are the end-to-end vendor numbers above.
| Setup | Seconds per image | Images per hour | RunPod Secure | RunPod Community | Modal |
|---|---|---|---|---|---|
| FLUX.1 [dev], RTX 5090, FP16 (offloading) | 10.93 | 329 | $3.61 ($1.19/h) | $2.10 ($0.69/h) | n/a |
| FLUX.1 [dev], RTX 5090, FP8 | 6.68 | 539 | $2.21 | $1.28 | n/a |
| FLUX.1 [dev], RTX 5090, FP4 | 3.85 | 934 | $1.27 | $0.74 | n/a |
| FLUX.1 [dev], RTX 4090, FP8 (offloading) | 10.62 | 339 | $2.63 ($0.89/h) | $1.00 ($0.34/h) | n/a |
| SD3.5 Large, H100, BF16 | 8.10 | 444 | $8.98 ($3.99/h) | $6.05 ($2.69/h) | $8.89 ($3.95/h) |
| SD3.5 Large, H100, FP8 | 5.71 | 631 | $6.33 | $4.27 | $6.26 |
How to read it:
- FP8 on Hopper saved about 30% on SD3.5 Large, like for like in TensorRT.
- Price the GPU and the precision together. FP8 on a $0.89 RTX 4090 costs about twice as much per image as FP4 on a $1.19 RTX 5090, because the 4090 has to offload.
- Utilization still dominates. The FP8 RTX 5090 row at 50% utilization costs $4.42, worse than FP16 running fully busy.
- The saving has to pay for the work. At 1 million FLUX.1 [dev] images a month, FP8 to FP4 saves about $940 a month at Secure Cloud prices, which pays for an eval and an engine build. At 50,000 a month it saves under $50. For weighing engineering spend against run cost, see our AI feature cost breakdown.
When FP8 pays, when FP4 pays, when to stay on BF16
| Your situation | Choose | Why |
|---|---|---|
| A100 or older | BF16; INT4 SVDQuant only if you need it to fit | No FP8 tensor cores. FP8 weights get dequantized, so you save memory but not time |
| L40S, L4, RTX 4090, H100, H200 | FP8 transformer, BF16 VAE | Native FP8 support, the smallest quality drift in public data, and mature tooling (torchao, TensorRT) |
| B200 or RTX 5090/PRO 6000, high volume, photo or illustration output | NVFP4 linear layers, FP8 attention, after an eval | 1.25–1.7x over FP8 in fused runtimes. Use a vendor checkpoint, QAT or SVDQuant rather than plain PTQ |
| Text in images, logos, product packaging, UI mock-ups | FP8, or BF16 if FP8 fails your OCR check | Small text is the first documented casualty of FP4 PTQ |
| Fixed seeds must reproduce compositions | BF16 or FP8 | NVFP4 can change composition, not just texture |
| 4-step distilled model at 512–1024 px | FP8; test FP4 last | Small matmuls and CPU overhead shrink the gain, and generic NVFP4 can be slower than BF16 on small shapes |
| Users bring their own LoRAs | FP8 in PyTorch, or SVDQuant | SVDQuant applies LoRAs without re-quantization. TensorRT engines need a rebuild per change |
| Low volume, or GPUs idle most of the day | Stay where you are | Scale-to-zero and batching save more. See the pillar |
The rule of thumb: on Ada and Hopper, FP8 is a default you need a reason not to take. On Blackwell, NVFP4 is an option you need evidence to take. If an outside team does the work, a fixed scope with a measurable pass bar is easier to hold them to; our notes on fixed-price vs time-and-materials explain why.
Getting a measured answer for your pipeline
Every number above is someone else’s model on someone else’s GPU. Yours depends on your model, resolution, step count, LoRAs and quality bar, so it has to be measured.
That is what the GPU Inference Speed Audit does. It is a fixed $4,900, paid upfront, and takes one week. On day one we record a reproducible baseline on your hardware and agree a quality bar with you. Then we profile the pipeline and prototype the precision changes that fit it (FP8, NVFP4, SVDQuant or a TensorRT engine, plus whatever else the profile turns up), each measured against that bar. You get the benchmark scripts, raw results, a ranked plan with effort and risk for each change, and a fixed quote to implement it if you want one.
If we can’t show at least 30% lower cost per output or 30% lower latency, measured on your workload at the agreed quality bar, you get a full refund automatically and keep the report.