Cho, Adya, Naik (Apple), 2023

PDP: Parameter-free
Differentiable Pruning

Make pruning a smooth, trainable decision — with no extra parameters at all. A softmax of each weight against a single threshold turns "keep or kill" into a gradient the task loss can steer, letting pruned weights claw their way back.

Prerequisites: Magnitude pruning + SGD / backprop + Softmax
11
Chapters
6
Simulations

Chapter 0: The Problem

You have trained a beautiful neural network. It works. Now you have to fit it on a phone — limited memory, a tiny battery, a latency budget measured in milliseconds. The standard move is pruning: set a large fraction of the weights to exactly zero, so they cost nothing to store and nothing to compute.

The catch is that not all weights are equal. A weight in a convolution filter that gets reused over a huge feature map does far more work than one in a tiny bottleneck layer. Two networks pruned to the same 90% sparsity can have wildly different accuracy and speed. So the real problem is not "which weights are smallest" — it is "which weights can I afford to lose, given the task loss?"

That question is a chicken-and-egg trap. To know if killing a weight hurts the loss, you'd like the loss to tell you — through gradients. But the act of zeroing a weight (a hard 0/1 mask) is not differentiable: a step function has zero gradient everywhere and an undefined jump at the threshold. The loss cannot flow back through "delete this weight."

The core question: Can we make the prune-or-keep decision differentiable — so the task loss itself decides which weights die — without bolting on a single extra learnable parameter or rewriting the training loop?

The whole field of differentiable pruning exists to answer the first half. PDP's contribution is the second half: doing it for free. By the end of this lesson you will be able to derive PDP's soft mask on a whiteboard, explain why a pruned weight can come back to life, and implement the core in ~10 lines of PyTorch.

Why can't we just backpropagate through a hard 0/1 pruning mask?

Chapter 1: The Key Insight

Here is the trick, stated as plainly as the paper does. A weight w is in one of two symbolic states: Z ("to-prune") or M ("to-keep"). Instead of forcing a hard choice, give each weight a soft mask m(w) ∈ [0,1] — the probability it survives — and multiply the weight by it: ŵ = m(w)·w.

Now the network runs on the masked weights ŵ. Because m(w) is a smooth function of w, the chain rule flows straight through it. The loss can nudge w not just to fit the data, but to push itself toward "definitely keep" (m→1) or "definitely kill" (m→0).

The insight: Almost every other differentiable-pruning method introduces new learnable parameters — a per-weight mask logit, a per-layer threshold — and trains them alongside the weights. PDP introduces none. The soft mask is computed analytically from the weights themselves and a single threshold t that is derived, not learned. The information that would have lived in extra parameters is instead fused into the weight distribution.

Why does "parameter-free" matter so much that Apple put it in the title? Three reasons:

Memory
No mask logits to store. Pruning a 163M-param GPT-2 with OptG needs +124M extra trainable params; PDP needs 0.
Speed
No new params means no extra gradient sync across GPUs, no extra optimizer state. PDP trains 2.7× faster than the parametric method CS on ResNet20.
Simplicity
Drop one masking op into the forward pass. No new layers, no scheduled regularizers, no STE hacks.
Notation we will use throughout. w = a single weight. W = the weight matrix of one layer/entity. r = target prune ratio (e.g. 0.9 = kill 90%). t = the magnitude threshold that splits keep from kill. τ = a temperature controlling mask softness. m(w) = soft keep-mask ∈ [0,1]. ŵ = masked weight = m(w)·w.
What makes PDP "parameter-free"?

Chapter 2: Why Hard Masks Kill Weights for Good

To feel why a soft mask is worth the trouble, we first have to watch a hard mask fail. This is the failure mode PDP was designed to escape, and it's worth understanding viscerally.

Classic magnitude pruning with a threshold t uses a hard mask: keep w if |w| ≥ t, else set it to 0. The forward pass uses ŵ = \mathbb{1}[|w|≥t]·w. During backprop, the gradient that reaches w is multiplied by that same hard mask:

∂L/∂w = \mathbb{1}[|w|≥t] · ∂L/∂ŵ

Read what this says. The moment a weight drops below t, its mask becomes 0, so its gradient becomes 0. It stops receiving updates. It is frozen at whatever near-zero value it had — permanently dead. Even if, later in training, that weight would have become important, it can never grow back, because it gets no signal to do so.

The buried catastrophe. A method that learns a per-layer threshold instead (like STR) makes this worse in a sneaky way. The paper's example: after one update the threshold drops from 0.205 to 0.202 — a tiny move — yet sparsity jumps from 50% to 66% because three weights happened to be sitting just above the line. The threshold is only indirectly tied to sparsity, so you lose precise control of how much you prune. And every newly-pruned weight is now frozen.

So hard-mask pruning has two diseases at once: (1) no recovery — pruned weights are gradient-dead, and (2) sparsity drift — when you steer the threshold, the achieved sparsity lurches unpredictably. PDP cures both: soft masks keep every weight's gradient alive, and deriving t from the target ratio r pins sparsity exactly.

Hard mask vs. soft mask — gradient flow at the boundary

Drag w toward the threshold (dashed line) and toggle the mode. With the hard mask, the gradient multiplier is a brick wall — 1 on one side, 0 on the other. With PDP's soft mask, the multiplier is a smooth S-curve: a weight just below the line still gets a fraction of its gradient, so it can climb back.

Under a hard pruning mask, what happens to a weight that drops just below the threshold?

Chapter 3: Building the Soft Mask from First Principles

Let's derive the mask instead of being handed it. We want two functions of magnitude: z(|w|) = chance of being pruned (state Z), and m(|w|) = chance of being kept (state M). What must they satisfy to be a sensible magnitude-based mask?

Now the pivotal observation. Because m rises monotonically from 0 to 1 and z falls from 1 to 0, and they're continuous, there must be exactly one magnitude where they cross. Call it the threshold t. At that point both are 0.5 — the weight is a perfect coin-flip:

z(t) = m(t) = 0.5

With the boundary conditions m(0)=0 (a zero weight is surely pruned), m(∞)=1 (a huge weight is surely kept), and m(t)=½, any smooth monotone function will do. PDP picks the most natural one in deep learning: a softmax over two logits.

Why softmax? Think of "keep" and "prune" as two classes. The logit for "keep" should grow with the weight's magnitude; the logit for "prune" should be fixed at the threshold's magnitude. PDP uses w^2 as the "keep" logit and t^2 as the "prune" logit. Squaring makes the score depend on magnitude (sign-agnostic) and gives a clean, symmetric curve. The softmax of these two logits automatically satisfies every condition above — sums to 1, monotone, crosses 0.5 exactly at |w|=t.

Writing out the two-class softmax for the keep probability gives PDP's soft mask:

m(w) = exp(w2/τ)exp(w2/τ) + exp(t2/τ)

Let's verify the boundary conditions by hand — this is exactly the kind of check that builds trust:

The role of τ (temperature). Divide both logits by τ. Small τ → the difference (w^2-t^2)/τ blows up → the mask becomes a sharp step (almost a hard mask). Large τ → the logits squash together → the mask is gentle and gray. τ is the dial between "soft and forgiving" and "hard and decisive."

Worked example. Suppose t = 0.5, τ = 0.1, and a weight w = 0.6. Then w^2/τ = 0.36/0.1 = 3.6 and t^2/τ = 0.25/0.1 = 2.5. So m = e^{3.6}/(e^{3.6}+e^{2.5}) = 36.6/(36.6+12.2) = 0.75. The weight is "75% kept": it passes through at ŵ = 0.75 × 0.6 = 0.45. A weight at w=0.4 (below t) would get m = e^{1.6}/(e^{1.6}+e^{2.5}) = 0.29 — mostly pruned, but not dead.
In PDP's soft mask m(w)=e^{w^2/τ}/(e^{w^2/τ}+e^{t^2/τ}), what is m exactly when |w|=t?

Chapter 4: The PDP Lab — Watch the Mask Live

This is the heart of the paper made interactive. Below is a real layer's weights, drawn from a bell-shaped distribution. You control the target prune ratio r and the temperature τ. PDP does the rest: it finds the threshold t that hits your sparsity target, computes m(w) for every weight, and shows you the masked weights ŵ=m(w)·w.

The top plot shows the soft mask curve m(w) over weight magnitude, with the threshold t marked. The bottom shows each weight as a dot — colored by how much of it survives. Watch what happens to weights right at the boundary: they are the gray, uncertain ones PDP keeps fighting over.

PDP soft-mask lab — tune r and τ, see the surviving weights
Three things to try. (1) Crank τ down toward 0.002 — the soft curve snaps into a step, the gray weights vanish, and the result matches the hard mask. (2) Crank τ up — the curve melts into a gentle ramp, and even large weights get scaled down a bit. (3) Slide r up — watch t march rightward and the threshold line eat into the bell. Notice t is derived from r, never learned — slide r and the sparsity is exactly what you asked for.

The "Show hard-mask result" button overlays where a plain magnitude prune would chop. PDP at small τ converges to that line — but during training it approaches it gradually, giving boundary weights many chances to prove themselves before the cut is final.

In the lab, dropping τ toward zero makes the soft mask...

Chapter 5: Finding the Threshold t (No Learning Required)

We've been treating t as given. Where does it come from? This is the "parameter-free" magic: t is computed from the current weights and the target ratio r, fresh after every SGD step. No gradient, no optimizer state — just sorting.

The recipe, using \text{topK} (the largest k magnitudes) and \text{bottomK} (the smallest k):

1. Count
For a layer with n weights and target ratio r, you keep k = (1-r)·n weights and prune the rest.
2. Sort by |w|
The smallest unpruned magnitude is the k-th largest. The largest pruned magnitude is the next one down.
3. Split halfway
t = midpoint between the smallest-kept and the largest-pruned magnitude. This is the value where keep and prune get equal odds.

Why the midpoint, not just the cutoff? Because the soft mask needs m(t)=0.5 to sit between the two camps. Putting t exactly halfway means the smallest kept weight gets m>0.5 (leaning keep) and the largest pruned weight gets m<0.5 (leaning prune) — symmetric, exactly as the equal-chance derivation demands.

Worked example. A layer has 10 weights with magnitudes (sorted): 0.02, 0.05, 0.11, 0.18, 0.30, 0.41, 0.55, 0.70, 0.90, 1.20. Target r = 0.6, so keep k = (1-0.6)×10 = 4 weights (the four largest: 0.55, 0.70, 0.90, 1.20). The smallest kept magnitude is 0.55; the largest pruned is 0.41. So t = (0.55 + 0.41)/2 = 0.48. Every weight then gets its softmax mask against t=0.48.

Where does r itself come from? Three options, all compatible with PDP: (a) sort weights globally after a few warm-up epochs and back out a per-layer r that meets the global target; (b) a budget-allocation algorithm that assigns more sparsity to less-critical layers; or (c) a human just sets it. PDP doesn't care — it only needs r per layer to derive t.

Threshold finder — drag the keep-count, watch t land halfway
The loop, end to end. After each SGD step: (1) recompute t per layer from the current weights and r; (2) forward-pass with ŵ=m(w)·w; (3) backprop the task loss through the soft mask into w; (4) repeat. Because t re-derives every step, the mask tracks the evolving weight distribution automatically — no schedule to tune.
PDP sets the threshold t to...

Chapter 6: The Hidden Gradient Boost

Now the deepest part of the paper — and the most beautiful. Let's differentiate the masked weight ŵ = m(w)·w with respect to w, and see what PDP secretly does to the gradient. (We treat t as fixed within a step, which it is.)

Start from ŵ = m(w)\,w. By the product rule:

dŵ/dw = m(w) + w · dm/dw

We need dm/dw. A softmax of two logits has the standard derivative dm = m(1-m)\,d(\text{logit difference}). The logit difference here is (w^2-t^2)/τ, whose derivative w.r.t. w is 2w/τ. So:

dm/dw = m(w)\,\{1-m(w)\} · (2w/τ)

Substitute back:

dŵ/dw = m(w) + (2w2/τ)\,m(w)\{1-m(w)\}

The gradient that actually updates w is Δw = (dŵ/dw)·Δŵ, where Δŵ=∂L/∂ŵ is the upstream gradient. So:

Δw = m(w)\,Δŵ  +  (2w2/τ)\,m(w)\{1-m(w)\}\,Δŵ
Read the two terms. The first term, m(w)\,Δŵ, is the ordinary masked gradient — exactly what any soft-mask method gives. The second term is PDP's gift: a bonus gradient with the factor (2w^2/τ)\,m(1-m), which is always ≥0. PDP adds push to the weight, on top of the normal update.

When is that bonus largest? The term m(1-m) peaks at m=0.5 — exactly at the threshold, where the prune decision is most uncertain. So PDP injects the strongest extra push precisely into the weights it can't yet decide about. The check-cases:

Why this is the whole point. Boundary weights are where pruning decisions get made. By accelerating exactly those weights toward the loss-reducing direction, PDP helps each undecided weight commit faster and better — to keep or to die. τ shows up as 1/τ in the bonus: a smaller temperature means a bigger boost (and a harder mask).
The gradient-boost factor — where PDP pushes hardest
PDP's extra gradient term (2w^2/τ)\,m(1-m)\,Δŵ is largest when...

Chapter 7: Second Chances — Recovery and Decision Flipping

Put Chapters 2 and 6 together and you get PDP's signature behavior: a weight's prune decision can flip, repeatedly, all the way to the end of training. A near-zero weight that keeps receiving keep-ward gradients (scaled, but never zeroed) can grow back above t and rejoin the network. A large weight that keeps getting pushed toward zero can eventually fall below t and die. The loss, not a one-time magnitude snapshot, decides.

The paper's smoking gun (Figure 3). They tracked the prune decision for every kernel in MobileNet-v1's first conv layer, epoch by epoch. They temporarily round each soft mask to a hard 0/1 decision and mark a cell white if that decision flipped during the epoch. PDP keeps flipping decisions deep into training (epochs 70–90 still show churn). The threshold-learning baseline STR locks in its decisions by epoch 30 and never revisits them. PDP keeps shopping for a better network long after STR has committed.

This matters because pruning decisions made early in training are made with bad information — the weights haven't settled. STR's early lock-in bakes in mistakes. PDP's perpetual second chances let it correct those mistakes as the weights mature. The cost is that train-time and test-time behavior differ slightly (test uses a hard mask), but the recovery upside dominates.

Decision-flip map — PDP vs threshold-learning, over training epochs

Each cell is a kernel. White = its keep/prune decision flipped this epoch; dark = stable. Press replay and compare: PDP's grid keeps sparkling with white cells late into training; STR's grid goes dark and stays dark early. White, here, is the visual signature of "still thinking."

What does PDP's continued decision-flipping late in training buy it?

Chapter 8: One Method, Many Shapes — Structured & Channel Pruning

So far we've pruned individual weights anywhere (unstructured / "random" sparsity). That's great for model size but hardware often can't exploit scattered zeros for speed. Two structured patterns are what actually accelerate inference — and PDP handles both with almost no change.

N:M sparsity (e.g. 2:4)

The constraint: in every consecutive group of M weights, exactly N may survive. NVIDIA Ampere+ GPUs have hardware that runs 2:4 sparsity at ~2× throughput — but only if the zeros follow this pattern exactly. PDP's adaptation is almost embarrassingly simple:

The reshape trick. Treat the layer as if it were made of tiny sub-layers, each holding one group of M consecutive weights. Run PDP within each group: the target ratio is fixed by N:M (e.g. 2:4 → r=0.5), so find t per group and apply the same softmax mask. Data flow: weights [\,\text{out},\text{in}\,] reshape to [\,\dots,\,M\,] groups → per-group threshold → per-group mask → reshape back. The exact same equation, applied to a different grouping.

Channel pruning

Here you remove whole output channels (filters), which shrinks the actual tensor dimensions and speeds up any hardware. PDP's tweak: instead of using each scalar weight's magnitude, compute the L2 norm of each entire channel and use that as the "magnitude" fed into the threshold and mask. A channel with a small norm gets a small mask; the whole channel fades out together, differentiably.

m(c) = exp(‖c‖22/τ)exp(‖c‖22/τ) + exp(t2/τ)

where c is a channel's weight vector and ‖c‖_2 replaces |w|. Every weight in that channel is multiplied by the same channel mask m(c) — so the channel survives or dies as a unit, and the decision stays differentiable.

N:M pruning — PDP applied per group of M
Why "universal" earns its keep. Most pruning papers specialize: one method for unstructured, a different machinery for N:M, yet another for channels. PDP is the same softmax-against-a-threshold every time — you only change what gets grouped and what magnitude you score. That generality is exactly what a deployment team wants: one tool, every sparsity pattern.
How does PDP do channel pruning?

Chapter 9: Does It Work? The Receipts

A pruning method is only as good as the accuracy it keeps at high sparsity and the cost it saves. PDP claims state-of-the-art across vision and language, structured and unstructured. Highlights:

PDP vs. prior art — accuracy at high sparsity (higher is better)
The honest trade-off table (paper Table 1). PDP isn't always the single highest accuracy on every benchmark — but it is the only method that is differentiable, parameter-free, gives precise sparsity control, and has a simple/fast training flow all at once. The pitch is the combination: comparable-or-better quality with dramatically less machinery and cost.
python
# PDP soft mask — the entire core idea, in ~12 lines.
import torch, torch.nn.functional as F

def pdp_mask(W, r, tau=0.03):
    # W: weight tensor; r: target prune ratio; tau: temperature
    a   = W.abs().flatten()
    k   = int(round((1 - r) * a.numel()))   # how many to keep
    s   = torch.sort(a, descending=True).values
    # t = midpoint of smallest-kept and largest-pruned magnitude
    t   = (s[k-1] + s[k]) / 2
    # two-class softmax: keep-logit w^2, prune-logit t^2
    logits = torch.stack([W**2, t**2 * torch.ones_like(W)]) / tau
    m   = F.softmax(logits, dim=0)[0]  # keep-probability in [0,1]
    return m * W                          # masked weights; gradient flows!

# Forward pass simply uses w_hat = pdp_mask(layer.weight, r). No new params,
# no optimizer changes. Recompute every step so t tracks the weights.
Against OptG on GPT-2 at 75% sparsity, PDP's headline advantage is...

Chapter 10: Connections & Cheat Sheet

PDP sits at the crossroads of three ideas you may already know: magnitude pruning (which weight to cut), the straight-through estimator (how others fake a gradient through a hard mask — PDP needs no STE because its mask is genuinely smooth), and Gumbel-softmax / soft relaxations (turning discrete choices into differentiable ones — PDP is a parameter-free cousin).

Where to go next

Cheat sheet — everything in one box.
Soft mask: m(w)=e^{w^2/τ}/(e^{w^2/τ}+e^{t^2/τ})   →   masked weight ŵ=m(w)\,w
Threshold: t= midpoint of (smallest kept |w|, largest pruned |w|), derived from ratio r; keep k=(1-r)n.
Gradient: Δw=m\,Δŵ + (2w^2/τ)\,m(1-m)\,Δŵ — second term = boost, max at m=0.5.
Symbols: w weight · t threshold · r prune ratio · τ temperature (small=hard) · m keep-prob · ŵ masked weight.
Structured: N:M → run PDP per group of M (r=1-N/M). Channel → score by ‖c‖_2, mask whole channel.
Why it wins: 0 extra params · precise sparsity · pruned weights recover · same op for all sparsity patterns.

The one-sentence summary to carry away: PDP turns pruning into a temperature-controlled softmax between each weight and a derived threshold, so the task loss can decide which weights die — and reverse that decision — without a single extra parameter.

Why does PDP not need a straight-through estimator (STE)?