Efficient Vision · On-Device Backbones

Mobile & NAS Backbones — Vision Models That Fit in a Phone

A ResNet-50 sees an image beautifully — and takes a quarter of a second and a chunk of your battery to do it. On a phone, in a camera app, on a drone, that is forever. This lesson is the story of how the field cut the cost of seeing by 30× without throwing away the accuracy: depthwise convolutions, inverted residuals, squeeze-and-excitation, compound scaling, and the search algorithms that now design these networks for your exact chip.

Prerequisites: you know what a convolution roughly does (a small filter slides over an image) and what a channel is (one feature map). If "a conv layer turns Cin channels into Cout channels" makes sense, you're ready.
10
Chapters
10
Simulations
0
Assumed Knowledge

Chapter 0: Ten Milliseconds, or You Lose the Frame

You are writing the camera app. The user raises the phone, and behind the live preview you want to run a vision model on every frame — to blur a background, to track a face, to read a price tag, to decide where to focus. The preview runs at 30 frames per second. That is your whole budget: 33 milliseconds per frame, and most of that is already spent decoding the camera buffer and drawing the screen. The model gets maybe 10 milliseconds, and not a microsecond more, or the preview stutters and the user feels it.

So you reach for the model everyone trusts: ResNet-50, a 25-million-parameter convolutional network that scores about 76% on ImageNet. You run it on the phone. It takes a quarter of a second — 250 milliseconds — per frame. You are 25× over budget. The phone gets hot. The battery, which a flagship model can drain in an hour of continuous inference, is gone before lunch. The app is unshippable. This is not a tuning problem. The model is the wrong size for the device, and no amount of clever code closes a 25× gap.

Here is the trap to name out loud. The metric that made ResNet famous — top-1 accuracy on ImageNet — says nothing about whether it can run on a phone. A leaderboard that ranks models by accuracy alone is optimizing the wrong axis for half the world's computers, which are phones, watches, doorbells, and drones, not datacenter GPUs. The whole discipline in this lesson exists because accuracy is free to chase and latency is what you actually pay.

What does it cost a model to "see"? The currency is the multiply-accumulate, or MAC: one multiply of an input by a weight, added into a running sum. A convolution is millions to billions of MACs. We often quote FLOPs (floating-point operations), where one MAC counts as two FLOPs (a multiply plus an add). ResNet-50 is about 4 billion MACs per image. A phone-class chip does perhaps tens of billions of MACs per second on this kind of workload — so 4 billion MACs simply cannot finish in 10 milliseconds. The arithmetic is merciless.

The widget below makes the budget physical. Drag the model's MAC count and watch its predicted frame time cross the 10 ms line. Anything above the line drops frames; anything below it ships. The entire field of mobile backbones is the engineering of pushing accurate models under that line.

The latency budget: MACs vs. the 10 ms line

Drag the model size (MACs per image). The bar shows predicted on-device frame time. Cross above the red 10 ms line and the preview stutters; stay below and the app ships. ResNet-50 is far off the right edge.

Model MACs (millions) 300
The misconception to kill on day one: "a smaller model is just a worse model — mobile vision is the cheap, dumb version." False. A well-designed mobile backbone like MobileNetV3 reaches ResNet-level accuracy at a fraction of the compute, because the savings come from removing redundancy in how convolution is computed, not from removing capacity to learn. The work ahead is not "make it dumber." It is "make it compute the same thing far more cheaply." Those are completely different design problems.

Why should you care beyond one camera app? The same 10 ms wall sits in front of every on-device vision system: a smart doorbell deciding "person or package" without sending video to the cloud; a drone avoiding a wire in real time; a watch reading your heart-rate region; an AR headset that must render at 90 Hz. Privacy, latency, cost, and battery all push computation onto the device, and the device is small. Mobile backbones are how vision actually reaches the edge.

Over the next nine chapters we will rebuild the cost of seeing from the ground up. We will split one expensive convolution into two cheap ones (the move that started everything), invert the residual block to save memory, bolt on a near-free attention trick, learn to scale a network with a single dial, and then hand the design problem to a search algorithm that optimizes for your chip's measured latency. We finish at the only honest way to compare these models: the latency–accuracy Pareto frontier. Let's start with the one idea that cut FLOPs by 8× in a single stroke.

ResNet-50 scores ~76% top-1 on ImageNet but takes ~250 ms per frame on a phone, while a mobile backbone hits similar accuracy in under 10 ms. Why is "just use the higher-accuracy model" the wrong call for the camera app?

Chapter 1: Depthwise Separable Convolution

To make convolution cheap we first have to see exactly where its cost comes from. A standard convolution does two jobs at once, and the trick of MobileNetV1 is to notice that and split them apart. Let us count, because the count is the insight.

Take one convolution layer. It reads an input with Cin channels and produces an output with Cout channels, using filters of size K×K (say 3×3), over an output feature map of spatial size H×W. A standard convolution's cost in MACs is the product of all of them: K · K · Cin · Cout · H · W. Every output pixel, in every output channel, is a weighted sum over a K×K patch across all input channels. That "across all input channels" is the expensive part: each output channel mixes spatially and across channels in one shot.

MobileNetV1's idea, the depthwise separable convolution, is to factor that single operation into two stages that, together, do the same two jobs but at a tiny fraction of the cost:

Stage 1 — the depthwise convolution. Apply one K×K filter to each input channel separately. Channel 0's filter only sees channel 0; channel 1's only sees channel 1. No mixing across channels. This does the spatial job (finding edges, textures) per channel. Its cost is K · K · Cin · H · W — the Cout factor is gone because we are not producing Cout outputs here, just filtering the Cin we have.

Stage 2 — the pointwise convolution. Now mix across channels with a 1×1 convolution: for each output pixel, take a weighted sum over all Cin channels to produce each of Cout channels. This does the channel-mixing job. Because the filter is 1×1, there is no K×K spatial extent, so its cost is 1 · 1 · Cin · Cout · H · W = Cin · Cout · H · W.

The FLOP cut, worked by hand. Let K=3, Cin=Cout=64, and an output map H=W=56 (so H·W = 3136).
Standard conv: 3·3·64·64·3136 = 9·4096·3136 ≈ 115.6 million MACs.
Depthwise: 3·3·64·3136 = 9·64·3136 ≈ 1.81 million MACs.
Pointwise: 64·64·3136 = 4096·3136 ≈ 12.85 million MACs.
Separable total: 1.81M + 12.85M ≈ 14.66 million MACs.
Ratio: 115.6 / 14.66 ≈ 7.9× cheaper. Same input, same output shape, one-eighth the multiply-accumulates.

Where does that ~8× come from in general? Divide the separable cost by the standard cost: (K·K·Cin·H·W + Cin·Cout·H·W) / (K·K·Cin·Cout·H·W) = 1/Cout + 1/K². With Cout=64 and K=3 that is 1/64 + 1/9 ≈ 0.0156 + 0.111 ≈ 0.127, i.e. about 7.9× cheaper — matching the worked numbers. For a 3×3 filter the term 1/K² = 1/9 dominates, so the speedup tends toward roughly K² = 9× for wide layers. That single algebraic ratio is the entire economic case for MobileNetV1.

Drag the sliders below to feel the factorization. Watch the standard-conv bar tower over the separable bar, and watch the ratio settle near K² as the channel count grows. The visual is the arithmetic.

Standard vs. depthwise-separable: where the MACs go

Set the filter size and channel count. The tall bar is a standard conv; the short stacked bar is depthwise (spatial) + pointwise (channel) separable. The ratio readout shows how many times cheaper the split is.

Filter size K3
Channels (Cin=Cout)64

Here is the factorization as code — first counting the MACs from scratch so nothing is hidden, then the library one-liner that builds the same block.

python
def standard_conv_macs(K, Cin, Cout, H, W):
    return K * K * Cin * Cout * H * W            # spatial AND channel mixing in one shot

def separable_conv_macs(K, Cin, Cout, H, W):
    depthwise = K * K * Cin * H * W              # one KxK filter per channel — spatial only
    pointwise = Cin * Cout * H * W               # 1x1 conv — channel mixing only
    return depthwise + pointwise

std = standard_conv_macs(3, 64, 64, 56, 56)   # 115.6M
sep = separable_conv_macs(3, 64, 64, 56, 56)   # 14.66M
print(std / sep)                                  # ~7.9x — equals 1/(1/Cout + 1/K**2)
python
import torch.nn as nn
# The library version: a depthwise-separable block is just two Conv2d layers.
def separable(Cin, Cout, K=3):
    return nn.Sequential(
        nn.Conv2d(Cin, Cin, K, padding=K//2, groups=Cin),  # groups=Cin == depthwise
        nn.BatchNorm2d(Cin), nn.ReLU(),
        nn.Conv2d(Cin, Cout, 1),                            # 1x1 == pointwise (channel mix)
        nn.BatchNorm2d(Cout), nn.ReLU())

The single line that makes it depthwise is groups=Cin: it tells PyTorch to give each input channel its own filter with no cross-channel mixing — exactly Stage 1. The following 1×1 conv is Stage 2. This little two-layer pattern is the atom from which MobileNetV1 (and, in evolved form, every network after it) is built.

Misconception: "depthwise separable is mathematically equivalent to a standard conv — it's a lossless rewrite." Not quite. It is a restriction: a standard conv can learn arbitrary spatial-and-cross-channel filters, while the separable version can only express filters that factor into (per-channel spatial) × (cross-channel 1×1). That is strictly less expressive. The bet MobileNet makes — and wins — is that most useful filters are approximately separable, so you give up a little representational power for an 8× compute cut. It is a deliberate accuracy-for-speed trade, not a free lunch.
A layer has K=3, Cin=Cout=128. Using the ratio 1/Cout + 1/K², roughly how many times cheaper is depthwise-separable than standard convolution here?

Chapter 2: Inverted Residuals & Linear Bottlenecks

MobileNetV1 was a flat stack of separable blocks. MobileNetV2 asked a sharper question: how do you add residual connections — the skip-and-add shortcuts that let very deep networks train — to a depthwise world, without paying for them in memory and compute? The answer, the inverted residual block, is so good it became the default block of mobile vision for years.

Recall the classic ResNet bottleneck: it is wide–narrow–wide. You take a wide tensor (many channels), squeeze it to few channels with a 1×1 conv, do the expensive 3×3 spatial work on the narrow tensor, then expand back to wide, and add the skip connection between the two wide ends. The skip connects the fat tensors; the work happens on the thin one.

MobileNetV2 inverts this to narrow–wide–narrow, which is why it is called an inverted residual. You start narrow (few channels), expand with a 1×1 conv by an expansion factor t (typically 6×) to a wide tensor, do a cheap depthwise 3×3 on the wide tensor, then project back down to narrow with another 1×1 — and the skip connection now joins the two narrow ends.

Why invert? Memory. The skip connection has to hold a tensor alive in memory across the block. In the classic block the skip is wide, so you keep a fat tensor resident. In the inverted block the skip is narrow, so the tensor you keep resident is small — the expensive wide tensor exists only transiently inside the block and is never carried across. On a memory-starved phone, that is the difference between fitting and not fitting. The depthwise conv being so cheap is what makes it affordable to do the spatial work on the wide tensor at all.

The second idea, the linear bottleneck, is subtle and important. The final projection (wide → narrow) is followed by no activation function — it is linear. Why remove the ReLU there? Because a ReLU destroys information when the tensor is low-dimensional: clamping negatives to zero in a narrow space collapses structure you cannot recover, whereas in a wide space the same information survives in other channels. MobileNetV2's authors argued the useful signal lives on a low-dimensional manifold, and you must not ReLU it after squeezing into that low-dimensional bottleneck. So: ReLU after the expand and the depthwise (both wide), but linear after the project (narrow).

Inverted residual cost, worked by hand. Input/output 24 channels, expansion t=6, depthwise 3×3, output map H=W=28 (H·W=784).
Expand 1×1: 24 → 144 channels: 24·144·784 ≈ 2.71M MACs.
Depthwise 3×3 on 144 channels: 9·144·784 ≈ 1.02M MACs.
Project 1×1: 144 → 24: 144·24·784 ≈ 2.71M MACs.
Block total:6.44M MACs. The skip connection that's added at the end carries only the narrow 24-channel tensor (24·784 ≈ 18.8K values), never the wide 144-channel one (113K values) — that 6× memory difference for the resident tensor is the entire point of inverting.

Trace the tensor through the block in the widget. Watch the channel count balloon at the expand, the depthwise do its cheap spatial work on the wide tensor, the projection squeeze it back, and the narrow skip connection arc over the top. Toggle the ReLU on the projection to see the "linear bottleneck" choice highlighted.

The inverted residual block: narrow → wide → narrow

Set the expansion factor and watch the middle tensor widen. The arc is the residual skip — notice it connects the narrow ends. Toggle the projection's activation to see why MobileNetV2 makes the bottleneck linear.

Expansion factor t6
Misconception: "expanding to 6× the channels must make the block more expensive than a plain separable conv — why widen at all?" The widen is cheap because the only operation done on the wide tensor is a depthwise conv (one filter per channel, K²·C cost, no cross-channel blowup). The expensive cross-channel mixing happens only in the 1×1 layers, and projecting straight back down keeps the carried tensor small. You buy more representational room in the middle for almost free, then pay nothing to carry it across the block. Width where it's cheap, narrowness where it's costly to remember — that's the inversion.
Why does MobileNetV2's inverted residual put the skip connection on the narrow ends and use a linear (no-activation) projection?

Chapter 3: Squeeze-and-Excitation & Hard-Swish

MobileNetV3 kept the inverted residual and added two near-free upgrades that buy accuracy for almost no latency: a tiny attention-like module called squeeze-and-excitation and a cheaper activation called hard-swish. Both are small, both are clever, and both teach the same lesson — on mobile, the best upgrades cost almost nothing to run.

Squeeze-and-excitation (SE) gives the network a way to say "this channel matters more for this image; turn it up." A convolution treats all channels democratically. But for a picture of a dog, the "fur texture" channel should probably shout while the "sky gradient" channel should whisper. SE learns to re-weight channels per image, in three steps.

Squeeze. Collapse each channel's whole H×W map to a single number by global average pooling. A (C, H, W) tensor becomes a (C,) vector — one scalar summarizing "how much of this channel fired anywhere." This is the same global-pooling move scene classifiers use, here serving as a cheap global descriptor.

Excite. Feed that C-vector through a tiny two-layer network: squeeze it down to C/r channels (a reduction ratio r, often 4 or 16), apply ReLU, expand back to C, and pass through a sigmoid to get C values in [0,1]. These are the channel gates: a learned importance weight per channel.

Scale. Multiply each original channel's entire feature map by its gate. Channels the network deemed important keep their full strength; unimportant ones are dialed down. The whole module adds a negligible number of MACs (a couple of small dense layers on a C-vector) but consistently lifts accuracy — it is the cheapest attention you will meet.

SE, worked by hand. Take 4 channels with global-average-pooled descriptors z = [2.0, −1.0, 0.5, 3.0]. Squeeze to 2 units with weights, ReLU, expand back to 4, sigmoid → suppose the resulting gates are g = [0.88, 0.27, 0.55, 0.95]. Then channel 0 is scaled by 0.88 (kept strong), channel 1 by 0.27 (suppressed — the net judged it unhelpful for this image), channel 3 by 0.95 (boosted). The feature map of channel 1 is multiplied by 0.27 everywhere; its influence on the next layer shrinks to about a quarter. No spatial computation, just a per-channel volume knob set by the image itself.

The second upgrade is the activation. MobileNetV3 noticed that the swish function, x·sigmoid(x), trains better than ReLU but the sigmoid and exponential are slow on mobile. So it swapped in hard-swish: x · ReLU6(x+3) / 6, where ReLU6 clamps to [0,6]. This is a piecewise-linear approximation of swish that uses only clamps and a multiply — no exponential — so it runs fast on integer-friendly mobile hardware while keeping most of swish's accuracy benefit. MobileNetV3 uses hard-swish in the deeper layers (where its benefit is largest) and plain ReLU early (where activations are cheaper and the gain is smaller).

Compare the curves and the gates in the widget. Slide a channel's importance and watch SE scale its activation; overlay ReLU, swish, and hard-swish to see how closely the cheap hard-swish tracks the expensive swish.

SE channel gates & the hard-swish approximation

Top: four channels, each with an SE gate you can set; the bar shows the channel's signal after scaling. Bottom: ReLU vs swish vs hard-swish — hard-swish (dashed) hugs swish using only clamps, no exponential.

Gate on channel 20.30
Misconception: "squeeze-and-excitation is just another conv layer, so it adds real cost." It is far cheaper than a conv: the squeeze is a free average-pool, and the excite is two small dense layers operating on a length-C vector (not on the whole H×W×C tensor). For C=128 and r=4 that's about 128·32 + 32·128 ≈ 8K MACs — thousands, against the millions a single conv layer costs. SE buys image-adaptive channel attention for essentially nothing, which is exactly why mobile architectures adopt it.
What does the squeeze-and-excitation module compute, and why is it cheap?

Chapter 4: Compound Scaling (EfficientNet)

Suppose you have a good mobile block and you want a bigger, more accurate model. You have three knobs you can turn: depth (more layers), width (more channels per layer), and resolution (larger input images). Before EfficientNet, people turned one knob at a time, somewhat arbitrarily. EfficientNet's contribution was to show that these three are coupled, and to scale them together with a single coefficient. This is compound scaling.

Why are the three coupled? Intuition: if you feed a bigger image (more resolution) but keep the network shallow, the deeper features cannot integrate the extra detail — the receptive field does not reach across the larger image. If you make the network deeper but keep the image tiny, you have more layers analyzing a starved input. And more width without more depth or resolution gives you fine-grained channels with nothing new to look at. The knobs only pay off when turned in balance.

EfficientNet formalizes this. Pick a single compound coefficient φ (your overall compute budget). Then scale depth by αφ, width by βφ, and resolution by γφ, where α, β, γ are constants found by a small grid search on the baseline, subject to the constraint that they keep total FLOPs growing predictably.

The constraint is the elegant part. FLOPs scale linearly with depth (twice the layers, twice the work), but quadratically with width (channels appear as Cin·Cout, so doubling width quadruples a layer's cost) and quadratically with resolution (H·W appears, so doubling each side quadruples the pixels). So total FLOPs grow as α · β² · γ², all raised to the φ. EfficientNet constrains α · β² · γ² ≈ 2, which means each unit increase of φ doubles the FLOPs — a clean, predictable dial.

Compound scaling, worked by hand. The published EfficientNet baseline found α=1.2 (depth), β=1.1 (width), γ=1.15 (resolution). Check the constraint: α · β² · γ² = 1.2 · 1.21 · 1.3225 ≈ 1.92 ≈ 2. Now scale to φ=2: depth × 1.2² = ×1.44, width × 1.1² = ×1.21, resolution × 1.15² = ×1.32. FLOPs grow by (1.92)² ≈ 3.7× — close to 22=4, the doubling-per-φ budget. Each knob moved a little; none moved alone. That balanced, modest, simultaneous growth is what beat the one-knob-at-a-time tradition.

EfficientNet-B0 (the φ=0 baseline, itself found by NAS — the topic of Chapter 5) through B7 are exactly this single block scaled up the compound ladder. B0 is mobile-sized; B7 is a server model — same architecture, one coefficient apart. EfficientNet matched the accuracy of much larger hand-designed networks at a fraction of the FLOPs precisely because it never wasted budget over-scaling a single dimension.

Turn the φ dial below. Watch all three knobs rise together along the constrained ratios, and watch accuracy climb while FLOPs double per step. Then try the "unbalanced" toggle — dump all the budget into one knob — and see accuracy lag behind the balanced curve for the same FLOPs. That gap is the entire argument for compound scaling.

Compound scaling: one dial, three balanced knobs

Drag φ to grow the network. The three bars (depth, width, resolution) rise together by αφ, βφ, γφ. Toggle "unbalanced" to spend the whole budget on width alone and watch accuracy fall behind for the same FLOPs.

Compound coefficient φ1.0
Misconception: "to make a model more accurate, just make it deeper — depth is what matters." Depth alone hits diminishing returns fast: a very deep, very thin network fed tiny images starves on input detail and gains little per added layer. EfficientNet's measured result is that for a fixed FLOP increase, splitting the growth across depth, width, and resolution beats pouring it all into any single one. The right mental model is a budget you allocate across three coupled axes, not a single "bigger = better" slider.
EfficientNet constrains its scaling constants so that α · β² · γ² ≈ 2 (depth α, width β, resolution γ). Why the squares on width and resolution, and what does the ≈2 buy?

Chapter 5: Hardware-Aware NAS (MobileNetV4 / UIB)

So far humans designed these blocks. But the design space is enormous — for each of dozens of layers you could pick a filter size, an expansion factor, whether to add SE, which activation, whether to downsample — and the best choice depends on the chip. Neural architecture search (NAS) hands this combinatorial design problem to an algorithm. The key modern twist is hardware-aware NAS: the search optimizes not for FLOPs, but for measured latency on the actual target device.

Why is FLOPs not enough? Because FLOPs are a poor predictor of real latency. Two layers with identical FLOP counts can have wildly different wall-clock times: depthwise convs are memory-bound (they move a lot of data per multiply) and often run slower than their FLOP count suggests, while big dense 1×1 convs are compute-bound and run efficiently on the same hardware that crawls on depthwise. Memory access, cache behavior, kernel availability, and accelerator quirks all break the FLOPs–latency link. The only honest cost is a stopwatch on the target chip.

Hardware-aware NAS makes the cost function literal: the search runs candidate architectures (or estimates them from a measured lookup table of per-block latencies) on the phone, and the objective rewards accuracy while penalizing measured milliseconds. The result is a network tuned to one device's reality — the same search targeting a CPU, a GPU, and a neural accelerator can return three different winning architectures, because each chip rewards different blocks. This is how MnasNet, MobileNetV3, and the EfficientNet baseline were born.

MobileNetV4 (2024) pushed this further with a single flexible block, the Universal Inverted Bottleneck (UIB). UIB generalizes the inverted residual by making the placement of depthwise convolutions optional and configurable: it can be a plain inverted bottleneck, an inverted bottleneck with an extra depthwise before the expansion, one with an extra depthwise after, or a lightweight variant with no extra depthwise at all. The NAS picks, per layer, which of these UIB shapes runs best on the target. One superset block, many instantiations, search chooses.

Why a latency lookup table, by example. Suppose block A is a 5×5 depthwise UIB and block B is a 3×3 inverted residual, and on paper A has 1.2× the FLOPs of B. You'd guess A is 1.2× slower. But you measure on the target: A takes 0.9 ms, B takes 1.1 ms — A is actually faster, because the chip has a fast 5×5 depthwise kernel and B's wider 1×1 saturates memory bandwidth. The FLOP ranking and the latency ranking disagree. NAS that scored by FLOPs would pick the slower block. This inversion is exactly why the search must use measured latency, stored in a per-block table the searcher queries millions of times.

The widget lets you be the hardware-aware search. Choose, per stage, between a few block types; each carries a (FLOPs, measured-latency, accuracy) triple that you can read live. Try to assemble the network that maximizes accuracy under a latency cap — and notice that the FLOP-cheapest choice is often not the latency-cheapest one.

Be the search: pick blocks under a latency cap

For each of four stages, click to cycle the block type. Watch total measured latency and accuracy update. The FLOPs and latency rankings disagree — the block with the fewest FLOPs is not always the fastest. Try to beat the cap.

Misconception: "NAS finds the globally optimal network." It does not, and cannot — the space is astronomically large and the search is heuristic (reinforcement learning, evolutionary search, or differentiable relaxations over a supernet). NAS finds a good network for a specified objective and search space. Change the target chip, the latency table, or the candidate blocks, and you get a different answer. NAS is automated, device-specific design under a budget — not an oracle handing you the one true architecture.
Why does modern mobile NAS optimize for measured on-device latency rather than FLOPs?

Chapter 6: The Pareto Frontier — Why One Number Lies

You now have a zoo of models: MobileNetV1/2/3, EfficientNet-B0…B7, MobileNetV4 variants, ResNets, ViTs. How do you compare them honestly? Not by accuracy alone (that ignores latency) and not by latency alone (that ignores accuracy). The honest comparison is a two-dimensional picture: the latency–accuracy Pareto frontier.

Plot every model as a point with latency on the x-axis and accuracy on the y-axis. A model is Pareto-optimal (or "on the frontier") if no other model is both faster and more accurate than it. A model that some other model beats on both axes is Pareto-dominated — strictly worse, never the right choice. The frontier is the upper-left boundary of the cloud of points: the set of best-possible trade-offs.

This reframes the whole field. There is no single "best mobile backbone." There is a frontier, and where you sit on it depends on your latency budget. If you have 5 ms, you pick the most accurate model at 5 ms. If you have 30 ms, you pick the most accurate model at 30 ms — a different model. The job of architecture research (MobileNet, EfficientNet, NAS) is to push the whole frontier up and to the left, so that at every budget you get more accuracy.

The key insight that makes you literate about these models: a leaderboard sorted by accuracy is reporting a single point and hiding the axis that matters for deployment. "Model X beats Model Y by 1% top-1" is meaningless until you know their latencies. X might be 1% better and 5× slower — Pareto-dominated for any real-time use. Always ask for the second number. A claim of progress in mobile vision is a claim that the frontier moved, not that one point rose.

And one number lies in a second way: the number depends on the device. A model that is Pareto-optimal on a datacenter GPU can be dominated on a phone CPU, because the GPU loves the wide dense ops that the CPU runs slowly. The frontier is per-hardware. This is the deep reason hardware-aware NAS exists: it pushes the frontier on your chip, not on a generic FLOP axis that no real device obeys.

Explore the frontier below. Each dot is a model. The frontier line connects the Pareto-optimal ones; dominated models sit below-and-right of it, greyed out. Switch the device and watch points shuffle — some that were on the frontier fall off it, because the new chip rewards different blocks. This shuffling is the reason "best model" is a device-specific question.

The latency–accuracy Pareto frontier (and how it depends on the chip)

Each dot is a model. The line is the Pareto frontier — the best accuracy at each latency. Greyed dots are Pareto-dominated (something is both faster and better). Switch device and watch points jump on and off the frontier.

Tap a model dot to read its trade-off.
Misconception: "the model on top of the accuracy leaderboard is the one to deploy." Only if you have unlimited latency budget. For real-time on-device work you want the Pareto-optimal model at your specific latency cap on your specific chip. The top-accuracy model is frequently Pareto-dominated for mobile — some smaller model is nearly as accurate and several times faster. Picking the deployment model is choosing a point on a frontier, not reading row one of a table.
Model A is 1.5% more accurate than Model B but runs 4× slower on your target phone. For a real-time camera feature, what is the right way to think about this?

Chapter 7: ShowcaseThe Pareto Explorer

Everything converges here. This is a live mobile backbone you design with sliders, and a Pareto plot that places your design among reference models in real time. You choose the block type, the width multiplier, the input resolution, and the depth; the panel computes predicted FLOPs, predicted on-device latency, and predicted accuracy, and drops your model onto the latency–accuracy plane. The goal: build a point that lands on the frontier — or pushes past it.

The block choices encode the whole lesson. Separable is MobileNetV1's bare depthwise-separable block — cheapest, least accurate per layer. Inverted residual is MobileNetV2 — more accurate, slightly costlier. Inv-res + SE + hard-swish is MobileNetV3 — the accuracy bump for near-free latency. UIB (searched) is MobileNetV4's universal block — the best trade-off, as if a hardware-aware NAS tuned it for the chip. As you change blocks you are literally walking through Chapters 1–5.

Four things to try, each a chapter made tangible:

Start with Separable, then upgrade the block to Inv-res + SE. Accuracy jumps for a small latency cost — you just felt why MobileNetV3 happened.
Crank the width multiplier alone. Watch latency rise quadratically (width enters as Cin·Cout) while accuracy gains taper — the unbalanced-scaling penalty of Chapter 4.
Raise resolution alone. Latency climbs with the pixel count (also quadratic) — another single-knob trap.
Switch the target device. The same design moves on the plane because the latency model changes — Pareto is per-chip, exactly Chapter 6.

Live Pareto explorer — design a backbone, watch it land on the frontier

Pick a block type, width, resolution, and depth. Your model (the orange star) drops onto the latency–accuracy plane among reference models and the frontier line. Try to land on or above the frontier, then switch device and watch it move.

Block typeinv-res
Width multiplier1.00
Input resolution224
Depth (blocks)16
What you just proved to yourself: a mobile backbone is a point on a latency–accuracy plane, and every architectural idea in this lesson is a move that shifts that point up-and-left — cheaper convolution (separable), free memory (inverted residual), near-free attention (SE), balanced growth (compound scaling), device-tuned blocks (NAS/UIB). You cannot read "good model" off accuracy alone; you read it off where the point lands relative to the frontier on your chip. That two-dimensional literacy is the entire lesson in one panel.

If you removed this simulation, you would lose something real: reading "FLOPs grow quadratically with width" is abstract until you drag the width slider and watch your star slide right far faster than it rises. The napkin drawing of this whole lesson is this plane — latency out, accuracy up, a frontier to chase.

Chapter 8: Quantization & Deployment to the Edge

You have a great architecture. There is one more lever, often the biggest single speedup of all, that lives below the architecture: quantization. The model you trained stores its weights and activations in 32-bit floating point (FP32). Quantization replaces those with 8-bit integers (INT8) — and on phone hardware that is roughly a 4× smaller model and 2–4× faster inference, because integer math is cheaper, moves less memory, and maps onto dedicated mobile accelerators.

How do you cram a float into 8 bits? An INT8 value holds 256 levels (−128 to 127). Quantization maps a float range onto those levels with two numbers: a scale s (the size of one step) and a zero-point z (which integer represents 0.0). The rule is q = round(x / s) + z, and you recover an approximate float with x ≈ s · (q − z). You are snapping every float to the nearest rung of a 256-rung ladder. The art is choosing s and z so the ladder covers the values the tensor actually takes.

Quantization, worked by hand. Suppose a weight tensor ranges over [−1.0, 1.0] and you map it to signed INT8 [−127, 127] with zero-point z=0. The scale is s = (max range) / (max int) = 1.0 / 127 ≈ 0.00787 per step. Quantize x = 0.42: q = round(0.42 / 0.00787) = round(53.4) = 53. Dequantize: 53 · 0.00787 ≈ 0.417. The error is |0.42 − 0.417| ≈ 0.003 — about 0.3% of a step's worth, invisible to the network. Quantize x = 0.005: q = round(0.635) = 1, dequantized 0.00787 — here the error is larger relative to the tiny value, which is why small activations near zero are the ones quantization hurts most.

There are two ways to do this. Post-training quantization (PTQ) takes an already-trained float model and quantizes it, using a small calibration set of images to measure each tensor's typical range and pick good scales. It is fast and needs no retraining, but can lose a point or two of accuracy on sensitive models. Quantization-aware training (QAT) simulates the rounding during training (the forward pass uses fake-quantized values, the backward pass flows gradients through), so the network learns weights that are robust to being snapped to the ladder. QAT recovers most of the lost accuracy at the cost of a retraining run.

Why does this interact with everything before it? Because some architectural choices are quantization-friendly and some are not. ReLU6 (used throughout MobileNet) bounds activations to [0,6], which keeps their range tight and easy to quantize — that bound was chosen partly for quantization. The linear bottleneck and hard-swish were likewise designed with integer hardware in mind. Mobile architecture and quantization co-evolved: the blocks are shaped so that snapping them to 8 bits barely hurts.

Slide the bit-width below and watch the weight histogram snap to a coarser ladder. At 8 bits the quantized curve hugs the original; at 4 bits it visibly stair-steps and accuracy drops. The readout shows model size and the round-trip error — the quantitative version of the trade-off you are watching.

Quantize a weight tensor: float → integer ladder

The bell curve is a float weight distribution; the bars are its quantized levels. Lower the bit-width and the ladder coarsens — size shrinks (good) but round-trip error grows (bad). 8 bits is the sweet spot mobile models target.

Bit-width8
python
import numpy as np
def quantize(x, s, z, qmin=-128, qmax=127):
    q = np.round(x / s) + z                  # snap to the integer ladder
    return np.clip(q, qmin, qmax).astype(np.int8)

def dequantize(q, s, z):
    return s * (q.astype(np.float32) - z)     # back to approximate float

w = np.array([-1.0, 0.42, 0.005, 0.99])
s = 1.0 / 127; z = 0                         # scale = range/levels, zero-point at 0
q  = quantize(w, s, z)                        # int8: [-127, 53, 1, 126]
wr = dequantize(q, s, z)                      # ~[-1.0, 0.417, 0.0079, 0.992]
print(np.abs(w - wr).max())                   # tiny — well under one step
Misconception: "quantization is just lossy compression for storage — it makes models smaller but not faster." Storage shrinkage is only half the win. The bigger win is speed and energy: INT8 matrix multiplies run on dedicated integer units that are far faster and more power-efficient than FP32 on mobile chips, and moving 8-bit data costs a quarter of the memory bandwidth of 32-bit. On many phones quantization is the single largest latency reduction available — often bigger than any one architectural tweak — which is why it is the final, mandatory step in shipping a model to the edge.
A weight tensor spans [−1, 1] and is quantized to signed INT8 [−127, 127], zero-point 0. The scale is 1/127 ≈ 0.00787. What integer represents x = 0.42, and why does INT8 help latency, not just size?

Chapter 9: Connections & Where It's Used

Mobile backbones are not a niche; they are the substrate of on-device intelligence. The pattern you built — factor the expensive operation, invert the block to save memory, add near-free attention, scale with one balanced dial, then let a search tune it to the chip, and finally quantize — recurs everywhere computation must be small. Naming the connections turns this lesson into a map.

The shared idea is efficiency-as-design. Depthwise separable convolution is the same "factor a big operation into cheaper pieces" move that low-rank factorization plays in language-model adapters and that separable filters play in classical signal processing. The squeeze-and-excitation gate is a lightweight cousin of the attention mechanism you meet in transformers — a learned, content-dependent re-weighting, here over channels instead of tokens.

Where it lands in the corpus. The convolution itself is taught from zero in CNN Classification; the whole-image-to-one-label task these backbones feed is Visual Scene Classification and Acoustic Scene Classification, whose edge constraint (a battery-powered sensor classifying its environment) is exactly the budget this lesson optimizes. The attention machinery the SE gate foreshadows is in Vision Transformer and The Transformer, and the modern blend of convolution and attention — relevant to MobileNetV4's hybrid stages — is in Conv-Transformer Hybrids. For the broader low-resource framing, see TinyML.

ArchitectureHeadline ideaWhat it boughtYear
MobileNetV1Depthwise-separable conv~8–9× fewer FLOPs per layer2017
MobileNetV2Inverted residual + linear bottleneckResiduals with a tiny memory footprint2018
MobileNetV3SE + hard-swish (NAS-found)Accuracy lift for near-zero latency2019
EfficientNetCompound scaling (depth/width/res)Balanced scaling → better FLOP–accuracy2019
MobileNetV4Universal Inverted Bottleneck + HW-aware NASDevice-tuned Pareto-optimal blocks2024
The one idea to carry out the door: a mobile backbone is the answer to "compute the same useful function with the fewest measured milliseconds on this chip." Every technique — separable conv, inverted residual, SE, hard-swish, compound scaling, hardware-aware NAS, quantization — is a way to move a point up-and-left on the latency–accuracy frontier. Master the frontier and the device-dependence of it, and you can read, choose, and design these networks instead of just naming them.

Cheat sheet

ThingWhat to remember
Why mobileReal-time budget (~10 ms/frame), battery, privacy → compute must run on-device, which is small
Depthwise separableSplit conv into depthwise (spatial, per-channel) + pointwise (1×1 channel mix); ~K² cheaper (ratio 1/Cout+1/K²)
Inverted residualnarrow→expand→depthwise→project; skip on narrow ends (saves memory); linear bottleneck (no ReLU after project)
SE + hard-swishSE = per-image per-channel gate (squeeze, excite, scale), near-free; hard-swish = swish without the exponential
Compound scalingOne dial φ scales depth/width/res together by αφφφ, with α·β²·γ²≈2 (FLOPs double per φ)
Hardware-aware NASSearch blocks by measured device latency, not FLOPs; UIB (MobileNetV4) = one flexible block, search picks per layer
Pareto frontierCompare on latency vs accuracy; pick the Pareto-optimal model at your budget on your chip; progress = frontier moves up-left
QuantizationFP32→INT8: q=round(x/s)+z; ~4× smaller, 2–4× faster; PTQ (calibrate) vs QAT (train-aware)

"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." — Antoine de Saint-Exupéry, which is, quite literally, the design philosophy of every network in this lesson.