Run an entire neural network with nothing but integer multiplication, addition, and bit-shifting — no floating point, not even integer division — and let an integer linear program decide which layers get 4 bits and which get 8.
You quantize a ResNet to 8-bit integers. The weights are now int8, the activations are int8, the model file is 4× smaller. You ship it, expecting it to run roughly 4× faster on your edge chip. It runs barely faster. What happened?
Open the inference graph and you find the culprit. Almost every "quantized" framework actually does simulated quantization (also called fake quantization): the weights are stored as integers, but right before every convolution they are cast back to FP32, the convolution runs in floating point, batch-norm runs in floating point, the residual addition runs in floating point, and the result is cast back to integer. The integers are a storage format, not a compute format.
Three things keep dragging floating point back into the graph, and they are exactly the three places prior "integer-only" work quietly kept FP32:
| Operation | Why a float sneaks in | What HAWQ-V3 must fix |
|---|---|---|
| Rescaling after a matmul | The scale factor SwSh/Sa is an arbitrary real number | Force it to a dyadic number b/2c → integer multiply + bit shift |
| Batch normalization | μ, σ are floats; quantizing them wrecks accuracy | Fold BN into the conv weights, quantize the folded result |
| Residual addition | Q(a+b) ≠ Q(a)+Q(b); naive int add corrupts the signal | Dyadic-scale both branches to a common scale, add in INT32 |
And there is a second, separate problem layered on top. Uniformly quantizing every layer to INT4 saves the most memory and runs fastest, but some layers are sensitive — slam them to 4 bits and accuracy collapses. You want a mixed-precision assignment: 4 bits where it's cheap, 8 bits where it matters. But with L layers and 2 choices each there are 2L assignments. For ResNet50 that's 250 ≈ a quadrillion. You cannot grid-search that.
The whole paper rests on one observation about a single number. After a quantized matmul, you have an INT32 accumulator, and to pass it to the next layer you must multiply by a real-valued scale and round. That scale is the only floating point that has to exist. Kill it, and the floats are gone everywhere.
HAWQ-V3 kills it by forcing every rescale factor to be a dyadic number: a rational of the form b/2c, where b and c are integers. Multiplying by b/2c is exactly "multiply by integer b, then shift right by c bits." No float, no division — and on a CPU, division costs roughly 10× a multiply, so removing it is a real win.
Everything else in the paper is making that one trick work in the three places floats normally hide, plus a way to choose bit-widths:
Chapters 2–3 build the dyadic core. Chapter 4 folds BN, Chapter 5 fixes residuals, Chapter 6 is the ILP showcase, and Chapters 7–8 are hardware and results. Let's start by being precise about what "quantize" even means here.
To map a real number r (a weight or an activation) onto an integer grid, the paper uses the standard affine rule:
Three symbols, each with a job:
To go back: r ≈ S·(q + Z). The pair (S, Z) is the dictionary between the integer world and the real world.
Take a single INT4 weight (4 bits, symmetric, signed range [−8, 7]). Suppose this channel's weights span [−0.4, 0.4], so the step is S = 0.4 / 7 ≈ 0.0571. Quantize r = 0.21:
The error is 0.0186 — less than half a step, as it must be for round-to-nearest. Channel-wise quantization (a separate S per output channel) keeps that step small: a channel with tiny weights gets a tiny S and doesn't have its resolution stolen by a louder neighbor.
Drag the bit-width and range. Watch how few INT4 grid lines there are versus INT8 — and how a single fat weight forces a coarse step that blurs every small weight near zero.
Now the core mechanism. A quantized layer takes activation h = Shqh and weight W = Swqw. The convolution multiplies the integers and accumulates in INT32:
Here qw ∗ qh is a pure INT4×INT4 multiply-accumulate landing in an INT32 register — fast, no floats. But the next layer wants its input as INT4, so we must requantize: divide by the next activation's scale Sa and round.
That factor SwSh/Sa is a product/quotient of three arbitrary reals — it is itself an arbitrary real. Multiplying an INT32 by it naively needs an FPU. This is the float we kill.
Say the true scale is SwSh/Sa = 0.1875. Pick a precision, e.g. c = 8 bits of fraction. Then:
Exact here. For a messier scale like 0.137, b = round(0.137×256) = 35, giving 35/256 = 0.13672 — an error of 0.0003, negligible after rounding to an integer code. At runtime: q_a = (acc * 35) >> 8. One multiply, one shift. That single line replaces a floating-point rescale on every layer of the network.
Set any real scale and the fractional bit-budget c. See the dyadic ladder b/2⁰ snap to it, and the rounding error shrink as c grows.
Batch norm is the second place floats hide. At inference, BN is an affine map with fixed statistics:
where μ, σ are the running mean/std and γ, β are the learned scale/shift. Prior quantizers keep these in FP32 because quantizing μ, σ directly is brutal — the std appears in a division, and small errors blow up. But FP32 BN is illegal on integer-only hardware.
The trick: a convolution followed by BN is itself an affine map, so the two collapse into one conv with adjusted weights and bias. For a conv weight W and bias, folding gives:
Now there is no separate BN layer at all. HAWQ-V3 quantizes the folded result, and routes the float pieces into integer slots:
The earlier integer-only work (Jacob et al., 2018) folded BN while still updating the running statistics during training, which forced computing each conv twice per step (once without BN to get stats, once with). HAWQ-V3 found this both wasteful and accuracy-hurting. Their fix is simpler:
Click a stage to trace where each BN parameter goes. Drag σ to see the weight scale absorb it; drag μ to see the INT32 bias shift.
The third hiding place is the residual add in a ResNet block: out = main + skip. It looks trivial — just add two tensors. Prior work added them in FP32 and re-quantized. HAWQ-V3 shows this is a disaster on integer hardware, and the reason is a one-line fact:
Quantization is not linear. If you accumulate the residual in float and then quantize, you get a different answer than if you accumulate the already-quantized integers. So a model trained/calibrated assuming FP32 residual accumulation, when actually deployed with integer accumulation, produces a different signal. The paper measures the mismatch at over 90% of feature-map values for low-precision residuals. The signal is gone.
The two branches arrive with different scales: main is m = Smqm, skip is r = Srqr. You can't add qm and qr directly — they're in different units. Rescale both into the output scale Sa using two dyadic factors, then add the integers:
Each DN(·) is a precomputed (b, c) pair — an integer multiply and shift. The addition is INT32. Every scale is statically known, so all the (b, c) pairs are baked in offline. The residual is now genuinely integer-only.
Same two branches, two ways. Left: quantize-then-add (HAWQ-V3, integer). Right: add-in-float-then-quantize (prior work). Drag the bit-width down and watch the mismatch (red) explode at low precision.
Now the second half of the paper. We can run any layer at INT4 or INT8. INT4 is smaller and faster but more lossy. Which layers get which? With L layers and 2 choices, the search space is 2L — intractable. We need to score each layer's sensitivity and then optimize under a budget.
How much does quantizing layer i to b bits hurt the loss? The answer is the curvature of the loss in that layer's directions. A layer sitting in a flat valley tolerates the perturbation — the loss barely moves. A layer in a sharp ravine is sensitive — a small weight nudge spikes the loss. The paper measures this with the Hessian (second derivative): per-layer perturbation
Big eigenvalue (sharp) × big rounding error (few bits) = high cost. Assuming layers perturb independently, total damage is the sum — so we only need BL sensitivity probes, not 2L.
Let bi ∈ {4, 8}. Minimize total sensitivity subject to whatever the hardware demands:
Because everything is linear in binary choices, an off-the-shelf ILP solver (PuLP) cracks it in under one second. The RL-based HAQ took tens of hours to do the same job — and wasn't hardware-aware.
8 layers, each with a sensitivity (sharpness) and a measured INT4 speedup. Drag the latency budget: the solver greedily 4-bits the layers that are least sensitive per unit of latency saved. Toggle "hardware-aware" off to see it ignore measured speedups and make worse choices.
A quantization paper that never touches silicon is a paper of promises. HAWQ-V3 actually deploys, on NVIDIA T4 Turing Tensor Cores, which natively support both INT8 and INT4 matmul. The catch: the only API is the low-level WMMA micro-kernel, and no compiler existed that could map an INT4 network onto it. So the authors extended Apache TVM — the first framework to add INT4 support to TVM.
The training happens in PyTorch (with fake quantization); the deployment happens in TVM. A tiny mismatch between the two could silently break the model and never show up in the PyTorch validation accuracy. So the authors forced layer-by-layer, machine-precision agreement between PyTorch and TVM for every stage, then confirmed the final Top-1 with the actual integer-only hardware execution — not a simulation.
Drag the lanes. Eight 4-bit values share a single 32-bit machine word so the hardware can move and multiply them as a chunk on Tensor Cores.
The headline: integer-only, no-FP32-anywhere quantization that matches or beats prior work which cheated by keeping BN and residuals in float — while running measurably faster on real hardware.
| Model | FP32 baseline | HAWQ-V3 INT8 | INT4/8 mixed | Uniform INT4 |
|---|---|---|---|---|
| ResNet18 | 71.47% | 71.56% | 70.22% (70.38 +distill) | 67.50% (68.45 +distill) |
| ResNet50 | 77.72% | 77.58% | 75.39% (76.73 +distill) | 73.70% (74.24 +distill) |
| InceptionV3 | 78.88% | 78.76% | 74.65% (74.72 +distill) | 70.39% |
Read the ResNet50 row carefully. INT8 lands at 77.58% — just 0.14% below FP32 and +2.68% over the prior integer-only work (Jacob et al.), almost entirely thanks to the two-phase BN fold from Chapter 4. The INT8 model is also 4× smaller (97.8 MB → 24.5 MB).
Slide along the frontier the ILP traces out by varying its latency budget. Each point is a real configuration; see what accuracy each precision recipe buys.
HAWQ-V3 sits at the end of a lineage: Jacob et al. (2018) gave us INT8 integer-only inference and dyadic rescaling; HAWQ / HAWQ-V2 gave us Hessian-based sensitivity for mixed precision; HAWQ-V3 fuses both, pushes to INT4, makes BN and residuals truly integer, and adds a fast hardware-aware ILP with a real TVM deployment.
| Symbol / term | Meaning | Where |
|---|---|---|
| Q(r)=Int(r/S)−Z | affine quantizer; S=step, Z=zero-point | Ch 2 |
| b/2c = DN(scale) | dyadic number: integer multiply b + right-shift c, no float/division | Ch 3 |
| qa=Int((SwSh/Sa)·qw∗qh) | requantize after matmul via dyadic scale | Ch 3 |
| Ŵ=(γ/σ)W, b̂=β−γμ/σ | BN fold; σ→weight scale, μ→INT32 bias | Ch 4 |
| qa=DN(Sm/Sa)qm+DN(Sr/Sa)qr | integer residual (Q is nonlinear, must add in int) | Ch 5 |
| Ωi(b) | Hessian sensitivity = sharpness × quant-error² | Ch 6 |
| Gi=bwbaMACi | BOPS: bit-operation compute proxy | Ch 6 |
| min ΣΩ s.t. size/BOPS/latency | the ILP, solved in <1s with PuLP | Ch 6 |
If you want the foundations under this paper: a Quantization gleam for the affine grid and calibration, a BatchNorm lesson for the fold algebra, and the Hessian / loss-landscape material for why sharpness predicts sensitivity. Forward from here: Parallel/Post-training quantization, GPTQ/AWQ for LLM-era weight-only quantization, and QLoRA for fine-tuning quantized models.