Majid Al-RaimiActivations and peak memory

COE 592Lecture 03Part 03

Activations and peak memory

Why activations rather than parameters are the memory bottleneck in inference and training, how memory is spread across layers of MobileNetV2 and MCUNet, and how to compute total and peak activations for AlexNet.

Concepts
4
Slides
18-22
Reading
24 min
Understood
0/4 concepts

Why this part matters

When a vision model is pushed onto a UAV, a robot controller or an IoT board, the first question a compiler or a reviewer asks is not "how many parameters" but "what is the peak activation". Those boards carry 256 kB to 512 kB of SRAM and no DRAM, and it is the activations, not the weights, that have to live there.

Part 02 counted weights and turned them into a model size. This part counts the other tensor family, the activations, and shows why they decide fit. You get the two ratios to quote in an exam (parameters fell 4.6x while peak activation rose 1.8x; in training, 4.3x against 1.1x), the reason MobileNet-style models can still fail on a microcontroller, a rule for where in a network the memory sits, and a formula for peak memory that you can apply to any layer list.

By the end you can

  1. Explain why activations, not parameters, bound memory in both CNN inference and training, quoting 4.6x against 1.8x and 4.3x against 1.1x.
  2. Read a per-block or per-layer memory profile and say which layers set the SRAM peak, which set the flash budget, and why resolution and channel growth cause it.
  3. Compute total and peak #activations for a given layer list using peak ≈ max over layers of input + output, and convert to bytes for a chosen bit width.
  4. State the assumptions behind the peak approximation: layer-by-layer execution, no branches, no in-place operations, no scratch buffers.
  5. Place weights in flash and activations in SRAM, and connect activation size to the memory term of the latency model from part 01.

Take a real target: an STM32F746 microcontroller has a Cortex-M7 core, 1 MB of flash and 320 kB of SRAM. It has no DRAM and no operating system; MCUNet describes such boards as bare-metal devices (Lin et al., 2020). Now take two ImageNet classifiers that both reach about 70 percent top-1 accuracy with every value stored as an 8-bit integer. ResNet-18 needs 11.2 MB for its weights. MobileNetV2 at width 0.75 needs about 2.5 MB as MCUNet's Figure 8 labels the bar, a 4.6x reduction on the slide (strictly 11.2 / 2.5 ≈ 4.5; the paper rounds to 4.6x). If parameters were the memory story, the second model would be the obvious choice.

Then look at the other pair of bars. The largest amount of activation memory that ResNet-18 needs at any moment is 0.9 MB. For MobileNetV2-0.75 it is 1.7 MB. The efficient model is 1.8x worse on peak activations, and the MCUNet paper says so plainly: MobileNetV2 "reduces the model size by 4.6x compared to ResNet-18, but the peak activation size increases by 1.8x, making it even more difficult to fit the SRAM on microcontrollers" (Lin et al., 2020). Neither model fits 320 kB. The memory bottleneck moved without anyone shrinking it.

Parameter memory drops 4.6x from ResNet-18 to MobileNetV2-0.75, but peak activation rises 1.8x. Both peak bars stand far above a 320 kB SRAM line.

Two budgets, and the smaller one belongs to activations

The reason the two bars behave differently is that they are paid from different memories. Weights are read-only: they are written once when the firmware is flashed and only read during inference, so they live in flash, the larger and cheaper memory. Activations are produced by one layer and consumed by the next on every single inference, so they must sit in memory that can be written and read at speed, which on a microcontroller is the on-chip SRAM. MCUNet puts it in one sentence: "SRAM constrains the activation size (read&write); Flash constrains the model size (read-only)" (Lin et al., 2020). The model size from part 02 answers the flash question. The peak activation answers the SRAM question, and SRAM is the scarcer of the two by a factor of two to four on every board in MCUNet's list (the STM32F746 has about 3x more flash than SRAM).

This also explains why bit width alone does not rescue MobileNetV2. The slide already assumes 8-bit integers for both weights and activations. Even after that 4x shrink from 32-bit floats, the peak sits at 1.7 MB. MCUNet's Table 1 reports 1.7 MB for full-width int8 MobileNetV2 (6.8 MB in fp32), 5.3x over the 320 kB of an STM32F746, not counting im2col or other runtime buffers. Quantization scales both bars by the same factor; it does not change which bar is the problem.

ModelParam memory (flash)Peak activation (SRAM)Fits STM32F746 (1 MB flash, 320 kB SRAM)?Fits STM32H743 (2 MB flash, 512 kB SRAM)?
ResNet-1811.2 MB0.9 MBNo (flash and SRAM)No (flash and SRAM)
MobileNetV2-0.752.5 MB1.7 MBNo (flash and SRAM)No (flash and SRAM)
MCUNet (paper, Figure 8)1.9 MB0.49 MBNo (flash and SRAM)Yes
Two models at about 70 percent ImageNet top-1, int8, against two MCUNet target boards

The MCUNet row comes from Figure 8 of the same paper and is there to show what solving the right problem looks like: its designers shrank the peak activation below 0.5 MB rather than chasing a smaller parameter count, and only then did an ImageNet model fit a 512 kB board. The 4.6x model is not an efficient model for this class of device; it is an efficient model for a phone, where DRAM is plentiful and flash is not the constraint.

Why MobileNetV2 in particular: an imbalanced profile

The right-hand chart explains where MobileNetV2's peak comes from. Plot the memory that each of its eighteen blocks needs and the profile is wildly uneven: block 2 needs 1372 kB, blocks 0, 1, 3 and 4 sit between roughly 500 kB and 600 kB, and the remaining thirteen blocks mostly need 40 kB to 170 kB. Draw the 256 kB constraint of a typical microcontroller and exactly five blocks cross it. MCUNetV2, the follow-up paper that this chart comes from, states it directly: "The first 5 blocks have large peak memory, exceeding the memory constraints of MCUs, while the remaining 13 blocks easily fit 256kB memory constraints. The third block has 8x larger memory usage than the rest of the network" (Lin et al., 2021).

Eighteen MobileNetV2 blocks against a 256 kB line: the first five stand above it, with block 2 at 1372 kB, while thirteen stay below.

The two charts on the slide come from different papers with different memory accounting: the left bars are MCUNet's (2020) figures for MobileNetV2-0.75, while the right chart is MCUNetV2's analytic input-plus-output count for full-width MobileNetV2 in int8, so the numbers are not directly comparable. Its block 2 peak is 16×112×112 + 96×112×112 = 1,404,928 B, which is 1372 kB in 1024-byte kilobytes.

The cause is the shape of MobileNetV2's inverted residual block. Each block first expands the channel count about six times with a 1×1 convolution, applies a depthwise 3×3, and projects back down. The depthwise step is what makes the block cheap in weights and MACs, exactly as part 02 computed. But the expanded tensor exists at full spatial resolution, and in the early blocks that resolution is 112×112 or 56×56. Six times the channels at a large H×W gives a huge activation in a block that owns almost no parameters. MCUNet's per-block peak memory analysis (Figure 11) measures the same imbalance on a MobileNetV2 scaled to 0.3x width to fit 320 kB: even then one block has 2.2x the peak activation of the average block (Lin et al., 2020). A model can be light in weights and heavy in activations at the same time, and this architecture is the canonical example.

Quick check

At about 70 percent ImageNet top-1 with 8-bit values, what happens to peak activation memory going from ResNet-18 to MobileNetV2-0.75?

Recall

Why is MobileNetV2 not microcontroller friendly even though it has few parameters?

Its early inverted residual blocks expand channels about six times while the feature map is still at high resolution, so block 2 needs 1372 kB and the first five blocks all exceed 256 kB. The binding budget on a microcontroller is SRAM, which holds activations, not flash, which holds the weights.

Recall

Where do weights and activations live on a microcontroller at run time, and which one sets the peak?

Weights live in flash (read only, written once). Activations live in SRAM (written and read every inference). The largest input plus output pair sets the SRAM peak.

Inference keeps a few activations alive; training keeps all of them. Switch to 32-bit floats and a training step of ResNet-50. Its weights take 102 MB. Its activations, for one mini-batch, take 707 MB, about 7x more. Now swap in MobileNetV2 at width 1.4: the weights fall to about 24 MB, a 4.3x reduction, but the activations fall only to about 626 MB, a 1.1x reduction. The TinyTL paper that produced these numbers concludes: "It is the activation that bottlenecks the training memory, not the parameters" (Cai et al., 2020).

ModelParameters (MB)Activations (MB)Activations ÷ parameters
ResNet-50102707about 7x
MobileNetV2-1.4about 24about 626about 26x
Reduction4.3x1.1x
Training memory per mini-batch, fp32 (slide values, batch 8; TinyTL Figure 1 shows exactly double at batch 16)

The mechanism is backpropagation itself. Write one linear layer as a_(i+1) = a_i W + b, where a_i is the layer's input activation. To update the weight you need the gradient of the loss with respect to W, and that gradient is the outer product of the input activation with the gradient flowing back from the next layer.

LWi=aiTLai+1,Lbi=Lai+1\frac{\partial L}{\partial W_i} = a_i^{\mathsf{T}}\,\frac{\partial L}{\partial a_{i+1}}, \qquad \frac{\partial L}{\partial b_i} = \frac{\partial L}{\partial a_{i+1}}
Backpropagation through one linear layer (TinyTL, equation 2). The weight gradient needs the stored input a_i; the bias gradient does not.

The forward pass computes a_1, then a_2, and so on up to the loss. The backward pass returns in reverse order, and when it reaches layer i it must still have a_i. So every layer's input has to be held from the moment it is produced until the backward pass comes back to it. Goodfellow, Bengio and Courville make the same point for a multilayer perceptron: "we need to store the input to the nonlinearity of the hidden layer. This value is stored from the time it is computed until the backward pass has returned to the same point," and the cost per layer is O(m·n_h) for a mini-batch of m examples and n_h hidden units (Goodfellow et al., 2016, section 6.5.7). Depth multiplies that cost by the number of layers, and the batch size multiplies it again.

Inference slides a two-buffer window along the network. Training fills every buffer on the forward pass and only frees each one when the backward pass reads it.
ModeWhich activations are heldHow manyMemory rule
InferenceInput and output of the layer running nowTwo tensorsLargest input + output pair
TrainingEvery layer input a_i until its dL/dW_i is computedAll tensors, times the batchSum of all activations × batch size
What has to be alive at once
Mtrainmiaibytes  +  Mweights  +  Mgradients  +  MoptimizerM_{\text{train}} \approx m \sum_i |a_i| \cdot \text{bytes} \;+\; M_{\text{weights}} \;+\; M_{\text{gradients}} \;+\; M_{\text{optimizer}}
Training memory. The slide counts only the first two terms, so the true gap to inference is even larger.

Worked example

AlexNet in fp32, inference against training

  1. Inference peak

    The last concept derives AlexNet's peak (440,928 values) and total (932,264 values); take them as given here. At 4 bytes each, the peak is 1.76 MB of activations alive at once.
  2. Training, batch of one

    All 932,264 activations are held for the backward pass: 932,264 × 4 B = 3.73 MB, more than double the inference peak before counting gradients.
  3. Training, batch of sixteen

    Activations scale with the batch: 16 × 3.73 MB ≈ 60 MB. The weights do not scale; AlexNet's 61M parameters stay at 244 MB whatever the batch, and their gradients add another 244 MB.
  4. Why the slide's bars look the way they do

    AlexNet is unusual: its huge fully connected layers make weights dominate even in training. ResNet-50 and MobileNetV2 are mostly convolutions with large feature maps, so at the slide's batch of 8 their activations (707 MB and about 626 MB) dwarf their weights (102 MB and 24 MB).

The formula also points to the cure that TinyTL proposes and that this course returns to when it reaches on-device learning. The bias gradient in equation 2 needs no stored activation at all. If you freeze the weights and update only biases (plus a small added module), the a_i tensors never have to be kept, and the 707 MB term collapses. That is a memory argument, not an accuracy argument, and it is only visible once you have separated activations from parameters as this slide does. It matters doubly on a microcontroller, because TinyTL notes that DRAM access consumes two orders of magnitude more energy than on-chip SRAM access (Cai et al., 2020), echoing the Horowitz numbers from part 01.

Quick check

Why does training need far more activation memory than inference for the same network?

Recall

Quote the four ratios from slides 18 and 19 and say what each one compares.

4.6x less parameter memory and 1.8x more peak activation: ResNet-18 to MobileNetV2-0.75, int8, inference. 4.3x less parameter memory and 1.1x less activation memory: ResNet-50 to MobileNetV2-1.4, fp32, training at batch 8.

Recall

Why does training need every activation while inference needs only two?

The weight gradient dL/dW_i is the product of the stored input a_i and the gradient from the next layer, so every a_i is held until the backward pass reaches it. Inference has no backward pass and frees an input as soon as the layer's output exists.

Plot, layer by layer, how much memory the activations and the weights of an efficient network take, and a clear shape appears. For MCUNet, a network designed for microcontrollers, the first six layers need 50 kB to 75 kB of activation memory and almost no weight memory, and activation stays the dominant term, mostly 20 kB to 55 kB, out to about layer 17. Around layers 18 to 30 both are small, under about 15 kB. From layer 32 onward the weight memory climbs to 35 kB to 75 kB while activations stay near 5 kB to 10 kB. The total traces a U: expensive at both ends, cheap in the middle, for two different reasons. The chart comes from MCUNet's on-device training work and shows the memory needed to update each layer, which is the stored input activation plus the weight and its gradient. The same geometry governs inference, as the AlexNet arithmetic below shows.

LayersActivation memoryWeight memoryDominant
0 to 550 to 75 kBabout 0Activation
6 to 1720 to 55 kBabout 0Activation
18 to 30under 15 kBunder 15 kBNeither
32 to 425 to 10 kB35 to 75 kBWeight
The U in one table: which tensor dominates each band of the MCUNet profile
Activation memory (amber) decays across the early layers while weight memory (teal) climbs across the late ones; the dashed U is their sum.

The two counting rules from part 02 predict this. An activation tensor holds C × H × W values. A convolution weight tensor holds C_o × C_i × k_h × k_w values. Now follow a CNN from input to output. At every downsampling stage the spatial size halves in each direction, so H × W drops by 4x, while the channel count typically only doubles. The activation count C × H × W therefore falls by about 2x per stage. The weight count contains C_o × C_i, which grows by about 4x when channels double and does not care about H and W at all. MCUNetV2 states the consequence: "the memory bottleneck tends to appear at the early stage of the network" because resolution shrinks faster than channels grow (Lin et al., 2021).

The same U in numbers you already computed

You do not have to trust the chart. Take the AlexNet layers from part 02, put each layer's output activation count next to its weight count, and the profile reproduces itself from arithmetic.

LayerOutput C×H×WActivationsWeightsWhich dominates
conv196×55×55290,40034,848Activations, 8x
conv2256×27×27186,624307,200Weights, 1.6x
conv3384×13×1364,896884,736Weights, 14x
conv4384×13×1364,896663,552Weights, 10x
conv5256×13×1343,264442,368Weights, 10x
fc640964,09637,748,736Weights, 9,216x
fc740964,09616,777,216Weights, 4,096x
fc810001,0004,096,000Weights, 4,096x
AlexNet per layer: output activations against weights (biases ignored, as in this lecture)

The first convolution has 8x more activations than weights: a 55×55 map with 96 channels is large, and an 11×11 filter over only 3 input channels is small. By conv3 the map is 13×13 and the weights are already 14x the activations. The fully connected layers are the extreme: fc6 emits 4,096 values and owns 37.7M weights. Nothing about AlexNet was designed to show this; it is the geometry of every CNN.

What each end of the U decides

  • The early, activation-heavy stage sets the SRAM peak. That is why MobileNetV2's problem in the previous concept was in blocks 0 to 4, and why MCUNetV2 runs only that stage in patches.
  • The late, weight-heavy stage sets the flash budget and most of the parameter count. Pruning and quantization pay off most there.
  • The cheap middle is where on-device training is affordable: updating a middle layer needs neither a large stored activation nor a large weight gradient. This band is exactly what the training chart above measures, and the paper it comes from exploits exactly that band (Lin et al., 2022).

Quick check

In the MCUNet per-layer profile, which memory dominates the last layers, and why?

Recall

In one sentence each: why is activation memory high in early layers, and why is weight memory high in late layers?

Early layers keep a large H × W with few channels, so C × H × W is large. Late layers have many input and output channels, so C_o × C_i × k_h × k_w is large while the map has shrunk to a few pixels.

Counting AlexNet activations: total versus peak

Now make the peak a number you can compute. AlexNet takes a 3×224×224 image: 150,528 values. Its first convolution emits 96×55×55 = 290,400 values. While that convolution runs, both tensors must exist, because the layer is still reading the image as it writes the output: 150,528 + 290,400 = 440,928. When the first pooling layer runs, the image is no longer needed and can be freed, but the convolution output (290,400) and the pooling output (69,984) are both alive: 360,384. Memory rises and falls one layer at a time.

Two counts fall out of this picture. The total number of activations is the sum over the input and every layer output, which for AlexNet is 932,264. The peak number of activations is the largest number alive at any one moment. For a plain chain of layers executed one at a time, that is the largest input plus output pair. MCUNetV2 gives the rule and the reason: "the memory required for a layer is the sum of input and output activation (since weights can be partially fetched from Flash)", and the runtime "allocates the input and output activation buffer in SRAM, and releases the input buffer after the whole layer computation is finished" (Lin et al., 2021).

#acttotal=x0+i=1Lai\#\text{act}_{\text{total}} = |x_0| + \sum_{i=1}^{L} |a_i|
Total activations: the input plus every layer output
#actpeakmaxi(ai1+ai)\#\text{act}_{\text{peak}} \approx \max_{i}\big(|a_{i-1}| + |a_i|\big)
Peak activations: the largest input plus output pair over all layers

The approximation sign carries four assumptions, and an exam answer should name at least one of them. The network runs layer by layer, with no two layers in flight together. Weights are streamed from flash and are not counted in SRAM. There are no residual branches; if there are, MCUNetV2 says to add the memory of all branches alive at the same time, counting a shared input once. And there are no in-place tricks: MCUNet shows that a depthwise layer can overwrite its input channel by channel, needing N + 1 instead of 2N values (Lin et al., 2020). Real runtimes also need scratch buffers for im2col or accumulation, which push the true peak slightly above the formula.

Sweep through AlexNet yourself before reading the worked example. Watch which two bars are highlighted, how the running peak stops moving after the first layer, and how the bit width and the SRAM preset change whether the same 440,928 values fit.

SimulatorPeak memory sweeper: AlexNet, one layer at a time
image150,528conv1290,400pool1conv2pool2conv3conv4conv5pool3fc6fc7fc8256 kB SRAM290.4 kB0
Live now (input + output)440,928elements150,528 + 290,400 = 440.9 kB, over the 256 kB limit
Running peak440,928elements440.9 kB at conv1; whole network peaks at 440.9 kB
Total so far440,928elementsevery tensor produced so far; all 12 sum to 932,264 = 932.3 kB
Layer executingInputOutputLiveAt 8-bit
150,528290,400440,928440.9 kB
290,40069,984360,384360.4 kB
69,984186,624256,608256.6 kB
186,62443,264229,888229.9 kB
43,26464,896108,160108.2 kB
64,89664,896129,792129.8 kB
64,89643,264108,160108.2 kB
43,2649,21652,48052.5 kB
9,2164,09613,31213.3 kB
4,0964,0968,1928.2 kB
4,0961,0005,0965.1 kB

Each bar is one tensor at 8-bit, linear scale; the dashed window holds the executing layer's input and output. Bytes are elements × bits ÷ 8 and kB is decimal (÷ 1000), as on slide 17. Selected preset: STM32F412, Cortex-M4. Weights are not counted here because on a microcontroller they stay in flash and are streamed in, so SRAM only has to hold the two live activation buffers.

Worked example

Total and peak activations of AlexNet

  1. Write the C × H × W column

    150,528; 290,400; 69,984; 186,624; 43,264; 64,896; 64,896; 43,264; 9,216; 4,096; 4,096; 1,000, from the image through fc8. Each entry is channels × height × width, or just the channel count for a linear layer.
  2. Sum for the total

    932,264 values, matching the slide. This is the number that training would have to hold.
  3. Form the eleven adjacent sums

    Input + output while each layer executes

    conv1 runs (image + conv1 out)
    150,528 + 290,400 = 440,928
    pool1 runs (conv1 out + pool1 out)
    290,400 + 69,984 = 360,384
    conv2 runs (pool1 out + conv2 out)
    69,984 + 186,624 = 256,608
    pool2 runs (conv2 out + pool2 out)
    186,624 + 43,264 = 229,888
    conv3 runs (pool2 out + conv3 out)
    43,264 + 64,896 = 108,160
    conv4 runs (conv3 out + conv4 out)
    64,896 + 64,896 = 129,792
    conv5 runs (conv4 out + conv5 out)
    64,896 + 43,264 = 108,160
    pool3 runs (conv5 out + pool3 out)
    43,264 + 9,216 = 52,480
    fc6 runs (pool3 out + fc6 out)
    9,216 + 4,096 = 13,312
    fc7 runs (fc6 out + fc7 out)
    4,096 + 4,096 = 8,192
    fc8 runs (fc7 out + fc8 out)
    4,096 + 1,000 = 5,096
  4. Pick the maximum

    The first row wins: 440,928 while conv1 runs. The runner-up, conv1 output plus pool1 output, is 360,384, and by conv3 the pairs are a quarter of the peak.
  5. Convert to bytes for a bit width

    AlexNet activation memory by bit width (kB decimal, KiB binary)

    Peak, 8-bit integers
    440,928 B ≈ 441 kB (431 KiB)
    Peak, 32-bit floats
    1,763,712 B ≈ 1.76 MB (1.68 MiB)
    Total, 8-bit integers
    932,264 B ≈ 932 kB (910 KiB)
    Total, 32-bit floats
    3,729,056 B ≈ 3.73 MB (3.56 MiB)
  6. Peak = 440,928 values

    441 kB at 8-bit and 1.76 MB at 32-bit. Even fully quantized, AlexNet's first layer alone exceeds a 256 kB or 320 kB microcontroller, and the peak is 47 percent of the total.

The same arithmetic explains why MobileNetV2 failed in the first concept. Its first convolution maps a 3×224×224 image to 32×112×112: 150,528 + 401,408 = 551,936 values, which MCUNetV2 quotes as "539kB even when quantized in int8" (using 1024-byte kilobytes). A 224 input through a standard stem can never run layer by layer on a 256 kB board, whatever happens to the weights afterwards.

Finally, connect the counts back to latency. Part 01 approximated memory time as the bytes of activations and weights moved divided by the memory bandwidth. The per-layer input plus output is the activation traffic of that layer, so the same column that gives the peak also feeds the latency model. Peak tells you whether the model fits; total tells you how many activation bytes cross the memory interface per inference.

Quick check

During layer-by-layer inference, which quantity approximates the peak activation memory of a CNN?

Recall

A toy CNN: input 3×32×32, a convolution to 16×32×32, a pool to 16×16×16, then a linear layer to 10 outputs. Total and peak #activations?

Counts: 3,072; 16,384; 4,096; 10. Total 23,562. Adjacent sums 19,456, 20,480, 4,106. Peak 20,480, while the pool runs (convolution output plus pool output), not the input pair. About 20.5 kB at 8-bit.

Recall

AlexNet's peak is 440,928 values. How many kB is that at 8-bit and at 32-bit, and does either fit a 320 kB STM32F746?

441 kB at 8-bit and 1.76 MB at 32-bit (decimal kilobytes). Neither fits 320 kB; the first layer alone is over budget.

Recap

If you remember nothing else

  • SRAM holds activations (read and write); flash holds weights (read only). SRAM is the smaller budget, so activations decide whether a model fits.
  • ResNet-18 to MobileNetV2-0.75 at about 70 percent top-1, int8: parameter memory 4.6x smaller, peak activation 1.8x larger.
  • MobileNetV2's memory is imbalanced: 1372 kB at block 2, the first five blocks above 256 kB, the remaining thirteen below it.
  • Training keeps every layer input for dL/dW. ResNet-50 in fp32 at batch 8: 102 MB of weights against 707 MB of activations; MobileNetV2-1.4 cuts weights 4.3x but activations only 1.1x.
  • Activation memory is high in early layers (large H×W) and weight memory is high in late layers (large C_o·C_i); the middle is cheap.
  • Total #activations sums every tensor (AlexNet: 932,264). Peak ≈ the largest input + output pair (AlexNet: 150,528 + 290,400 = 440,928 at conv1).
  • 440,928 values are 441 kB at 8-bit and 1.76 MB at 32-bit, above a 256 kB or 320 kB microcontroller either way.
  • The peak formula assumes layer-by-layer execution and ignores residual branches, in-place tricks and runtime scratch buffers.

Sources