
EE 641 - Unit 1
Fall 2026
Backpropagation Through Convolutions
[Convolution] V. Dumoulin and F. Visin, “A guide to convolution arithmetic for deep learning,” arXiv preprint arXiv:1603.07285, 2016.
[ResNet] K. He, X. Zhang, S. Ren, and J. Sun, “Deep residual learning for image recognition,” in Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 2016, pp. 770–778.
[DenseNet] G. Huang, Z. Liu, L. van der Maaten, and K. Q. Weinberger, “Densely connected convolutional networks,” in Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 2017, pp. 4700–4708.
[Mobile] A. G. Howard, M. Zhu, B. Chen, D. Kalenichenko, W. Wang, T. Weyand, M. Andreetto, and H. Adam, “MobileNets: Efficient convolutional neural networks for mobile vision applications,” arXiv preprint arXiv:1704.04861, 2017.
[Scaling] M. Tan and Q. Le, “EfficientNet: Rethinking model scaling for convolutional neural networks,” in International Conference on Machine Learning, 2019, pp. 6105–6114.
[Systems] H. He, “Making Deep Learning Go Brrrr From First Principles,” 2022 — blog, but the best short treatment of compute- vs memory-bound thinking.
[Quantize] M. Nagel, M. Fournarakis, R. A. Amjad, Y. Bondarenko, M. van Baalen, and T. Blankevoort, “A white paper on neural network quantization,” arXiv preprint arXiv:2106.08295, 2021.
[Compress] S. Han, H. Mao, and W. J. Dally, “Deep compression: Compressing deep neural networks with pruning, trained quantization and Huffman coding,” in International Conference on Learning Representations, 2016.
[Distill] G. Hinton, O. Vinyals, and J. Dean, “Distilling the knowledge in a neural network,” arXiv preprint arXiv:1503.02531, 2015.
[Transfer] J. Yosinski, J. Clune, Y. Bengio, and H. Lipson, “How transferable are features in deep neural networks?” in Advances in Neural Information Processing Systems, 2014, pp. 3320–3328.
Fully connected layers ignore local structure:
Convolutional layers impose structure:
Classical pipeline: fixed features (SIFT, HOG) → trained classifier.
CNN: filters are trainable parameters — feature extractor and classifier optimized jointly.

Learned filters require gradients through the convolution operation.
For 2D discrete signals \(x\) and kernel \(h\):
\[(x * h)[i,j] = \sum_{m=0}^{K-1} \sum_{n=0}^{K-1} h[m,n] \cdot x[i+m, j+n]\]
where \(K\) is the kernel dimension.
CNNs implement cross-correlation (not convolution):
This distinction is academic during learning since kernels are learned.

One output element requires \(K^2\) multiply–accumulate operations (MACs: one multiply, one add — 2 FLOPs)
Dense layer: parameters scale with activation counts
\[N_{\text{in}} \times N_{\text{out}}\]
Convolutional layer: parameters scale with kernel and channels only
\[K^2 \cdot C_{\text{in}} \cdot C_{\text{out}} + C_{\text{out}} \quad \text{— independent of } H \times W\]
nn.Conv2d(16, 32, kernel_size=3, padding=1) on 64×64 input:
| Quantity | Size |
|---|---|
| Input activations | 16×64×64 = 65,536 |
| Output activations | 32×64×64 = 131,072 |
| Filter weights | 32×(3×3×16) = 4,608 |
| Biases | 32 |
| Trainable parameters | 4,640 |
Each filter spans the full input depth: 3×3×16.
Dense layer with the same activations: \(65{,}536 \times 131{,}072 + 131{,}072 \approx 8.59 \times 10^9\) — a 1.85 million× reduction.

Trade off: fewer parameters, more computation per parameter — 4,640 weights perform 18.9M MACs, each weight applied at all 4,096 spatial positions.
For layer \(\ell\) with pre-activation \(z^\ell = W^\ell a^{\ell-1} + b^\ell\):
\[\delta^\ell = \frac{\partial \mathcal{L}}{\partial z^\ell} = (W^{\ell+1})^T \delta^{\ell+1} \odot \sigma'(z^\ell)\]
Weight gradient: \[\frac{\partial \mathcal{L}}{\partial W^\ell_{ij}} = \delta^\ell_i \cdot a^{\ell-1}_j\]
Matrix form: \[\frac{\partial \mathcal{L}}{\partial W^\ell} = \delta^\ell (a^{\ell-1})^T\]
The transpose operation \((W^{\ell+1})^T\) reverses the linear transformation during backpropagation.

\[\text{Given } \underbrace{\frac{\partial \mathcal{L}}{\partial y[i,j]}}_{\delta^\ell \;(y \,=\, z^\ell)} \text{, we require } \underbrace{\frac{\partial \mathcal{L}}{\partial x[p,q]}}_{\delta^{\ell-1} \text{ after } \odot\, \sigma'}\]
Element \(x[p,q]\) contributes to output positions: \[\{y[i,j] : p-K+1 \leq i \leq p, \; q-K+1 \leq j \leq q\}\]
By chain rule: \[\frac{\partial \mathcal{L}}{\partial x[p,q]} = \sum_{i,j} \frac{\partial \mathcal{L}}{\partial y[i,j]} \cdot \frac{\partial y[i,j]}{\partial x[p,q]}\]
Since \(y[i,j] = \sum_{m,n} h[m,n] \cdot x[i+m, j+n]\):
\[\frac{\partial y[i,j]}{\partial x[p,q]} = \begin{cases} h[p-i, q-j] & \text{if } 0 \leq p-i < K, \; 0 \leq q-j < K \\ 0 & \text{otherwise} \end{cases}\]

\(x[3,3]\) influences a \(3 \times 3\) region in output (for \(K=3\))
Substituting the partial derivative:
\[\frac{\partial \mathcal{L}}{\partial x[p,q]} = \sum_{i=p-K+1}^{p} \sum_{j=q-K+1}^{q} \frac{\partial \mathcal{L}}{\partial y[i,j]} \cdot h[p-i, q-j]\]
Change of variables: \(m = p-i\), \(n = q-j\):
\[\frac{\partial \mathcal{L}}{\partial x[p,q]} = \sum_{m=0}^{K-1} \sum_{n=0}^{K-1} h[m,n] \cdot \frac{\partial \mathcal{L}}{\partial y[p-m, q-n]}\]
This is convolution with a rotated kernel:
\[\boxed{\frac{\partial \mathcal{L}}{\partial x} = \frac{\partial \mathcal{L}}{\partial y} * h^{\text{rot180}}}\]
where \(h^{\text{rot180}}[m,n] = h[K-1-m, K-1-n]\)
The conv form of \((W^\ell)^T \delta^\ell\) — kernel rotation is the transpose.

180° rotation: \(h[m,n] \rightarrow h[K-1-m, K-1-n]\)
To recover original dimensions: \[\text{pad} = K - 1\]
This yields “full” convolution in backward pass.

Each kernel element \(h[m,n]\) appears in computing all output positions:
\[\frac{\partial \mathcal{L}}{\partial h[m,n]} = \sum_{i,j} \frac{\partial \mathcal{L}}{\partial y[i,j]} \cdot \frac{\partial y[i,j]}{\partial h[m,n]}\]
From \(y[i,j] = \sum_{m',n'} h[m',n'] \cdot x[i+m', j+n']\):
\[\frac{\partial y[i,j]}{\partial h[m,n]} = x[i+m, j+n]\]
Therefore: \[\boxed{\frac{\partial \mathcal{L}}{\partial h[m,n]} = \sum_{i,j} \frac{\partial \mathcal{L}}{\partial y[i,j]} \cdot x[i+m, j+n]}\]
This is the correlation between input and output gradient — the conv form of \(\delta^\ell (a^{\ell-1})^T\), the outer product summed over shared positions.

Each weight gradient accumulates contributions from all spatial positions where that weight was applied.
Input region influencing one output element. For stacked stride-1 convolutions:
\[r = 1 + \sum_{\ell} (K_\ell - 1)\]
Two 3×3 layers: \(r = 5\). Three 3×3 layers: \(r = 7\).
| Configuration | Params | Same \(r\) as | Savings |
|---|---|---|---|
| Two 3×3 | \(18C^2\) | one 5×5 (\(25C^2\)) | 28% |
| Three 3×3 | \(27C^2\) | one 7×7 (\(49C^2\)) | 45% |
Plus one extra nonlinearity per layer.
VGG (2014) established the convention; \(K=3\) has been the default since.
Dilated kernels grow \(r\) without additional parameters — spacing \(d\) between taps gives effective extent \(d(K-1)+1\).

\(18C^2\) parameters see the same extent as \(25C^2\) — with two nonlinearities.
Several convolutions at fixed resolution, then downsample:
Per-layer compute \(\propto H \cdot W \cdot K^2 \cdot C_{\text{in}} \cdot C_{\text{out}}\)
Halving spatial (÷4) while doubling channels (×4) → constant compute per layer across stages.
Each downsample also doubles the receptive-field growth rate of every subsequent layer — late stages see the entire image.
Global average pooling: \(H \times W \times C \rightarrow C\), any input size.
Two downsampling mechanisms: strided convolution and pooling.

Output dimension: \[H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} - K}{s} \right\rfloor + 1\]
Convolution with stride \(s\): \[y[i,j] = \sum_{m,n} h[m,n] \cdot x[si+m, sj+n]\]
Insert \((s-1)\) zeros between gradient elements: \[\tilde{g}[i,j] = \begin{cases} \frac{\partial \mathcal{L}}{\partial y[i/s, j/s]} & \text{if } s|i \text{ and } s|j \\ 0 & \text{otherwise} \end{cases}\]
Then apply standard transposed convolution to \(\tilde{g}\).


For \(K \times K\) pooling window:
\[\text{Forward}: \quad y[i,j] = \frac{1}{K^2} \sum_{m=0}^{K-1} \sum_{n=0}^{K-1} x[Ki+m, Kj+n]\]
\[\text{Backward}: \quad \frac{\partial \mathcal{L}}{\partial x[p,q]} = \frac{1}{K^2} \cdot \frac{\partial \mathcal{L}}{\partial y[\lfloor p/K \rfloor, \lfloor q/K \rfloor]}\]
The gradient is uniformly distributed across the pooling window.
\[y[i,j,c_o] = \sum_{c_i=0}^{C_{\text{in}}-1} \sum_{m=0}^{K-1} \sum_{n=0}^{K-1} h[m,n,c_i,c_o] \cdot x[i+m,j+n,c_i]\]
Input gradient (sum over output channels): \[\frac{\partial \mathcal{L}}{\partial x[p,q,c_i]} = \sum_{c_o=0}^{C_{\text{out}}-1} \left( \frac{\partial \mathcal{L}}{\partial y[\cdot,\cdot,c_o]} * h^{\text{rot180}}[\cdot,\cdot,c_i,c_o] \right)[p,q]\]
Weight gradient (correlation structure): \[\frac{\partial \mathcal{L}}{\partial h[m,n,c_i,c_o]} = \sum_{i,j} \frac{\partial \mathcal{L}}{\partial y[i,j,c_o]} \cdot x[i+m,j+n,c_i]\]

\[H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} + 2P - K}{s} \right\rfloor + 1\]
| Convention | Setting | Output |
|---|---|---|
| Valid | \(P=0\), \(s=1\) | \(H - K + 1\) |
| Same | \(P=\frac{K-1}{2}\), \(s=1\) | \(H\) |
| Downsample | \(K=3\), \(P=1\), \(s=2\) | \(\lceil H/2 \rceil\) |
Parameter tensors:
conv.weight: \([C_{\text{out}}, C_{\text{in}}, K, K]\)conv.bias: \([C_{\text{out}}]\)import torch
import torch.nn as nn
x = torch.randn(8, 16, 64, 64) # [B, C_in, H, W]
same = nn.Conv2d(in_channels=16, out_channels=32,
kernel_size=3, stride=1, padding=1)
same(x).shape # [8, 32, 64, 64]
down = nn.Conv2d(16, 32, kernel_size=3,
stride=2, padding=1)
down(x).shape # [8, 32, 32, 32]
valid = nn.Conv2d(16, 32, kernel_size=3) # padding=0
valid(x).shape # [8, 32, 62, 62]
same.weight.shape # [32, 16, 3, 3]
same.bias.shape # [32]conv.weight holds the four indices of the multi-channel sum — \(c_o, c_i, m, n\) — as one tensor.
The im2col transformation linearizes the convolution operation:
# Forward pass
X_col = im2col(X, kernel_size=K, stride=s, padding=p) # Shape: (K²·C_in, H'·W')
W_row = W.reshape(C_out, K²·C_in) # Shape: (C_out, K²·C_in)
Y_col = W_row @ X_col # Shape: (C_out, H'·W')
Y = Y_col.reshape(H', W', C_out)
# Backward pass
dY_col = dY.reshape(C_out, H'·W')
dW = dY_col @ X_col.T # Weight gradient
dX_col = W_row.T @ dY_col # Input gradient (columnar)
dX = col2im(dX_col, shape=(H, W, C_in)) # Reshape to spatialThis reformulation enables hardware acceleration through optimized BLAS libraries.
For a network with \(L\) layers: \[\frac{\partial \mathcal{L}}{\partial x_1} = \prod_{\ell=1}^{L} \frac{\partial f_\ell}{\partial x_{\ell-1}}\]
If \(\left|\frac{\partial f_\ell}{\partial x_{\ell-1}}\right| < 1\): \[\left|\frac{\partial \mathcal{L}}{\partial x_1}\right| \approx \gamma^L \rightarrow 0\]
Skip connections: \(x_\ell = \mathcal{F}_\ell(x_{\ell-1}) + x_{\ell-1}\)
Returns diminish with depth. The original ResNet-1001 degrades on CIFAR-10. Pre-activation block ordering (He et al., 2016b) restores the gain.

Block output: identity plus a learned residual
\[x_\ell = \mathcal{F}_\ell(x_{\ell-1}) + x_{\ell-1}\]
\(\mathcal{F}\) learns the correction to \(x\), not the full mapping. Zero weights give identity — depth cannot hurt initialization.
Basic block (ResNet-18/34): two 3×3 convs at full width.
Bottleneck block (ResNet-50+): 1×1 reduce → 3×3 → 1×1 restore.
The 3×3 runs at 64 channels inside 256-d input/output: basic-block cost (≈70K params) at 4× the width.
| Model | Params | GFLOPs | Top-1 |
|---|---|---|---|
| ResNet-18 | 11.7M | 1.8 | 69.8% |
| ResNet-34 | 21.8M | 3.7 | 73.3% |
| ResNet-50 | 25.6M | 4.1 | 76.1% |
| ResNet-101 | 44.5M | 7.8 | 77.4% |
| ResNet-152 | 60.2M | 11.5 | 78.3% |
Top-1: ImageNet validation accuracy — the highest-scoring class must be the labeled class.
Dimension changes (stride, channel growth) use a 1×1 projection on the shortcut.

Forward: \[x_\ell = \mathcal{F}_\ell(x_{\ell-1}) + x_{\ell-1}\]
Backward: \[\frac{\partial \mathcal{L}}{\partial x_{\ell-1}} = \frac{\partial \mathcal{L}}{\partial x_\ell} \left(1 + \frac{\partial \mathcal{F}_\ell}{\partial x_{\ell-1}}\right)\]
During training:

Addition can wash out earlier features
Instead of addition: \[x_\ell = H_\ell([x_0, x_1, ..., x_{\ell-1}])\]
where \([\cdot]\) denotes concatenation and \(x_\ell\) is the \(k\) feature maps produced by layer \(\ell\) — the new slice, not a running state.
Direct connections: Layer \(\ell\) receives feature maps from all preceding layers
Gradient flow, with \(x_L\) the final layer’s output: \[\frac{\partial \mathcal{L}}{\partial x_i} = \frac{\partial \mathcal{L}}{\partial x_L} \frac{\partial x_L}{\partial x_i} + \sum_{j=i+1}^{L-1} \frac{\partial \mathcal{L}}{\partial x_j} \frac{\partial x_j}{\partial x_i}\]
\(L(L+1)/2\) connections in an \(L\)-layer network


Standard DenseNet-B (Bottleneck):
Pre-activation design rationale:

Layer \(\ell\) in a dense block:
For an \(L\)-layer block: \[\text{Total channels} = k_0 + k \times L\]
Feature maps stored for concatenation: \[\text{Memory} \propto \sum_{\ell=1}^{L} (k_0 + k\ell) = k_0 L + \frac{kL(L+1)}{2}\]
\(k_0 = 64\), \(k = 32\):
| \(\ell\) | Stack in | 1×1 | 3×3 out | Stack after |
|---|---|---|---|---|
| 1 | 64 | 128 | 32 | 96 |
| 2 | 96 | 128 | 32 | 128 |
| 3 | 128 | 128 | 32 | 160 |
| 4 | 160 | 128 | 32 | 192 |
| 5 | 192 | 128 | 32 | 224 |
| 6 | 224 | 128 | 32 | 256 |
Each layer reads the whole stack and appends its \(k\) new channels.
Transition: 1×1 to 128 (\(\theta = 0.5\)) + 2×2 average pool. Dense connectivity is within-block only — the next block starts a fresh stack from the transition output.
Purpose: Control model complexity
If block outputs \(m\) feature maps:
Compression is critical for deep networks


DenseNet-201 matches ResNet-101 accuracy at 2.2× fewer parameters
Must maintain all intermediate features:
Shared memory allocations:
Memory-efficient variant: Recompute activations during backward pass (trading compute for memory)

Loss gradient to layer \(\ell\): \[\frac{\partial \mathcal{L}}{\partial x_\ell} = \frac{\partial \mathcal{L}}{\partial x_L} \frac{\partial x_L}{\partial x_\ell} + \sum_{s=\ell+1}^{L-1} \frac{\partial \mathcal{L}}{\partial H_s} \frac{\partial H_s}{\partial x_\ell}\]
Each layer receives:
Shorter paths to loss function → stronger gradient signal
Average path length: \(\frac{L+1}{2}\) (vs \(L\) in sequential)
Dense connections act as implicit regularization:


Later layers actively use features from all depths, not just immediate predecessors
ResNet-101 (44.5M params):
DenseNet-201 (20M params):
A ResNet block pays for a full-width mapping at every layer. A dense layer pays for one narrow bottleneck — its cost grows only through the 1×1’s input term.

DenseNet-201: 4.3B FLOPs ResNet-101: 7.8B FLOPs
Despite more connections, fewer FLOPs due to:
Concatenation overhead:
GPU utilization:


Dense connectivity principle spawned multiple architectural innovations
Counting unit — the MAC: \(\text{acc} \mathrel{+}= a \cdot b\)
Standard convolution complexity: \[O(K^2 \cdot C_{\text{in}} \cdot C_{\text{out}} \cdot H_{\text{out}} \cdot W_{\text{out}})\]
Example: Single ResNet-50 layer
Mobile constraint: <500M MACs total

Standard convolution combines two operations:
\[y[i,j,c_o] = \sum_{c_i} \sum_{m,n} h[m,n,c_i,c_o] \cdot x[i+m,j+n,c_i]\]
These operations can be separated for efficiency.
\[y[i,j,c_o] = \sum_{c_i} w[c_i,c_o] \cdot \left(\sum_{m,n} h'[m,n,c_i] \cdot x[i+m,j+n,c_i]\right)\]

Each input channel convolved with its own \(K \times K\) kernel:
\[\hat{x}[i,j,c] = \sum_{m,n} h_{\text{dw}}[m,n,c] \cdot x[i+m,j+n,c]\]
Parameters: \(K^2 \cdot C_{\text{in}}\) (vs \(K^2 \cdot C_{\text{in}} \cdot C_{\text{out}}\) standard)
Example: 3×3 kernel, 16 channels


1×1 convolution: linear combination across channels at each position:
\[y[i,j,c_o] = \sum_{c_i} h_{\text{pw}}[c_i,c_o] \cdot \hat{x}[i,j,c_i]\]
Parameters: \(C_{\text{in}} \cdot C_{\text{out}}\)
Use cases:
Example: 256 → 64 channels costs 256×64 = 16,384 params

Reduction factor: \[\frac{\text{Depthwise Separable}}{\text{Standard}} = \frac{K^2 C_{\text{in}} + C_{\text{in}} C_{\text{out}}}{K^2 C_{\text{in}} C_{\text{out}}} = \frac{1}{C_{\text{out}}} + \frac{1}{K^2}\]
For \(K=3\): approaches 9× as \(C_{\text{out}}\) grows. At 32→64 channels: 18,432 → 2,336 params (7.9×).

Total: 569M MACs, 4.2M parameters (vs VGG16: 15.3B MACs, 138M parameters)
Width multiplier \(\alpha \in \{1, 0.75, 0.5, 0.25\}\) and input resolution \(\rho \in \{224, 192, 160, 128\}\) scale the family down from there.
Low-dimensional ReLU causes information loss:

Linear bottleneck: no activation after projection

Expansion factor \(t=6\) typical
\(\text{ReLU6}(x) = \min(\max(0, x), 6)\) — the clip bounds the activation range for fixed-point arithmetic
Divide channels into \(g\) groups, each processed independently:
\[y[i,j,c_o] = \sum_{c_i \in \mathcal{G}(c_o)} \sum_{m,n} h[m,n,c_i,c_o] \cdot x[i+m,j+n,c_i]\]
where \(\mathcal{G}(c_o)\) is the input group for output \(c_o\).
Reduction factor = \(g\) (here: 8×)

No cross-group information flow → limited representation
Deterministic channel permutation:


ShuffleNet 1.5× matches MobileNet accuracy with 2× fewer operations
Given compound coefficient \(\phi\):
\[\begin{align} \text{depth}: \quad d &= \alpha^\phi \\ \text{width}: \quad w &= \beta^\phi \\ \text{resolution}: \quad r &= \gamma^\phi \end{align}\]
Constraint: \(\alpha \cdot \beta^2 \cdot \gamma^2 \approx 2\)
(FLOPS \(\propto\) width² × resolution²)
For EfficientNet: \(\alpha = 1.2\), \(\beta = 1.1\), \(\gamma = 1.15\)

Discovered through hardware-aware NAS:
\[h\text{-}swish(x) = x \cdot \frac{\text{ReLU6}(x+3)}{6}\]
Approximates swish but cheaper:

Search pipeline: platform-aware NAS (MnasNet) for the block structure, NetAdapt for per-layer widths, manual redesign of the expensive stem and head.
| MobileNetV2 | MobileNetV3-Large | |
|---|---|---|
| Design | Manual | NAS + manual |
| Parameters | 3.4M | 5.4M |
| ImageNet top-1 | 72.0% | 75.2% |
| Pixel-1 latency | baseline | −20% |
+3.2% accuracy at lower measured latency — the search optimizes phone latency directly, not FLOPs.
ConvNeXt (2022): “Modernized” ResNet with depthwise conv, larger kernels (7×7), and transformer-inspired designs. Achieves 87.8% ImageNet with standard convolutions.
EfficientNetV2 (2021): Progressive training with Fused-MBConv blocks. Trains 5-11× faster than V1.

Memory bandwidth often dominates latency on edge devices, not arithmetic operations
A layer has two costs:
The architectures so far reduce MACs:
But measured latency and energy fall by much less — FLOPs ≠ latency.
Arithmetic intensity relates the two:
\[I = \frac{\text{MACs performed}}{\text{bytes moved}}\]
A property of the layer, independent of hardware. Computed for one 112²×64 layer:
Reducing MACs without reducing bytes lowers \(I\): the arithmetic decreases, the memory traffic does not.

Architecture reduces MACs. Numeric precision reduces bytes.
Attainable throughput is the minimum of two limits:
\[\text{MACs/s} = \min\left(\text{peak}, \; \text{bandwidth} \times \frac{\text{MACs}}{\text{byte}}\right)\]
Machine balance — the crossover intensity:
\[\frac{\text{peak}}{\text{bandwidth}} = \frac{9{,}750 \text{ GMAC/s}}{1{,}555 \text{ GB/s}} \approx 6.3 \; \text{MACs/byte}\]
(A100, FP32)
Below balance, the arithmetic units wait on memory. A higher peak rate does not help.
The three convolution types at their computed intensities:

Architecture design reduces FLOPs. Below the balance point, only reducing bytes helps.
Every forward pass moves two kinds of bytes:
| FP32 | ResNet-50 | MobileNetV2 |
|---|---|---|
| Weights | 102 MB | 14 MB |
| Peak activation (batch 1) | 3.2 MB | 4.6 MB |
| Weight traffic per image (batch 1) | 102 MB | 14 MB |
At batch 1, all weights are read for every image:
\[\frac{102 \text{ MB}}{1{,}555 \text{ GB/s}} \approx 66 \; \mu\text{s}\]
— a lower bound on latency before the first MAC executes. Batching amortizes weight reads. Edge inference rarely has a batch.
Moving bytes also dominates energy (45 nm, Horowitz 2014):

Bytes per value is the one parameter that scales weights, activations, bandwidth, and energy together.
Reducing bytes per value means changing the number format. The starting point is what FP32’s 32 bits encode.
\[v = (-1)^s \times 1.m \times 2^{\,e - 127}\]
| Field | Bits | Controls |
|---|---|---|
| Sign \(s\) | 1 | polarity |
| Exponent \(e\) | 8 | dynamic range: ±3.4×10³⁸ |
| Mantissa \(m\) | 23 | relative precision: \(2^{-23} \approx 1.2\times10^{-7}\) |
Representable values form a geometric grid: the gap between neighbors is proportional to magnitude. Absolute error grows with scale. Relative error stays \(\approx 2^{-(\text{mantissa bits})}\).

| FP16 | BF16 | |
|---|---|---|
| Layout | 1 / 5 / 10 | 1 / 8 / 7 |
| Max value | 65,504 | ~3.4×10³⁸ |
| Min normal | 6.1×10⁻⁵ | 1.2×10⁻³⁸ |
| Relative precision | \(2^{-10} \approx 10^{-3}\) | \(2^{-7} \approx 8\times10^{-3}\) |
FP16: more precision, less range.
BF16: FP32’s exponent, less precision.
One allocation decision: range vs precision, 16 bits total.

Integers have no per-value exponent — a fixed uniform grid:
Consequences in silicon:
The cost: one shared scale must represent the whole tensor. Choosing the scale, and measuring the resulting error, is a quantization problem.

Uniform spacing allows pure integer MACs.
A convolution MAC chain sums many products. The accumulator cannot use the storage format.
INT8 worked example — 3×3×64 dot product:
| Accumulator | Max | Result |
|---|---|---|
| INT16 | 3.3×10⁴ | overflows after ~2 terms |
| INT32 | 2.1×10⁹ | safe up to ~10⁵ terms |
FP16 accumulation fails by swamping: a small product added to a large partial sum falls below the mantissa. GPU tensor cores — dedicated units executing a small matrix block of MACs per cycle — accumulate FP16 products in FP32 by default.
The narrow format is for storage. The wide format is for arithmetic.
Two standardized 8-bit floats. The name gives the field widths (E4M3: 4 exponent bits, 3 mantissa bits):
| E4M3 | E5M2 | |
|---|---|---|
| Layout | 1 / 4 / 3 | 1 / 5 / 2 |
| Max | 448 | 57,344 |
| Relative precision | ~6% | ~12% |
| Used for | weights, activations | gradients |
The same range-vs-precision allocation at 8 bits:
Hardware support arrived with H100 tensor cores (2022). FP8 training runs with FP32 accumulation and per-tensor scaling.
FP8 is used for transformer training and serving. CNN deployment standardized on INT8.

INT4: 16 levels, two weights per byte.
NF4: 16 levels placed at the quantiles of a Gaussian — a codebook, not a uniform grid. Matches the distribution of trained weights: levels are dense where the weights are.
Sub-byte quantization is standard for LLMs, not CNNs:
The 4-bit methods — GPTQ, AWQ, QLoRA — are large-language-model techniques.

Dense (non-sparsity) tensor-core rates:
| A100 | Peak |
|---|---|
| FP32 | 19.5 TFLOPS |
| TF32 (tensor core) | 156 TFLOPS |
| FP16 / BF16 | 312 TFLOPS |
| INT8 | 624 TOPS |
| H100 | Peak |
|---|---|
| BF16 | 989 TFLOPS |
| FP8 | 1,979 TFLOPS |
TF32: a 19-bit tensor-core-internal format — FP32’s exponent, FP16’s mantissa. TOPS: integer operations per second, the INT counterpart of FLOPS.
Bytes shrink by the same factor: INT8 moves 4× the values per unit bandwidth of FP32.
Which gain applies depends on position on the roofline:

32× from FP32 to INT8, usable only if accuracy holds in the narrower format.
An FP32-trained network must run on an integer grid.
Two tensor families, two different problems:
Weights — fixed before deployment.
Activations — different for every input.
Two questions decide the outcome:
A poor scale collapses accuracy outright. Calibrated per-channel INT8 loses under 0.5%.

Heavy-tailed values, uniform grid, a fixed number of levels.
Map reals to integers with two parameters — a scale \(s\) and a zero-point \(z\):
\[x_q = \text{clip}\!\left(\text{round}\!\left(\frac{x}{s}\right) + z,\; q_{\min},\; q_{\max}\right)\]
Recover an approximation by inverting:
\[\hat{x} = s \,(x_q - z)\]
Symmetric (\(z = 0\)): grid centered at zero, \(s = \alpha / 127\) for range \([-\alpha, \alpha]\) in INT8.
Asymmetric: \(s = (\beta - \alpha)/255\), \(z\) shifts the grid.
Noise analysis, calibration, and training all concern the choice of \(s\) and \(z\).

Six weights, symmetric INT4 (16 levels, \(q \in [-8, 7]\)):
\[\mathbf{w} = [\,0.31,\; -0.84,\; 0.12,\; 2.30,\; -0.55,\; 0.07\,]\]
Step 1 — scale from the range:
\[\alpha = \max|w_i| = 2.30 \qquad s = \frac{\alpha}{7} = 0.329\]
Step 2 — round to the grid:
\[q_i = \text{round}(w_i / s)\]
Step 3 — dequantize: \(\hat{w}_i = s \, q_i\)
| \(w\) | \(q\) | \(\hat{w}\) | error |
|---|---|---|---|
| 0.31 | 1 | 0.329 | +0.019 |
| −0.84 | −3 | −0.986 | −0.146 |
| 0.12 | 0 | 0.000 | −0.120 |
| 2.30 | 7 | 2.300 | 0.000 |
| −0.55 | −2 | −0.657 | −0.107 |
| 0.07 | 0 | 0.000 | −0.070 |
Errors are bounded by \(\pm s/2 = \pm 0.164\), half the grid spacing.
Two small weights collapsed to zero: with 16 levels there is little resolution.
One value set the scale for the whole tensor. The outlier 2.30 is represented exactly. The other five weights — 6× smaller — share a grid sized for it.
Drop the outlier and \(s = 0.84/7 = 0.12\): a 2.7× finer grid for the five remaining weights.
Exact representation of one outlier costs the remaining weights 1.5 bits of precision.
Three decisions per tensor — range, symmetry, bit width — and the range decision dominates the error.
Model rounding as an additive noise channel:
\[\hat{x} = x + e, \qquad e \sim \text{Uniform}\left(-\tfrac{\Delta}{2}, \tfrac{\Delta}{2}\right)\]
Noise power:
\[\sigma_e^2 = \frac{1}{\Delta}\int_{-\Delta/2}^{\Delta/2} e^2 \, de = \frac{\Delta^2}{12}\]
For \(b\) bits spanning \([-\alpha, \alpha]\): \(\Delta = 2\alpha/2^b\), so
\[\sigma_e^2 = \frac{\alpha^2}{3} \cdot 2^{-2b}\]
\[\text{SQNR} = 10\log_{10}\frac{\sigma_x^2}{\sigma_e^2} \approx 6.02\,b + \text{const dB}\]
Each bit adds 6 dB. The constant is set by the loading factor \(\alpha/\sigma_x\) — how much headroom the range leaves. At 4σ loading:
The quantizer is a channel. Bit width sets its SNR.

Assumes fine spacing and no clipping. Choosing \(\alpha\) deliberately violates the second assumption.
Two error sources move in opposite directions as the clip threshold \(\alpha\) grows:
\[\text{MSE}(\alpha) = \underbrace{\frac{(2\alpha/2^b)^2}{12}\,P(|x| \le \alpha)}_{\text{granular}} + \underbrace{\mathbb{E}\big[(|x| - \alpha)^2 \,;\, |x| > \alpha\big]}_{\text{clipping}}\]
Trained weights are heavy-tailed. Min/max range — \(\alpha = \max|x|\) — gives exact representation of a few outliers and coarse resolution for the entire body.
The optimum clips. For a Laplacian tensor at 4 bits, the best \(\alpha\) clips the top fraction of a percent of the mass and reduces total error ~5× vs min/max.

Range selection is a granular-vs-clipping optimization. Calibration methods approximate this optimum from data.
One scale per tensor forces every output channel onto the same grid — and channel weight ranges routinely differ by 10–100×:
Per-channel quantization: one scale \(s_c\) per output channel, weights only.
\[\hat{W}[c, :] = s_c \, W_q[c, :]\]
Free at inference — \(s_c\) factors out of each output row of the matmul. Activations stay per-tensor.
Measured consequence (Nagel et al., 2019, ImageNet top-1):
| MobileNetV2, W8A8 PTQ | Top-1 |
|---|---|
| FP32 | 71.7% |
| Per-tensor | 0.1% |
| Per-channel | 71.2% |
One scale per channel is the difference between broken and working.

No labels, no gradients, minutes of work:
The only estimation problem is step 3 — activation ranges depend on data:
All three are approximations of the clipping-trade optimum, estimated from a few hundred images.

ImageNet top-1, post-training, per-channel weights:
| Model | FP32 | W8A8 PTQ | Δ |
|---|---|---|---|
| ResNet-50 | 75.2% | 75.1% | −0.1 |
| InceptionV3 | 78.0% | 77.8% | −0.2 |
| MobileNetV2 | 71.7% | 71.2% | −0.5 |
(Krishnamoorthi 2018; Nagel et al. 2019)
Over-parameterized networks lose almost nothing. Efficiency-optimized networks lose more, with the loss concentrated in depthwise layers — few weights per channel, wide ranges, no redundancy to absorb the noise.
Where PTQ stops working:
The failure is compounding, not local:
Bias correction and AdaRound (per-weight choice of round-up vs round-down) recover 1–2% without training.
Past that, the fix is structural: put the quantizer inside the training loop.
Insert quantize→dequantize nodes into the training graph:
\[w \;\rightarrow\; \underbrace{s\,\text{clip}\!\left(\text{round}(w/s)\right)}_{\text{fake quant}} \;\rightarrow\; \text{conv} \;\rightarrow\; \ldots\]
Training adapts to the grid:
One obstacle: the round function has no useful gradient.

Rounding is a staircase:
\[\frac{\partial\, \text{round}(x)}{\partial x} = 0 \quad \text{almost everywhere}\]
Backpropagated exactly, every weight gradient is zero at the fake-quant node.
STE: replace the true derivative with the identity inside the representable range:
\[\frac{\partial \hat{w}}{\partial w} := \begin{cases} 1 & |w| \le \alpha \\ 0 & |w| > \alpha \end{cases}\]
\[\frac{\partial \mathcal{L}}{\partial w} = \frac{\partial \mathcal{L}}{\partial \hat{w}} \cdot \mathbb{1}[|w| \le \alpha]\]
The same surrogate trains VQ-VAE codebooks through their discrete bottleneck: gradients copied straight through the nearest-neighbor lookup.

ImageNet top-1 (Krishnamoorthi 2018; representative 4-bit values):
| MobileNetV2 | FP32 | PTQ | QAT |
|---|---|---|---|
| W8A8 | 71.7% | 71.2% | 71.6% |
| W4A8 | 71.7% | ~65% | ~70% |
The cost is a training pipeline: labeled data, the original recipe, typically 10–50% of the original schedule — versus minutes for PTQ.
Decision rule:
Limits at scale:
This drove new PTQ methods — GPTQ, AWQ, weight-only 4-bit serving — for large language models.
For this deck’s models: INT8 per-channel PTQ covers the backbones. QAT covers the mobile deployments.
Quantization shrinks bits per value. Pruning removes values. Distillation replaces the model.
Three ways to compress a network:
Evidence that values can go (Han et al., 2015, ImageNet, no accuracy loss):
| Weights | Pruned | Ratio | |
|---|---|---|---|
| AlexNet | 61M | 6.7M | 9× |
| VGG-16 | 138M | 10.3M | 13× |
Where the redundancy is:
Over-parameterization aids optimization. Inference does not need it.

Criterion: remove weights with \(|w| < \tau\) — threshold per layer or global.
One-shot — prune to target sparsity, fine-tune once:
Iterative — prune a fraction, fine-tune, repeat:
Fine-tuning is not optional — pruning without retraining loses accuracy at any useful sparsity.
Sparse subnetworks at these ratios can be trained from initialization in isolation (Frankle & Carbin, 2019) — the redundancy exists before training, not only after.

Removed weights leave zeros scattered through the tensor, not a smaller tensor.
Storage: sparse formats keep values plus indices.
Compute: the MAC array does not skip zeros.
What unstructured sparsity delivers:
90% of the weights removed, 0% faster: the sparsity pattern does not match what the hardware can use.

Constraining where the zeros go restores the hardware speedup.
Channel pruning — remove whole filters:
2:4 semi-structured (Ampere tensor cores):
The trade at fixed sparsity:

Same fraction removed. Only the two right patterns change what the hardware executes.
A large trained teacher supervises a small student.
Temperature-softened outputs:
\[p_i^{(T)} = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}\]
Student loss — hard labels plus teacher match:
\[\mathcal{L} = (1 - \lambda)\, \text{CE}(y, p_S) + \lambda\, T^2\, \text{KL}\!\left(p_T^{(T)} \,\|\, p_S^{(T)}\right)\]
The teacher runs once per example. Only the student trains.

A hard label carries the class. The teacher’s full distribution carries the class and its similarity structure:
At \(T = 1\) this structure is invisible — the correct class takes ~all the mass. Temperature exposes it.
Consequences for the student:
Example logits (right): one handwritten 2, teacher output at two temperatures.

Same task, smaller model (Sanh et al., 2019):
| BERT-base | DistilBERT | |
|---|---|---|
| Parameters | 110M | 66M |
| Inference speed | 1× | 1.6× |
| GLUE average | 100% | 97% |
40% of the parameters removed for 3% of the benchmark — the standard reference point for distillation at scale.
CNN teachers and students (ImageNet, representative):
What distillation requires:
What it composes with:
Distillation transfers function. Pruning and quantization shrink the representation. The three are independent.
Deep Compression pipeline (Han et al., 2016), applied in sequence:
| AlexNet | VGG-16 | |
|---|---|---|
| Original | 240 MB | 552 MB |
| Compressed | 6.9 MB | 11.3 MB |
| Ratio | 35× | 49× |
No accuracy loss on ImageNet.
Interaction limits:

The three techniques on one plot:
Quantization — fewer bytes per value:
Structured pruning — fewer values:
Distillation — a smaller model:
Deployment order: distill the capacity, prune the remainder, quantize the result.

Bytes per value, number of values, size of the model: three independent reductions of the same cost.
First-layer filters converge to Gabor edges and color blobs regardless of task or dataset (Yosinski et al., 2014).
Specificity increases with depth:
Transferred initialization helps even when target data is abundant.

ImageNet-pretrained weights for the architectures in this lecture:
| Model | Params | GFLOPs | Top-1 |
|---|---|---|---|
| AlexNet | 61.1M | 0.71 | 56.5% |
| VGG-16 | 138.4M | 15.5 | 71.6% |
| ResNet-50 | 25.6M | 4.1 | 76.1% |
| DenseNet-121 | 8.0M | 2.8 | 74.4% |
| MobileNetV3-L | 5.5M | 0.22 | 74.0% |
| EfficientNet-B0 | 5.3M | 0.39 | 77.7% |
Weights are versioned. Same ResNet-50 architecture, improved training recipe:
IMAGENET1K_V1: 76.1% top-1IMAGENET1K_V2: 80.9% top-1+4.7 points from recipe alone — augmentation, LR schedule, and regularization, not architecture.
import torch.nn as nn
import torchvision.models as models
from torchvision.models import ResNet50_Weights
# Pretrained classifier
model = models.resnet50(
weights=ResNet50_Weights.IMAGENET1K_V2
)
# Backbone: drop the classification head
backbone = nn.Sequential(
*list(model.children())[:-1]
)
# Output: [B, 2048, 1, 1] feature vector
# New task head
head = nn.Linear(2048, num_classes)Feature dimensions at the cut point:
Small target dataset (~10³ images), similar domain:
Large target dataset (≥10⁵ images) or distant domain:
Intermediate: freeze layers 1–3, fine-tune the rest — the generality boundary from the transferability experiments.
# Freeze: feature extraction only
for p in backbone.parameters():
p.requires_grad_(False)
optimizer = torch.optim.Adam(
head.parameters(), lr=1e-3
)
# Fine-tune: discriminative learning rates
optimizer = torch.optim.Adam([
{'params': backbone.parameters(),
'lr': 1e-4},
{'params': head.parameters(),
'lr': 1e-3},
])Failure mode: fine-tuning a 25M-parameter backbone on 10³ images with a uniform LR destroys the pretrained features before the head converges.
Every detection and segmentation architecture in this course starts from a pretrained backbone.