Engineering deep dive · · 14 min read

Quantization, Explained: From FP32 to INT4, and What Each Precision Costs You

A number's bits are sign, exponent and mantissa: exponent buys range, mantissa buys precision, and every format on this page is a different trade between the two. We lay out FP32, TF32, BF16, FP16, INT8 and INT4 bit by bit, size an illustrative 27-billion-parameter model at each one (108 GB down to 13.5 GB), show exactly why one outlier weight can wreck naive quantization, and walk through what GPTQ, AWQ and QLoRA's NF4 format do about it, with the authors' own published perplexity and MMLU numbers.

When you run a modern language model, memory is not just an implementation detail — it defines what is physically possible. A 27-billion parameter model stored in full 32-bit floating point takes up over 108 GB of VRAM just for its weights, immediately exceeding the physical capacity of an 80 GB GPU like the NVIDIA H100. If you want to serve it on a single card, or serve it with high throughput and reasonable cost, you have to compress those numbers.

That is where quantization comes in: the practice of storing weights and activations in fewer bits than the precision they were trained on. Done carelessly, quantization quietly degrades reasoning ability or turns text generation into gibberish. Done well, it shrinks a 108 GB model down to 13.5 GB with almost zero noticeable loss in quality.

In this guide, we walk through how quantization actually works under the hood — starting from the binary anatomy of floating-point numbers, exploring why hardware designers introduced specialized formats like TF32 and BF16, and examining how modern post-training algorithms (GPTQ, AWQ, and the NF4 format behind QLoRA) keep 4-bit models remarkably faithful to their 16-bit counterparts, backed by published benchmarks.

1. What a floating-point number actually is

At its core, a floating-point number is binary scientific notation: a sign, an exponent that shifts the scale, and a mantissa (or fraction) that supplies the digits within that scale.

x=(−1)sign×(1+mantissa2m)×2 exponent−biasx=(-1)^{\text{sign}}\times\left(1+\frac{\text{mantissa}}{2^{m}}\right)\times 2^{\,\text{exponent}-\text{bias}}

Here mm represents the number of mantissa bits. Every floating-point format is a zero-sum trade-off between range and precision:

  • More exponent bits widen the range: the span between the largest and smallest magnitudes the format can represent before overflowing to infinity or underflowing to zero.
  • More mantissa bits sharpen precision: the spacing between adjacent representable numbers is tighter, preserving fine-grained mathematical differences.

Within a fixed bit budget, you cannot maximize both. Every format on the market represents a deliberate compromise tailored to a specific hardware or training workload.

Six ways to store one numberEach small block is one bit. Sign picks the direction, exponent sets the scale (range),mantissa sets the digits (precision). An integer has no exponent: a scale kept outside the number does that job.FP3232 bits±exp 8mantissa 23IEEE 754 singleTF3219 bits±exp 8mantissa 10NVIDIA tensor-core compute format · padded to 32 bits in memoryBF1616 bits±exp 8mantissa 7bfloat16 · FP32's exponent, a short mantissa · default for trainingFP1616 bits±exp 5mantissa 10IEEE 754 half · wide mantissa, narrow exponent · can overflowINT88 bits8-bit integer8-bit integer · fixed step size, needs a scale to mean anythingINT44 bits4-bit integer4-bit integer · only 16 distinct levels per valueexponent: how far a number can reachmantissa: how finely spaced the values areinteger: no exponent, range fixed by the scale you choose
Bit layout of six formats. Sign picks the direction, exponent sets the scale (range), mantissa sets the digits (precision). Integers have no exponent: a scale kept outside the number does that job.

Two crucial takeaways emerge when you look at these bit layouts side by side:

  1. TF32 is not a storage format: Although it only uses 19 meaningful bits (1 sign, 8 exponent, 10 mantissa), NVIDIA pads it to a standard 32-bit container in memory. It saves zero VRAM over standard FP32 at rest; its sole purpose is speeding up Tensor Core matrix multiplies during execution.
  2. BF16 and FP16 reflect opposing philosophies: Both occupy exactly 16 bits, but they allocate them completely differently. FP16 prioritizes precision (10 mantissa bits) at the expense of exponent range (5 bits). BF16 (Brain Floating Point) does the reverse: it preserves FP32’s full 8-bit dynamic range, sacrificing precision (only 7 mantissa bits). That single architectural choice explains why BF16 took over deep-learning training.

2. Why training moved to BF16, and what TF32 actually is

During backpropagation in a deep transformer, gradients and activations swing wildly across orders of magnitude. FP16’s 5-bit exponent gives it a dynamic range capped at roughly 65,504. In large models, useful gradient updates frequently exceed that ceiling, overflowing directly to inf and corrupting training with NaN losses. Smaller gradients vanish into underflow. To prevent crashes, engineers had to use loss-scaling heuristics that dynamically rescaled values to squeeze them into FP16’s narrow window.

BF16 eliminated that headache entirely. By matching FP32’s 8 exponent bits, BF16 provides an identical dynamic range (up to ≈3.4×1038\approx 3.4 \times 10^{38}). Values that would catastrophically overflow FP16 simply round to a slightly less precise BF16 neighbor. Because gradient descent is remarkably tolerant of rounded mantissas but completely brittle to blown exponents, BF16 rapidly became the universal default for training frontier models.

TF32 (TensorFloat-32) solves a different problem: accelerating FP32 code without requiring developers to rewrite pipelines. Introduced with NVIDIA’s Ampere architecture (A100), TF32 is not a format you store on disk, but an execution mode inside Tensor Cores. Your tensors remain standard 32-bit floats in memory. When Tensor Cores execute a matrix multiply, the hardware reads the inputs, truncates the mantissa from 23 bits down to 10 bits internally, performs the multiply at Tensor Core speeds, and accumulates the result in full FP32.

The result is the best of both worlds for legacy software: FP32’s full exponent range, FP16-like math speed, and zero code changes. In fact, PyTorch enables TF32 by default on Ampere and newer GPUs. If you trained an “FP32” baseline on modern hardware, you were almost certainly running TF32 under the hood.

The rule of thumb: dynamic range comes from the exponent, while precision comes from the mantissa. FP32 and TF32 share range; TF32 and FP16 share mantissa precision; BF16 borrows FP32’s range inside FP16’s footprint. Once you inspect the bit diagram, you can predict how any new numeric format will behave before writing a line of code.

3. From floats to integers: what quantization does

Unlike floating-point numbers, integer formats have no exponent bits at all. An INT8 tensor is simply a uniform grid counting from −128 to 127 (or 0 to 255 unsigned), where every single step is the exact same size.

To map continuous floating-point weights onto discrete integer buckets, quantization introduces two external values:

  • A scale factor (ss): a floating-point multiplier setting how wide each integer step is in real units.
  • A zero-point (zz): an integer offset aligning the real value 0.0 with a specific integer index.
s=xmax⁡−xmin⁡qmax⁡−qmin⁡z=round⁡ ⁣(qmin⁡−xmin⁡s)q=clamp⁡ ⁣(round⁡(x/s)+z, qmin⁡, qmax⁡)x^=s (q−z)\begin{aligned} s&=\frac{x_{\max}-x_{\min}}{q_{\max}-q_{\min}}\\ z&=\operatorname{round}\!\left(q_{\min}-\frac{x_{\min}}{s}\right)\\ q&=\operatorname{clamp}\!\big(\operatorname{round}(x/s)+z,\ q_{\min},\ q_{\max}\big)\\ \hat{x}&=s\,(q-z) \end{aligned}

Here xx is the original floating-point value, qq is the stored quantized integer, and x^\hat{x} is the dequantized value reconstructed during math.

In practice, quantizers use two primary configurations:

  • Symmetric quantization (z=0z=0): When weights are centered symmetrically around zero (typical for neural network layers), we drop the zero-point offset entirely and map values into [−127, 127] based on the maximum absolute magnitude. This saves computation because the hardware does not need to subtract an offset during matrix multiplies.
  • Asymmetric quantization: Retains both scale and zero-point. This is essential for activations that are skewed or strictly positive, such as outputs after ReLU or SiLU functions.

The other key architectural choice is granularity:

  • Per-tensor: One single scale and zero-point for the entire weight matrix. Highly compact, but easily ruined if a few values in the tensor are unusually large.
  • Per-channel (or per-row): A dedicated scale for each output channel. Standard for INT8 weight quantization.
  • Per-group: Dividing each row into small blocks of 32, 64, or 128 weights, giving each block its own scale. This is the foundation of modern 4-bit algorithms like GPTQ and AWQ: it adds a modest metadata footprint (storing scales per group), but prevents local spikes from degrading unrelated weights.

4. Model size at each precision

Calculating weight memory is straightforward: total parameter count multiplied by bytes per parameter. While runtime memory also demands space for KV-cache, activations, and workspace buffers, the raw weights establish the baseline footprint required just to load the model.

Mweights=P×bytes per parameter,FP32=4, FP16/BF16=2, INT8=1, INT4=0.5M_{\text{weights}}=P\times\text{bytes per parameter},\qquad\text{FP32}=4,\ \text{FP16/BF16}=2,\ \text{INT8}=1,\ \text{INT4}=0.5
The same 27-billion-parameter model, four ways to store itWeights only: size = parameters x bytes per parameter. KV-cache, activations and quantization metadata are separate and come on top.one H100, 80 GBFP32108.0 GBBF16 / FP1654.0 GBINT827.0 GBINT413.5 GB020406080100120gigabytes (decimal GB), weights only
The same illustrative 27-billion-parameter model (the figure used across this series) at four precisions. Weights only, decimal GB; KV-cache and activations are not included. FP32 alone exceeds one 80 GB H100; every lower precision leaves headroom for context and batch.
Illustrative 27-billion-parameter model · weights only · decimal GB
PrecisionBytes / parameterWeight size
FP324108.0 GB
BF16 / FP16254.0 GB
INT8127.0 GB
INT40.513.5 GB

Notice how these thresholds line up with real-world hardware:

  • At FP32 (108 GB), the weights alone exceed the physical capacity of an 80 GB NVIDIA H100 or A100. Running the model requires multi-GPU sharding.
  • Dropping to BF16 (54 GB) fits the weights onto a single 80 GB card, but leaves constrained memory for deep concurrency and long context windows.
  • At INT4 (13.5 GB), the entire model fits comfortably on a single consumer GPU (like an RTX 4090 with 24 GB) or leaves more than 65 GB of free VRAM on an H100 for high-throughput KV-cache batching. This same math underpins QLoRA: a frozen 4-bit base model consumes only 13.5 GB, allowing adapters to train efficiently in the remaining space.

5. The outlier problem

If quantizing a tensor were as simple as laying an even ruler between its minimum and maximum values, low-bit quantization would have been solved years ago. The complication is a peculiar mathematical phenomenon that emerges in deep language models.

In 2022, Dettmers et al. discovered that once transformer models grow past roughly 6.7 billion parameters, their internal representations undergo a phase transition: they develop emergent outlier features. Across specific hidden dimensions, activation magnitudes suddenly spike to values 50× to 100× larger than all other channels.

Rounding real numbers onto a short, evenly spaced rulerQuantization picks a scale so every weight lands on one of a small number of integer steps, then a zero-point shifts the ruler if the range is not symmetric.A - no outliers: the ruler fits the real spread-0.20.216 levels · step = 0.027B - one outlier channel stretches the whole ruler-2.42.416 levels · step = 0.32one outlier weightEvery ordinary weight above now rounds to the same one or two levels: 16x less resolution for 99% of the values, from a single channel.GPTQ, AWQ and LLM.int8() all exist to stop this: reconstruct around the error (GPTQ), protect salient channels (AWQ), or split outliers into their own FP16 path (LLM.int8()).
The same 16-level integer ruler with and without one outlier value. In panel B, a single outlier 12x larger than the rest of the data forces the step size up by the same factor, so every ordinary weight collapses onto one or two integer levels.

Look at what happens in Panel B: because a single outlier is 12× larger than its peers, the quantizer is forced to stretch its scale factor (ss) by 12× to avoid clipping that outlier to infinity.

As a result, all the normal, everyday weights collapse onto just one or two integer buckets near zero. All fine-grained precision is obliterated. This “outlier catastrophe” is why early naive per-tensor INT8 and INT4 implementations suffered steep drops in accuracy, and why modern post-training algorithms were designed specifically to isolate and protect these extreme values.

6. Post-training quantization in practice

Modern quantization algorithms solve the outlier problem using four distinct strategies:

  • LLM.int8() (Mixed-Precision Decomposition): Rather than forcing outliers into INT8, Dettmers et al. (2022) isolates the ~0.1% outlier coordinate dimensions and keeps them in full 16-bit floating point. The remaining 99.9% of normal values are multiplied in INT8, and the results are summed. By exempting the extreme channels from quantization entirely, it eliminates outlier distortion without loss of accuracy.
  • GPTQ (Second-Order Error Compensation): Frantar et al. (2022) quantizes weights layer by layer. When a weight is rounded to an integer, it introduces a small rounding error. Instead of ignoring that error, GPTQ uses second-order (Hessian) curvature information to immediately adjust the remaining unquantized weights in that row to compensate. Because it dynamically balances rounding errors, GPTQ achieves stable 4-bit compression in a few hours without retraining, typically operating at per-group granularity (128 weights per scale).
  • AWQ (Activation-Aware Weight Quantization): Lin et al. (2023) recognized that not all weights are equally important. Weights that multiply high-magnitude activations have an outsized impact on output perplexity, whereas weights multiplying near-zero activations can be rounded aggressively. Rather than keeping outliers in FP16 (which complicates runtime kernels), AWQ scales up salient weight channels before quantization and scales the corresponding activation inputs down. Protecting just the top 1% most salient channels preserves model quality under clean, uniform INT4 execution.
  • NF4 (NormalFloat-4 in QLoRA): Dettmers et al. (2023) observed that standard INT4 grids assume weights are uniformly distributed. But neural network weights cluster in a bell curve (normal distribution) around zero. NF4 creates an information-theoretically optimal grid where all 16 discrete levels represent equal quantiles of a standard normal distribution. More quantization bins cluster near zero where most weights live, with wider spacing covering the rare tails. Paired with double quantization (quantizing the block scale constants themselves into 8-bit), NF4 achieves near-lossless 4-bit compression for QLoRA fine-tuning.

7. What the published benchmarks show

The empirical literature confirms that naive rounding fails at low bit-widths, while structured quantizers preserve baseline performance:

Selected published post-training quantization results
MethodSettingReported result
GPTQ, 4-bitOPT-175B, WikiText2 perplexity+0.03 ppl vs. FP16; round-to-nearest loses 2.2 ppl at the same bit width
RTN / GPTQ / AWQ, INT4 g128LLaMA & Llama-2 family, WikiText2 perplexityRanges of 3.46–5.96 / 3.42–6.22 / 3.41–5.78 across model sizes; AWQ at or below GPTQ and RTN on every size tested
LLM.int8(), 8-bitOPT up to 175B, zero-shot accuracy & perplexityNo measurable degradation vs. FP16 baseline; naive 8-bit quantization degrades sharply once outlier features emerge (~6.7B+ parameters)
NF4 + double quantization (QLoRA)LLaMA 7B–65B, 5-shot MMLUMatches 16-bit LoRA and full fine-tuning performance within noise

The lesson across every benchmark is consistent: naive round-to-nearest (RTN) degrades quickly because it treats every parameter as uniform noise. But as soon as an algorithm accounts for error propagation (GPTQ), activation salience (AWQ), empirical weight distributions (NF4), or dedicated outlier channels (LLM.int8()), models retain virtually all of their native capability — even compressed down to 4 bits.

8. Picking a precision for your workload

There is no single “best” precision. The optimal choice depends on whether you are training, optimizing for latency, or packing maximum batch density:

Practical rules of thumb for production precision
WorkloadTypical choiceWhy
Pretraining / full fine-tuningBF16 weights & gradients, TF32 matmuls, FP32 optimizer momentsRange to survive gradient spikes; FP32 moments for optimizer stability
Parameter-efficient fine-tuningNF4 frozen base + BF16 adapters (QLoRA)Base model dominates memory and is not being updated; adapters stay high precision
Latency-sensitive servingINT8 weights, BF16/FP16 activationsHalves weight memory and bandwidth with published near-zero accuracy loss
Maximum density servingINT4 via GPTQ or AWQ, group size 128Quarters weight memory; pick AWQ or GPTQ over round-to-nearest
Long-context KV-cacheINT8 (sometimes INT4) KV-cache, independent of weight precisionCache scales with context x batch, not parameter count, and is usually the first thing to run out of VRAM

One final operational reality: benchmark perplexity does not tell the whole story. A 4-bit model that looks flawless on a WikiText perplexity test can still regress on delicate edge cases — such as multi-step mathematical proofs, strict JSON schema output, or niche code syntax. Always validate your quantized checkpoint directly on domain verifiers and task-specific gyms before shipping to production.