Michael H. Zhu (Stanford) & Suyog Gupta (Google), 2017

To Prune, or Not to Prune:
Gradual Magnitude Pruning

Given a fixed memory budget, is it better to train a big network and delete most of its weights, or to train a small dense network from scratch? A four-symbol cubic schedule answers it — and big-but-sparse wins, every time.

Prerequisites: Training a neural net + basic algebra. That's it.
10
Chapters
6
Simulations
0
Assumed Knowledge

Chapter 0: The Problem

You trained a beautiful image classifier. It has 27 million parameters, hits 78% top-1 accuracy, and weighs about 108 megabytes as 32-bit floats. Now your product manager wants it running on a phone, inside an app that also has to fit photos, music, and the actual app. Your budget is 13 megabytes. The model has to shrink by 8×.

You have two roads in front of you, and they look completely different.

Road A — small-dense
Throw away the big model. Design a smaller network from scratch — fewer channels, fewer hidden units — that naturally fits in 13 MB. Train it normally. Every weight it has is a real, used weight.
vs
Road B — large-sparse
Keep the big 27M-parameter network, but delete 7 out of every 8 weights — force them to exactly zero. Store only the survivors. A model that is 87.5% zeros also fits in 13 MB.

Both roads end at the same memory footprint. Same number of non-zero parameters (NNZ) — the only count that matters for storage and energy. So the question is brutally practical: which one is more accurate?

The core question of the paper: Given a fixed bound on the model's memory footprint, how do we get the most accurate model — train big and prune to sparse, or train small and dense? These are two distinct paths to the same size, and they have very different answers.

Why the question is not obvious

Your instinct might be: "a model that is 87.5% zeros has thrown away most of itself — surely the honest small model that uses all its weights is better?" That is a reasonable guess. It is also wrong, and understanding why it is wrong is the whole lesson.

The other half of the problem is how to delete those weights without wrecking the model. Set 87.5% of a trained network's weights to zero all at once and accuracy collapses to near-random. The network never recovers. So we need both: a reason to believe the sparse road wins, and a careful procedure to walk down it.

A worked sense of scale

Let's make "8× smaller" concrete with the paper's own InceptionV3 numbers:

ModelNNZ paramsSize (fp32)Top-1 acc.
Dense baseline (0% sparse)27.1M~108 MB78.1%
50% sparse13.6M~54 MB78.0%
75% sparse6.8M~27 MB76.1%
87.5% sparse3.3M~13 MB74.6%

Look at the first two rows. Deleting half the weights cost 0.1% accuracy. The network barely noticed. That single fact — half a state-of-the-art model is dead weight — is the seed of everything that follows.

Why is "large-sparse vs small-dense" a fair, meaningful comparison rather than an obvious win for the bigger model?

Chapter 1: The Key Insight

The paper's central empirical claim is short enough to memorize: across CNNs, stacked LSTMs, and seq2seq translation models, large-sparse beats small-dense at every memory budget — achieving up to a 10× reduction in non-zero parameters with minimal accuracy loss.

The intuition is about capacity during training versus capacity at inference. A big network has a rich, high-dimensional space of solutions to explore while learning. It finds a good basin. Most of its weights then turn out to be redundant — small, contributing little. You can carve them away and the good solution survives. The small-dense network never had that rich space to search; it is stuck optimizing in a cramped landscape from step one.

The insight, in one line: Over-parameterization is a training advantage, not an inference requirement. Train wide to find a good solution, then keep only the weights that mattered. The extra weights were scaffolding — you remove the scaffolding once the building stands.

The second contribution: a schedule, not a hyperparameter zoo

Prior pruning methods needed per-layer weight thresholds, two-phase linear schedules with two slopes to tune, and careful hand-holding. Zhu & Gupta replace all of it with one idea: pick a target sparsity and a cubic schedule that ramps the network up to it gradually during normal training. Four hyperparameters, no per-layer thresholds, works across vision and NLP unchanged.

Claim 1 (empirical)
large-sparse > small-dense at equal NNZ, across vision + NLP.
Claim 2 (method)
A single cubic gradual magnitude-pruning schedule reaches that sparsity with almost no tuning.
Why it matters
On-device inference: smaller, cheaper, lower-energy models with state-of-the-art accuracy.

We will build both claims from scratch. First the mechanism (what does "prune" even mean to a tensor?), then the schedule (the cubic curve and why it has that exact shape), then the head-to-head comparison.

What is the intuition for why a large-sparse model beats a small-dense model of equal size?

Chapter 2: Magnitude Pruning — What "prune" Means to a Tensor

"Prune a connection" sounds surgical. In practice it is one of the simplest operations in deep learning: set a weight to exactly zero, and keep it there. The art is deciding which weights, and how to keep them dead while the rest of the network keeps learning.

Which weights? The magnitude heuristic

Earlier methods (Optimal Brain Damage, Optimal Brain Surgeon) used the Hessian — second derivatives of the loss — to rank each weight's importance. Powerful, but expensive: you need second-order information for millions of parameters. The paper uses a cheap proxy that scales: magnitude. A weight's importance is approximated by its absolute value |w|. Small weights barely move their neuron's output, so they are the first to go.

Why magnitude is a reasonable proxy: a weight w multiplies an input x to contribute w·x to a sum. If |w| is tiny, its contribution is tiny across all typical inputs — deleting it perturbs the output the least. It's not optimal (a small weight on a huge input still matters), but it's nearly free to compute and works remarkably well at scale.

How to keep them dead: the binary mask

The mechanism is a binary mask M — a tensor the exact same shape as the weight tensor W, holding 1 (keep) or 0 (prune) for every weight. The network does its forward pass with the masked weights, W ⊙ M (element-wise multiply). To reach sparsity s, you sort all weights by |w|, then zero the mask for the smallest s fraction.

Weff = W ⊙ M,    where Mi = 0 for the smallest s·N weights by |Wi|, else 1

Here W is the layer's full weight tensor, N is its number of elements, s ∈ [0,1] is the target sparsity (fraction of zeros), and is element-wise (Hadamard) product. The pruned weights still exist in memory during training — they're just masked to zero in the forward pass.

The gradient detail that makes it work

Here is the engineering decision that is easy to get wrong. Gradients flow through the mask too. A weight that was masked to zero in the forward pass gets a zero contribution, so it receives no useful gradient and stays put. The surviving weights keep training normally, free to grow and adapt to compensate for their deleted neighbors. This is exactly the recovery mechanism — the network heals around the holes.

Magnitude pruning, live

A grid of 8×8 = 64 weights, colored by magnitude (bright = large |w|, dim = small). Drag the slider to set target sparsity. The smallest-magnitude weights get masked to zero (shown as empty cells) first. Watch how the survivors are always the brightest.

Sparsity 50%
During gradual pruning, what happens to a weight after its mask is set to zero?

Chapter 3: Why One-shot Pruning Fails

The obvious approach: train the model fully, then sort all weights, zero the smallest 87.5%, and ship it. This is one-shot pruning. It is also a disaster at high sparsity, and seeing why motivates the whole gradual idea.

When you delete 7 of every 8 weights at once, you remove a huge fraction of the network's machinery in a single step. The carefully balanced solution the optimizer found — where every surviving weight assumed its neighbors were there — is shattered. Accuracy falls off a cliff. And because you do it after training is over, there are no more gradient steps left for the survivors to compensate.

The core failure: One-shot pruning at high sparsity removes too much, too fast, with no chance to recover. The survivors never get to re-learn the function with the deleted weights gone. Gradual pruning fixes both halves: it removes a little at a time, and it interleaves pruning with training so survivors can heal between cuts.

Visualizing the cliff

Below, the same network is pruned two ways. One-shot: all the sparsity is applied in a single step, then training continues briefly. Gradual: sparsity ramps up over many steps. Watch the accuracy curves. Both end at the same final sparsity, but only one survives the journey.

One-shot vs gradual: the accuracy journey

Both runs target the same final sparsity. The one-shot run (warm) drops everything at the cut step; the gradual run (teal) ramps. Higher target sparsity makes the one-shot cliff deeper and the recovery harder.

Target sparsity 87.5%
Why does one-shot pruning fail at high sparsity where gradual pruning succeeds?

Chapter 4: The Cubic Sparsity Schedule (the math)

Now the heart of the paper — Equation (1). We want a curve st that starts at an initial sparsity si (usually 0) and rises to a final sparsity sf over n pruning steps. The authors chose a cubic ramp. Let's derive its shape and understand every symbol.

st = sf + (si − sf) · (1 − (t − t0) / (n·Δt))3

for t ∈ {t0, t0+Δt, ..., t0+n·Δt}

Every symbol, in plain words

SymbolMeaningPlain analogy
stsparsity applied at training step t"how empty the network is right now"
siinitial sparsity (usually 0)"start full"
sffinal / target sparsity"the goal — e.g. 0.875"
t0step where pruning begins"warm-up before we start cutting"
nnumber of pruning updates"how many cuts total"
Δtsteps between mask updates"how often we cut" (100–1000)

Reading the curve: why cubic, not linear

Let p = (t − t0)/(nΔt) be progress, going from 0 to 1. The schedule is st = sf + (si − sf)(1−p)3. The factor (1−p)3 is the key. At p=0 it's 1, so st=si. At p=1 it's 0, so st=sf. In between, the cube makes it fall fast early and slow late.

That gives the schedule its signature personality: prune aggressively at the start, gently at the end. Early on, the network is dense and full of redundant connections — there's lots of dead weight to cut cheaply. Later, every remaining weight is precious, so we slow down and give the network many steps between small cuts to heal.

Why the cube specifically: the increment per step shrinks like the derivative of (1−p)3, which is −3(1−p)2. As p→1 this approaches 0 — the cuts get vanishingly small near the target. The schedule automatically takes its biggest bites when redundancy is highest and its tiniest bites when the network is most fragile. A linear schedule cuts the same amount every step, ignoring that the network gets more brittle as it empties.

Worked numerical example

Let's compute the schedule by hand. Take si=0, sf=0.875 (the 8× InceptionV3 setting), and n=10 cuts. Compute progress p = k/10 at each cut k, then s = 0.875·(1 − (1−p)3):

Cut kp = k/10(1−p)3s = 0.875(1−(1−p)3)Δs this cut
00.01.0000.0%
10.10.72923.7%+23.7%
20.20.51242.7%+19.0%
30.30.34357.5%+14.8%
50.50.12576.6%(cumulative)
80.80.00886.8%tiny cuts
101.00.00087.5%+0.0%

Read the last column. The very first cut removes 23.7% of all weights in one step — a huge bite while redundancy is plentiful. By cut 8 the network is already at 86.8%; the last two cuts barely move it. That front-loaded shape is exactly what "prune fast when there's slack, slow when it's fragile" looks like in numbers.

The schedule in code

import numpy as np

def cubic_sparsity(t, t0, n, dt, s_i=0.0, s_f=0.875):
    # Equation (1): cubic ramp from s_i to s_f over n cuts.
    if t < t0:
        return 0.0                       # warm-up: no pruning yet
    if t >= t0 + n * dt:
        return s_f                       # target reached, mask frozen
    p = (t - t0) / (n * dt)             # progress in [0, 1]
    return s_f + (s_i - s_f) * (1.0 - p) ** 3

def apply_mask(W, sparsity):
    # Mask the smallest-|W| weights to reach `sparsity` fraction of zeros.
    k = int(sparsity * W.size)         # how many to kill
    if k == 0: return np.ones_like(W)
    thresh = np.sort(np.abs(W.ravel()))[k]  # magnitude cutoff
    return (np.abs(W) >= thresh).astype(W.dtype)

# In the training loop, every dt steps:
#   s = cubic_sparsity(step, t0, n, dt)
#   M = apply_mask(W, s)        # recompute mask at the new sparsity
#   W_effective = W * M         # forward pass uses masked weights
In the cubic schedule, why does it prune aggressively early and gently late?

Chapter 5: The Sandbox — Schedule + Recovery, Live

This is the showcase. It reconstructs the paper's Figure 2 as a live system: the cubic sparsity curve and the accuracy curve that responds to it, just like the real InceptionV3 training run where the 87.5% model suffers a near-catastrophic dip and then heals.

Tune all four schedule knobs. Watch how a too-fast schedule (small n) or a too-late warm-up (large t0) starves the network of recovery time and leaves the accuracy curve scarred. A well-tuned cubic schedule lets accuracy dip with each cut and bounce right back.

How to read it: the teal curve is sparsity (right axis, 0–100%). The warm curve is model accuracy (left axis). Each pruning event nicks accuracy; the flat training steps between cuts are when the survivors recover. Push n down or t0 up and you'll see the recovery fail.
Gradual pruning simulator (Figure 2, interactive)

Drag the four knobs. The accuracy model is a faithful toy: each cut of size Δs causes a dip proportional to Δs × (current fragility), and accuracy recovers exponentially toward a ceiling that sags slightly with total sparsity — the same shape the paper reports.

Target sf 87.5%
Cuts n 20
Warm-up t0 10%
Δt (cut gap) 0.03

Try this experiment the paper effectively ran: set sf to 87.5%, then drag n from 20 down to 3. The final accuracy reading collapses — the schedule is now nearly one-shot. Crank n back up and accuracy returns. That single slider is the difference between Chapter 3's cliff and a healthy sparse model.

Chapter 6: Concept + Realization — The Data Flow

Let's be ruthlessly concrete about what actually happens to tensors during a pruning-enabled training step. This is the difference between knowing about pruning and being able to implement it.

What gets added to the graph

For every layer you choose to prune, you add one tensor: a binary mask M of the same shape as the weight W. If W is a conv layer's kernel of shape [3, 3, 256, 512] (height, width, in-channels, out-channels), then M is also [3, 3, 256, 512], holding 1s and 0s. That's 1.18M mask entries for that one layer — the mask doubles that layer's storage during training (it's discarded at deployment; you just store the sparse survivors).

forward, step t
W [3,3,256,512] ⊙ M [3,3,256,512] → Weff [3,3,256,512]. Conv runs with Weff. Masked entries contribute 0.
↓ loss, backprop
backward
∂L/∂W flows back, but is multiplied by M. Masked weights get gradient 0 → frozen at 0. Survivors update normally.
↓ every Δt steps
mask update
s ← cubic_sparsity(t). Sort |W| over the whole layer, recompute M to keep the top (1−s) fraction. Once t ≥ t0+nΔt, freeze M forever.

Two engineering decisions worth understanding

Why a mask instead of literally deleting weights? Deleting changes the tensor shape and breaks the optimizer state (momentum buffers, etc.). A mask keeps shapes fixed, so the whole training pipeline — optimizer, learning-rate schedule, distributed sync — is untouched. The mask is a soft overlay you can recompute every Δt steps without surgery.

Why recompute the mask, not just keep zeroing the same weights? Between cuts, a previously-surviving weight might shrink below a newly-pruned one. Re-sorting at each cut lets the network reconsider which weights matter as it adapts. A weight isn't condemned forever until the final mask freezes — up to that point the set of survivors can shift.

The deployment payoff: at inference you discard M and W entirely and store only the non-zeros plus their indices (a sparse format like CSR). 27.1M weights at 87.5% sparsity → 3.3M non-zeros. The catch (a real one): sparse storage carries index overhead, and fast sparse matrix-multiply needs hardware/kernel support. The paper is explicit that the NNZ win only translates to a speed/energy win on hardware built for sparsity.

What happens when inputs degrade

Set t0 too late — into the region where the learning rate has decayed to near-zero — and the survivors cannot recover, because there's no gradient magnitude left to heal with. Set t0 too early, before the weights have converged to a meaningful solution, and you prune based on noise: small-magnitude-right-now weights might have become important later. The paper's guidance: prune in the regime where the learning rate is still reasonably high, and choose n alongside the learning-rate schedule.

Why does the method use a binary mask M rather than physically deleting the pruned weights during training?

Chapter 7: Large-Sparse vs Small-Dense — The Verdict

Now the headline experiment. The paper trains pairs of models that land at the same NNZ count via the two roads, across vision (MobileNet) and NLP (Penn Treebank LSTMs), and reads off accuracy. Let's look at the real numbers and make them interactive.

MobileNet on ImageNet (Table 2)

The clean comparison: a dense 0.5-width MobileNet has 1.32M parameters and 63.7% top-1. A 75%-sparse model pruned from the full 1.0-width MobileNet has fewer non-zeros (1.09M) yet hits 67.7% top-1 — 4 points better with less memory. Large-sparse wins.

RoadModelNNZTop-1
dense0.5-width1.32M63.7%
sparse1.0-width, 75% sparse1.09M67.7%
dense0.75-width2.57M68.4%
sparse1.0-width, 50% sparse2.13M69.5%

The interactive sparse-vs-dense frontier

Below, both roads are plotted as accuracy-vs-NNZ curves (the paper's Figure 3 shape). Drag the budget line. At almost every memory budget, the sparse curve sits above the dense curve — more accuracy for the same number of non-zeros. Toggle between the MobileNet (accuracy, higher=better) and Penn Treebank (perplexity, lower=better) datasets.

Sparse vs dense frontier (Figure 3, interactive)

Drag the budget slider to set a non-zero-parameter budget. The readout shows which road gives the better model at that exact budget. The gap between the curves is the value of pruning.

NNZ budget
Why does the gap exist? The dense small model and the sparse large model have the same NNZ, but the sparse one was born from a wider network. Its surviving weights were selected from a much larger, better-explored solution space — and they keep adapting after each cut. The small-dense model optimized inside a tiny box from step one. Same final size, very different provenance.
In Table 2, the 75%-sparse 1.0 MobileNet (1.09M NNZ) beats the dense 0.5 MobileNet (1.32M params) on top-1 accuracy. What does this demonstrate?

Chapter 8: Failure Modes & the Fine Print

A method this clean has sharp edges. The paper is honest about them, and knowing them is what separates using the schedule from understanding it.

1. Pruning fights the learning-rate schedule

This is the subtle one. SGD decays the learning rate over training. If your pruning extends into the low-LR tail, the survivors get cut but have no gradient juice left to heal — accuracy stays scarred. Prune too early, with LR still huge and weights unconverged, and you prune based on noise. The schedule must be co-designed with the LR curve. This is why t0 and n aren't free parameters; they're tied to the optimizer.

The near-catastrophe in Figure 2b: for the 87.5% InceptionV3 model, accuracy plunges mid-training as sparsity ramps — then recovers almost completely with continued training. The dip is real and scary; the recovery is the point. Higher target sparsity makes the dip deeper. If your LR has decayed by the time the dip hits, there's no recovery.

2. Sparsity has no structure

Magnitude pruning removes individual scattered weights — unstructured sparsity. The surviving weight tensor has zeros sprinkled randomly throughout. This is great for parameter count but bad for commodity hardware: a GPU's dense matrix multiply runs at full speed whether the weights are zero or not. You need sparse kernels or special hardware to turn 87.5% zeros into 8× faster inference. (Structured pruning — removing whole channels or filters — is hardware-friendly but generally costs more accuracy.)

3. Index overhead eats into the win

Storing a sparse matrix means storing not just the non-zero values but where they are (row/column indices). At moderate sparsity, the index overhead can offset much of the parameter savings. The NNZ reduction is the upper bound on compression, not the realized number.

4. Not every layer should be pruned

In MobileNet, the paper deliberately leaves the depthwise-conv layers and the first standard conv unpruned — they hold only 1.1% of parameters, so pruning them buys nothing and risks accuracy. 99% of MobileNet's weights live in the 1×1 pointwise convs and the final fully-connected layer; that's where pruning is aimed. Where you prune matters as much as how much.

Why might 87.5% sparsity NOT give you an 8× speedup on a standard GPU?

Chapter 9: Connections & Cheat Sheet

This 2017 paper became the default pruning recipe in TensorFlow's model-optimization toolkit and a baseline that nearly every later pruning paper compares against. Its ideas echo through modern compression.

Where this fits in the lineage

Before (1990s)
Optimal Brain Damage/Surgeon: Hessian-based saliency. Powerful but expensive, didn't scale.
This paper (2017)
Cheap magnitude proxy + cubic gradual schedule. Simple, scalable, near-tuning-free. large-sparse > small-dense.
After (2018+)
Lottery Ticket Hypothesis (sparse subnets exist at init), structured/2:4 sparsity for hardware, pruning + quantization + distillation stacks.

Related lessons on Engineermaxxing

Cheat sheet — everything in one place

ConceptFormula / factWhen it matters
Cubic schedulest = sf + (si−sf)(1−p)3, p=(t−t0)/(nΔt)setting target sparsity over training
Masked forwardWeff = W ⊙ Mevery forward pass
Magnitude ruleprune smallest |W| to reach stevery Δt (100–1000) steps
Gradient gatemasked weights get 0 gradient; survivors adaptrecovery mechanism
Headline resultlarge-sparse > small-dense; up to 10× NNZ cut, minimal accuracy lossthe verdict
Co-design ruleprune while LR is still high; pick n with the LR scheduleavoiding the no-recovery trap
The one-sentence takeaway: Over-parameterization helps you find a good solution; you don't need to keep all of it. A cubic schedule that prunes fast early and gently late — interleaved with training so survivors heal — turns a big network into a small, sparse, equally-accurate one.

"What I cannot create, I do not understand." — and now you can create gradual magnitude pruning from a four-symbol formula.