Wang, Liu, Lin, Lin & Han (MIT), CVPR 2019

HAQ: Hardware-Aware
Automated Quantization with Mixed Precision

Stop quantizing every layer to the same 8 bits. Let a reinforcement-learning agent assign each layer its own bitwidth — guided not by FLOP estimates but by direct latency and energy feedback measured on the actual chip.

Prerequisites: Linear quantization + Actor-critic RL (DDPG)
10
Chapters
5
Simulations

Chapter 0: The Problem

You have a trained neural network and a phone. The network is 50 MB of 32-bit floats and runs too slowly to be useful. The standard fix is quantization: replace each float weight with a low-bit integer. Round everything to 8 bits and you cut size by 4× and speed up the math. Done — except you've left a lot on the table.

Here is the catch the paper opens with. Layers are not equally fragile. The first layer reads raw pixels and is famously sensitive; squeeze it to 2 bits and accuracy collapses. A fat middle layer with millions of redundant weights barely notices going to 4 bits. So a single global bitwidth is a blunt instrument: it over-spends bits where they're wasted and under-spends them where they matter.

The core question: Different layers want different bitwidths. But who decides which layer gets how many bits — and on what basis? Hand-tuning is hopeless, and "minimize FLOPs" lies about real hardware. HAQ asks: can we learn a per-layer bitwidth policy that's tailored to a specific chip's measured latency and energy?

How big is the design space, really?

Suppose your model has N layers and you may pick any bitwidth from 1 to 8 for both the weights and the activations of each layer. That's 8 choices for weights × 8 for activations = 64 combinations per layer, and the layers multiply:

#policies = 82N = 64N

Plug in ResNet-50 (N = 50 layers): the search space is 8100 ≈ 1090. The paper notes this is larger than the number of particles in the universe (~1080). You cannot grid-search this. You cannot intuit it.

Approach to picking bitsWhat it doesWhy it falls short
Uniform (e.g. PACT, all 4-bit)Same bitwidth for every layerIgnores per-layer redundancy & hardware behavior
Hand-rules ("more bits in first/last layer")Expert heuristicsDon't transfer across models or chips; sub-optimal
FLOP / model-size proxyMinimize a cheap surrogateDoesn't match real latency/energy on the chip
HAQ (this paper)RL learns per-layer bits from measured HW feedback

Why "minimize FLOPs" is a trap

Engineers love proxy metrics because they're cheap to compute. But the paper's Table 1 is a quiet bombshell: the same MobileNet-V1 quantization policy gives 16.3 ms latency on one accelerator and 117 ms on another. A policy optimized for chip A is not optimal for chip B. FLOPs can't see this — they don't know about cache locality, memory bandwidth, or whether a layer is compute-bound or memory-bound. Real chips do, and HAQ measures them directly.

Why is a single global bitwidth (e.g. "everything to 8 bits") sub-optimal?

Chapter 1: The Key Insight

Picking bitwidths layer-by-layer under a hardware budget is a sequential decision problem with a delayed reward. That is exactly the shape of a reinforcement-learning problem. HAQ's move is to treat the quantizer as an agent walking down the network, one layer at a time, each step choosing that layer's bitwidth.

The insight: Make the agent's reward come from the real hardware, not a proxy. The agent proposes a full bitwidth policy; a hardware simulator measures the resulting latency and energy; the model is briefly finetuned and its accuracy becomes the reward. The agent thus learns a policy specialized for that exact chip and that exact resource budget.

The loop in one breath

1. Observe
For layer k, the agent reads a 10-number description (channels, kernel, params, whether it's depthwise, last action…).
2. Act
It outputs a continuous number in [0,1], rounded to a bitwidth in {2…8} for that layer's weights (then a second step for activations).
3. Constrain
After all layers, if the policy exceeds the latency/energy/size budget, bitwidths are clipped down until it fits.
4. Reward
Quantize, finetune one epoch, measure accuracy. Reward = λ(accquant − accorig). DDPG updates the agent.

Why this is different from everything before it

Two design decisions carry the whole paper. First, the action is the bitwidth itself — the agent doesn't tweak training, it directly authors the quantization policy. Second, the environment is the hardware. Most quantization papers stop at "fewer bits = smaller model." HAQ closes the loop with the chip, so the policy it learns is co-designed with the silicon. The paper shows the learned policies for edge vs cloud accelerators are drastically different — and explains why using the roofline model (Chapter 8).

Everything that follows builds this loop piece by piece: how a weight actually becomes 4 bits (Ch 2), what the agent sees and does (Ch 3), how the chip talks back (Ch 4), the agent in motion (Ch 5–6), how budgets are enforced (Ch 7), and what the learned policies reveal (Ch 8).

In HAQ's RL formulation, what is the agent's action?

Chapter 2: Background — How a Weight Becomes b Bits

Before the agent can choose a bitwidth, we need to know exactly what "quantize this layer to b bits" does to a number. HAQ uses plain linear (uniform) quantization because linearly-quantized models only need fixed-point arithmetic — the cheap thing hardware is built for.

Take a weight w in layer k. Two steps:

quantize(w, b, c) = round(clamp(w, c) / s) × s,    s = c / (2b−1 − 1)

Reading every symbol: clamp(w, c) truncates w into the range [−c, c] (a hard ceiling and floor). s is the step size — the gap between adjacent representable values. With b bits you get 2b levels; for signed values that's 2b−1−1 positive steps on each side. Dividing by s, rounding, and multiplying back snaps w onto the nearest grid point.

Think of it as a ruler. c sets the ruler's length; b sets how many tick marks fit on it. More bits = more ticks = finer rounding. The agent is choosing how many ticks each layer's ruler gets.

Choosing the clip value c — the KL-divergence trick

Where do you put the ruler's ends? Too wide and most of your few ticks land in empty space; too narrow and you clip away large weights. HAQ picks c to make the quantized weight distribution look as much like the original as possible, by minimizing KL-divergence:

c = arg minx DKL( Wk  ||  quantize(Wk, b, x) )

In words: DKL measures how much information is lost when you replace the true weight histogram Wk with its quantized version. Sweep candidate clip values x, quantize, and keep the x that distorts the distribution least. Activations are handled identically except the range is [0, c] not [−c, c], because they're ReLU outputs and never negative.

Worked example: one weight, two bitwidths

Let w = 0.62 and clip c = 1.0.

Bits bLevels 2b−1−1Step s = c/levelsround(w/s)Quantized wError
211.000round(0.62)=11.0000.380
470.1429round(4.34)=40.5710.049
81270.00787round(78.7)=790.6220.002

The 2-bit ruler has one tick per side, so 0.62 rounds all the way to 1.0 — a 0.38 error. The 8-bit ruler nails it to 0.002. This is the lever the agent pulls: more bits buys precision but costs memory and compute. Use the widget below to feel it.

Linear quantization ruler — drag bits and clip
Why does HAQ pick the clip value c by minimizing KL-divergence instead of just using the max weight magnitude?

Chapter 3: What the Agent Sees and Does

The agent processes the network layer by layer, and for each layer it takes two steps: one to set the weight bitwidth, one to set the activation bitwidth. At each step it observes a 10-dimensional feature vector describing the current layer.

The observation Ok (10 numbers)

For a convolution layer:

Ok = ( k, cin, cout, skernel, sstride, sfeat, nparams, idw, iw/a, ak−1 )

Every symbol, plainly: k = layer index (where am I in the stack); cin, cout = input/output channel counts; skernel = kernel size; sstride = stride; sfeat = input feature-map size; nparams = parameter count; idw = is-this-depthwise flag (1/0); iw/a = is-this-the-weight-step-or-activation-step flag; ak−1 = the action the agent took on the previous step (so it has memory of context). Fully-connected layers use the same vector with kernel→1, stride→0, and the depthwise flag→0. Every dimension is normalized to [0,1] so no single feature dominates.

Why include the previous action ak−1? Bitwidths shouldn't lurch wildly between neighboring layers — adjacent layers often have correlated sensitivity. Feeding the last action gives the agent local context, like a writer remembering the previous sentence.

The action: continuous, then rounded

A natural instinct is a discrete action — pick directly from {2,3,…,8}. HAQ deliberately avoids this. The reason is subtle and worth internalizing: discrete categories lose order. To a softmax, "2 bits" and "8 bits" are just two unrelated labels; it can't feel that 2 is "more aggressive" than 4 which is "more aggressive" than 8. So HAQ uses a continuous action ak ∈ [0,1] and rounds it to a bitwidth:

bk = round( bmin − 0.5 + ak × (bmax − bmin + 1) )

With bmin=2, bmax=8 this maps the continuous knob onto 7 equal bins. Worked example: ak=0.0 → round(1.5)=2; ak=0.5 → round(2−0.5+0.5×7)=round(5.0)=5; ak=1.0 → round(8.5)=8 (clamped). The continuous knob means a tiny nudge in policy is a tiny nudge in bitwidth — gradients flow smoothly, which is exactly what an actor-critic agent needs.

Action → bitwidth map — slide the continuous action
Why does HAQ use a continuous action space rounded to a bitwidth, instead of directly classifying over {2,…,8}?

Chapter 4: The Chip Talks Back — Direct Feedback

This is the chapter that makes HAQ "hardware-aware." The agent could be rewarded with FLOPs or model size — both cheap to compute. HAQ refuses, and the reason is the heart of the paper.

Proxies lie. FLOPs count multiply-adds; they say nothing about cache locality, memory bandwidth, number of kernel calls, or degree of parallelism. Two layers with identical FLOPs can have wildly different real latency. HAQ instead feeds the policy into a hardware simulator that returns measured latency and energy — the things you actually care about.

The roofline model — why a layer's "shape" decides its bits

The key lens is operation intensity: arithmetic operations performed per byte of memory accessed (OPs/byte). The roofline model says a layer is either compute-bound (lots of math per byte — the chip's ALUs are the bottleneck) or memory-bound (little math per byte — moving data is the bottleneck).

OP intensity = (#MAC operations) / (#bytes moved)  →  low = memory-bound, high = compute-bound

This single number explains HAQ's most famous result. In MobileNet, depthwise convolutions have low operation intensity — they reuse data poorly, so they're memory-bound; their cost is dominated by moving activations. Pointwise (1×1) convolutions have high operation intensity — they're compute-bound. So if a chip is starved for memory bandwidth, cutting activation bits on the depthwise layers (less data to move) buys the most speed for the least accuracy loss. The agent discovers this on its own.

Edge vs cloud, same model, opposite policy. On an edge chip (small batch, low bandwidth) the agent gives depthwise layers fewer activation bits — they're the memory bottleneck. On a cloud chip (big batch, high bandwidth, high parallelism) the model becomes compute-bound, so the agent gives depthwise layers more bits to protect accuracy, since they're a tiny fraction of total compute. Same network, mirror-image policies — and FLOPs could never have told them apart.
Roofline: which layers are memory-bound? (drag bandwidth)
A depthwise conv layer has low operation intensity (OPs/byte). On a bandwidth-starved edge chip, what does HAQ tend to do?

Chapter 5: SHOWCASE — The Bit-Allocation Explorer

This is HAQ's core figure (the per-layer policy bars of Figures 3–4) made interactive — and turned into the actual optimization the agent solves. You're looking at a small MobileNet-style stack: alternating depthwise (memory-bound, low OPs/byte) and pointwise (compute-bound) layers, plus a sensitive first and last layer.

Each layer has its own bitwidth slider. As you change bits, watch three things update live: the model's size, its latency on the selected chip, and the predicted accuracy drop. The "Run HAQ agent" button performs a greedy version of HAQ's search — it spends the budget where it's cheapest in accuracy, exactly the trade-off the RL agent learns.

What to discover: (1) Cutting bits on the big pointwise layers shrinks the model most. (2) On the edge chip, cutting depthwise activation bits cuts latency most (memory-bound). (3) The first/last layers are sensitivity landmines — drop them and accuracy craters. The agent learns to avoid exactly these mines while still hitting the budget.
Per-layer bit allocation → size / latency / accuracy (showcase)

Notice the policy the agent lands on is not "everyone to 4 bits." It's lumpy — fat tolerant layers go low, sensitive landmark layers stay high, and the memory-bound layers on the edge chip get squeezed on activations. That lumpiness is the value of mixed precision, and it's invisible to any uniform-bitwidth scheme.

After running the agent under a tight budget, why is the resulting policy "lumpy" rather than uniform?

Chapter 6: The Reward and the DDPG Agent

We have the loop's shape. Now the engine. Two questions remain: what reward drives the agent, and what algorithm trains it?

A reward that's only about accuracy

You might expect the reward to balance accuracy against latency. It doesn't — and the reason is elegant. HAQ already enforces the resource budget by clipping the action space (Chapter 7), so every policy the agent is allowed to propose already fits the budget. With the constraint handled structurally, the reward is freed to care about one thing:

R = λ × ( accquant − accorig )

Here accorig is the full-precision model's top-1 accuracy (on the training set), accquant is the quantized model's accuracy after a quick one-epoch finetune, and λ=0.1 scales it. The reward is the accuracy you gave up — almost always negative — and the agent's job is to make it as close to zero as possible while living inside the budget.

Why finetune one epoch before measuring? Quantization shocks the network; raw post-quantization accuracy is unfairly low and noisy. One epoch of finetuning lets the weights re-settle onto the quantization grid, giving the agent a cleaner, more honest reward signal. To keep exploration fast, this finetune uses just 100 randomly chosen ImageNet classes.

DDPG: actor-critic for a continuous knob

Because the action is continuous, HAQ uses Deep Deterministic Policy Gradient (DDPG) — an off-policy actor-critic method. The actor maps an observation to an action (the bitwidth knob, squashed by a sigmoid into [0,1]). The critic estimates the value of a state-action pair so the actor knows which direction to move. One step = one layer's weight-or-activation decision; one episode = a full pass over all layers.

HAQ tweaks the Bellman target slightly. The discount γ=1 (every layer's decision matters equally toward the final accuracy), and a baseline B — an exponential moving average of past rewards — is subtracted to cut variance:

k = Rk − B + γ × Q(Ok+1, w(Ok+1))

Exploration is driven by adding truncated Gaussian noise to the actor's output, with noise std starting at 0.5 and decaying ×0.99 per episode — anneal from "try wild bitwidths" to "commit to the good policy."

# HAQ's core loop, distilled to the essential moves (pseudocode)
for episode in range(N_EPISODES):
    bits = []
    for k, layer in enumerate(net.layers):       # one episode = walk all layers
        for kind in ['weight', 'act']:           # two steps per layer
            O = observe(layer, kind, last_action)  # 10-dim state, normalized [0,1]
            a = actor(O) + noise()                 # continuous action in [0,1]
            b = round(B_MIN - 0.5 + a*(B_MAX - B_MIN + 1))  # -> bitwidth 2..8
            bits.append(b); last_action = a
    bits = clip_to_budget(bits, net, hw_sim, budget) # Ch.7: shrink until it fits
    qnet = quantize(net, bits)                       # linear quant, KL-chosen clip c
    acc  = finetune_one_epoch(qnet, subset=100)      # recover, fast
    R = 0.1 * (acc - acc_origin)                     # reward = accuracy delta
    ddpg.update(transitions, R, baseline)            # actor + critic step
    noise.decay(0.99)
Why is HAQ's reward defined only in terms of accuracy, with no latency/energy term?

Chapter 7: Living Within the Budget

How does HAQ guarantee the final model actually fits the latency/energy/size limit? Not through the reward — through the action space itself. This is the trick that lets the reward stay pure.

After the agent has proposed a bitwidth for every layer, HAQ measures the policy's total resource cost on the hardware simulator. If it exceeds the budget, HAQ sequentially decreases the bitwidth of each layer until the constraint is finally satisfied. The agent's proposal sets the relative priorities (which layers it wanted to protect); the clipping step enforces the absolute budget.

Constraint-as-projection. Think of the agent proposing a point in bitwidth-space, then HAQ projecting that point onto the feasible region (the set of policies that fit the budget). The agent never has to learn the budget arithmetic — it just learns where accuracy lives, and the projection handles legality. This separation is why the reward can be a clean accuracy delta.

What this means for the data flow

Concretely: a policy is a length-2N vector of bitwidths (weights + activations per layer). The hardware simulator maps that vector → (latency, energy, size). If size > budget, decrement the layer that loses the least accuracy per bit saved, re-measure, repeat. The agent then sees, via the reward, that aggressive proposals get clipped into mediocre accuracy — so it learns to pre-allocate bits in a way that survives clipping. The constraint shapes the policy indirectly, through experience.

Budget projection — propose a policy, watch it get clipped to fit
How does HAQ guarantee the final model meets the resource budget?

Chapter 8: Results — and Reading the Policy

HAQ was evaluated on ImageNet with MobileNet-V1/V2 against PACT (a strong uniform-bitwidth baseline) across three constraint types: latency, energy, and model size, on edge (Xilinx Zynq-7020) and cloud (Xilinx VU9P) accelerators.

The headline numbers

Constraint / chipResult vs fixed 8-bit
Latency (BISMO edge & cloud)1.4× – 1.95× faster, negligible accuracy loss
Latency (BitFusion, MobileNet-V1)2× faster (20.08 ms → 11.09 ms), almost no accuracy drop
Energy (BitFusion)~2× lower energy, nearly no accuracy loss
vs PACT at equal latencyConsistently higher top-1 accuracy on both edge and cloud

Concretely on the edge accelerator at the 4-bit-equivalent budget, MobileNet-V1 PACT scored 62.44% top-1; HAQ's flexible policy hit 67.40% at the same latency — a ~5-point gain for free, just by spending bits more wisely.

Reading the learned policy — the paper's real prize

The most cited part of HAQ isn't the accuracy table — it's the interpretation. The agent rediscovered hardware physics on its own:

Edge accelerator
Memory-bound. Agent gives depthwise layers fewer activation bits — activations dominate their memory traffic, so trimming them is the cheapest latency win.
vs
Cloud accelerator
Compute-bound (big batch, high bandwidth). Agent gives depthwise layers more bits to protect accuracy — they're a tiny slice of total compute, so it's cheap to be generous.

Same network, opposite policies — driven entirely by the chip's roofline. The agent also learned the human heuristic for free: keep more bits in the first and last layers. No one told it; the reward did. Toggle the chip below and watch the policy flip.

Learned policy: edge vs cloud (toggle the chip)
HAQ gives MobileNet's depthwise layers fewer bits on the edge chip but more on the cloud chip. What drives this flip?

Chapter 9: Connections & Cheat Sheet

HAQ sits at the intersection of three lineages, and pulls from each.

What HAQ doesn't solve (read the fine print). The RL search is expensive — many quantize-finetune-measure cycles per model/chip pair, so re-running for a new chip isn't free. It assumes the hardware supports per-layer mixed precision (true on A12/Turing/BISMO/BitFusion, not on commodity 8-bit-only chips). And it uses a hardware simulator, not the physical device — accurate for these accelerators, but a modeling assumption nonetheless.

Cheat sheet — every equation, every symbol

PieceFormulaWhat each symbol means
Search space82NN layers, 8 weight-bit × 8 act-bit choices each
Linear quantround(clamp(w,c)/s)×sc = clip range, s = c/(2b−1−1) step size, b = bits
Clip choicec = argminx DKL(W ‖ quant(W,b,x))pick c that distorts the weight histogram least
Action→bitsb = round(bmin−0.5 + a(bmax−bmin+1))a ∈[0,1] continuous; bmin=2, bmax=8
RewardR = λ(accquant − accorig)λ=0.1; accuracy delta after 1-epoch finetune
Q-targetQ̂ = R − B + γQ(O',w(O'))γ=1, B = EMA baseline (variance reduction)
Op intensity#MACs / #byteslow → memory-bound (cut bits to save bandwidth)

The one-sentence takeaway

HAQ in a breath: turn per-layer bit allocation into a DDPG sequential-decision problem whose environment is a real hardware simulator — so the agent learns a mixed-precision policy co-designed with the chip, beating uniform-bitwidth baselines by 1.4–2× in latency/energy at equal accuracy, and rediscovering hardware physics (memory- vs compute-bound) as a side effect.

Where to go next

What is HAQ's central, reusable idea — the one transferable beyond quantization?