PyTorch Quantization

Quantization reduces the numerical precision of a model's weights and activations — from 32-bit floats (float32) to 8-bit integers (int8) or even 4-bit. This shrinks model size by up to 4×, speeds up inference by 2–4×, and cuts memory bandwidth usage — making it possible to run models on mobile phones, microcontrollers, and edge devices that could never run the full-precision version.

Why Quantization Works

Original model:
  Each weight stored as float32 (32 bits = 4 bytes)
  1 million weights → 4 MB

After int8 quantization:
  Each weight stored as int8 (8 bits = 1 byte)
  1 million weights → 1 MB   ← 4× smaller

Speed improvement:
  Integer arithmetic is faster than floating-point on most hardware
  Especially on mobile chips with dedicated int8 accelerators

The Quantization Idea

float32 value: 0.347
  → mapped to int8 value: 44
  → stored as: 44 (1 byte instead of 4 bytes)

During inference:
  int8 arithmetic is performed
  result is de-quantized back to float for the final output

Accuracy loss:
  Rounding introduces small errors
  Well-quantized models lose < 1% accuracy on most tasks

Three Quantization Approaches in PyTorch

1. Post-Training Static Quantization (PTQ Static):
   → Train model normally → calibrate on unlabeled data → quantize
   → Fastest to apply, good accuracy
   → Requires calibration dataset

2. Post-Training Dynamic Quantization (PTQ Dynamic):
   → Quantize weights only, activations stay float32
   → No calibration needed → simplest approach
   → Best for LSTM and Linear-heavy models

3. Quantization-Aware Training (QAT):
   → Simulate quantization during training
   → Model learns to be robust to quantization errors
   → Best accuracy, requires retraining

Dynamic Quantization — Easiest

Dynamic quantization quantizes only the weights ahead of time. Activations are quantized on-the-fly at runtime. No calibration data required — the simplest path to a smaller model.

import torch
import torch.nn as nn
import torch.quantization

model = nn.Sequential(
    nn.Linear(128, 64),
    nn.ReLU(),
    nn.Linear(64, 10)
)

# Quantize Linear layers to int8
quantized_model = torch.quantization.quantize_dynamic(
    model,
    {nn.Linear},          # which layer types to quantize
    dtype=torch.qint8     # target precision
)

print(quantized_model)
x   = torch.rand(8, 128)
out = quantized_model(x)
print(out.shape)   # torch.Size([8, 10])

Comparing Model Size Before and After

import os
import torch

def model_size_mb(model, path='tmp_model.pt'):
    torch.save(model.state_dict(), path)
    size_mb = os.path.getsize(path) / 1e6
    os.remove(path)
    return size_mb

original_size  = model_size_mb(model)
quantized_size = model_size_mb(quantized_model)

print(f"Original:   {original_size:.2f} MB")
print(f"Quantized:  {quantized_size:.2f} MB")
print(f"Reduction:  {original_size/quantized_size:.1f}×")

Static Quantization — Better Accuracy

Static quantization also quantizes activations. It requires a calibration step where you run representative data through the model so PyTorch can measure activation ranges. The result is a fully int8 model — faster than dynamic quantization at inference time.

import torch
import torch.nn as nn
import torch.quantization

class QuantizableNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.quant   = torch.quantization.QuantStub()    # entry point
        self.fc1     = nn.Linear(128, 64)
        self.relu    = nn.ReLU()
        self.fc2     = nn.Linear(64, 10)
        self.dequant = torch.quantization.DeQuantStub()  # exit point

    def forward(self, x):
        x = self.quant(x)    # convert float to quantized
        x = self.relu(self.fc1(x))
        x = self.fc2(x)
        x = self.dequant(x)  # convert back to float
        return x

model = QuantizableNet()
model.eval()

# Step 1: Set quantization configuration
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')  # x86 CPU

# Step 2: Prepare for calibration
torch.quantization.prepare(model, inplace=True)

# Step 3: Calibrate with representative data
calibration_data = torch.rand(100, 128)
with torch.no_grad():
    model(calibration_data)

# Step 4: Convert to quantized model
torch.quantization.convert(model, inplace=True)

print("Static quantized model ready")
out = model(torch.rand(4, 128))
print(out.shape)   # torch.Size([4, 10])

Static Quantization Flow Diagram

Original float32 model
         │
         ▼ torch.quantization.prepare()
Model with observer hooks
         │
         ▼ Run calibration data (observe activation ranges)
Calibrated model
         │
         ▼ torch.quantization.convert()
Quantized int8 model   ← 4× smaller, 2–4× faster inference

Quantization-Aware Training (QAT)

QAT inserts fake quantization operations during training — the model sees quantization noise and learns weights that are robust to it. This produces the highest accuracy quantized model but requires retraining.

model = QuantizableNet()
model.train()
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')

# Prepare for QAT (inserts fake quant nodes)
torch.quantization.prepare_qat(model, inplace=True)

# Normal training loop ...
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
# ... train for several epochs ...

# Convert to quantized after training
model.eval()
torch.quantization.convert(model, inplace=True)

Choosing a Quantization Strategy

Situation                              Strategy
───────────────────────────────────────────────────────────────
Quick size reduction, no retraining   Dynamic quantization
Best speed, limited calibration data  Static quantization
Highest accuracy, willing to retrain  QAT
Deploying on mobile (ARM)             Static + mobile backend ('qnnpack')
Deploying on x86 CPU server           Static + 'fbgemm' backend

Summary

Quantization reduces model weights and activations from float32 to int8, making models 4× smaller and 2–4× faster at inference. Dynamic quantization is the simplest approach — one line of code, no calibration needed, great for LSTM and linear models. Static quantization requires a calibration step but quantizes both weights and activations for maximum speed. Quantization-Aware Training produces the best accuracy by training with simulated quantization noise. Choose the backend based on your hardware: fbgemm for x86 CPUs, qnnpack for mobile ARM processors. Quantization is essential for deploying large models on resource-constrained devices.

Leave a Comment

Your email address will not be published. Required fields are marked *