What activations cost, in memory and in arithmetic
complexity▲▲△Memory to 1 d.p.; FLOP counts approximate and stated as such.
STATEMENT
For a transformer-scale layer, compute the memory one activation tensor occupies and the arithmetic each activation function costs. Then compare both with the matrix multiply they sit next to, and draw the conclusion.
GIVEN
Batch , sequence , width , depth . Elementwise costs, approximately: ReLU FLOP (a compare), LeakyReLU , sigmoid (exp, add, divide), tanh , GELU with erf about , GELU with the tanh approximation about .
FIND
The element count and memory of one activation tensor in bf16 and fp32; the total across layers; the GFLOPs each activation costs per layer; and the ratio to one matrix multiply.
STRATEGY
Count elements once and reuse. Memory scales with the count; arithmetic scales with the count times a small constant; the matmul scales with the count times — and that last factor is the whole answer.
SOLUTION
Step 1 — the element count.
Step 2 — memory. At bytes per element in bf16:
and in fp32, MB. Across layers, storing one activation tensor per layer for the backward pass:
Step 3 — arithmetic. Each function is FLOPs per element:
| Activation | FLOP/element | GFLOP per layer |
|---|---|---|
| ReLU | ||
| LeakyReLU | ||
| sigmoid | ||
| tanh | ||
| GELU (erf) | ||
| GELU (tanh approx) |
Step 4 — the comparison that settles it. One matrix multiply on the same tensor costs FLOPs:
against the costliest activation’s GFLOP — a ratio of about .
The conclusion, in two halves.
Arithmetically, activations are free. Even GELU with erf is under of the matmul it follows. Choosing ReLU over GELU to save compute is optimising the wrong term by a factor of several hundred. Any argument for ReLU on grounds of speed is, at this scale, wrong.
In memory they are not free at all. GB of stored activations is a substantial fraction of an GB accelerator, and it is why activation checkpointing exists (II.8.B03): recompute the activation in the backward pass rather than store it, trading that GFLOP — which is free — against MB per layer, which is not.
That asymmetry is the general shape of the thing. Elementwise operations are bounded by memory bandwidth, matrix operations by arithmetic. An activation reads values and writes values to do FLOPs, giving an arithmetic intensity of about FLOPs per byte in bf16 — for ReLU that is , against roughly for the matmul. Chapter VIII.6 turns this into a roofline argument; here it is enough to notice that the two operations live in different worlds.
Answer
One activation tensor: elements, MB in bf16, MB in fp32. Across layers, GB in bf16.
Arithmetic ranges from GFLOP (ReLU) to GFLOP (GELU-erf) per layer, against GFLOP for the accompanying matmul — a ratio of about .
Check — numeric · i-3-b07-activation-cost.py
acts = B * T * d
print(f"{name}: {acts * bits // 8 / 1e6:.1f} MB each")
mm = 2 * acts * dPrints every figure above, including the ratio.
Executed in CI. The digits above are the digits it printed.
Check — sanity
Memory scales with elements, not with the function. ReLU and GELU store identical tensors: MB either way. Only the arithmetic column varies. If memory had varied by activation, an element would have been double-counted.
bf16 is exactly half of fp32. . Two bytes against four.
The matmul ratio is times the activation constant. Matmul is and the activation is , so the ratio is for GELU-erf. The snippet printed , differing by integer division. Consistent.
Where this breaks
The count assumes one stored tensor per layer, which understates reality: a transformer block stores several intermediates, and the FFN’s inner width is typically , so its activation tensor is four times the size computed here. The GB is therefore a floor, not an estimate. II.8.B01 does the full accounting; this problem establishes only that the activation’s arithmetic is negligible and its memory is not.
Variation
Recompute for the FFN’s inner activation at width . State the new per-layer memory and say what fraction of an GB accelerator such layers would occupy.