Md. Asif Uddin
Problem I.3.B07

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 B=8B = 8, sequence T=2048T = 2048, width d=4096d = 4096, depth L=32L = 32. Elementwise costs, approximately: ReLU 11 FLOP (a compare), LeakyReLU 22, sigmoid 44 (exp, add, divide), tanh 66, GELU with erf about 1212, GELU with the tanh approximation about 88.

FIND

The element count and memory of one activation tensor in bf16 and fp32; the total across LL layers; the GFLOPs each activation costs per layer; and the ratio to one d×dd\times d 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 dd — and that last factor is the whole answer.

SOLUTION

Step 1 — the element count.

B×T×d=8×2048×4096=67,108,8646.71×107B \times T \times d = 8 \times 2048 \times 4096 = 67{,}108{,}864 \approx 6.71\times10^{7}

Step 2 — memory. At 22 bytes per element in bf16:

6.71×107×2=1.342×108 bytes=134.2 MB6.71\times10^{7} \times 2 = 1.342\times10^{8}\ \text{bytes} = 134.2\ \text{MB}

and in fp32, 268.4268.4 MB. Across 3232 layers, storing one activation tensor per layer for the backward pass:

32×134.2 MB=4.29 GB (bf16),8.59 GB (fp32)32 \times 134.2\ \text{MB} = 4.29\ \text{GB (bf16)}, \qquad 8.59\ \text{GB (fp32)}

Step 3 — arithmetic. Each function is cc FLOPs per element:

ActivationFLOP/elementGFLOP per layer
ReLU110.070.07
LeakyReLU220.130.13
sigmoid440.270.27
tanh660.400.40
GELU (erf)12120.810.81
GELU (tanh approx)880.540.54

Step 4 — the comparison that settles it. One d×dd\times d matrix multiply on the same tensor costs 2×(BTd)×d2 \times (BTd) \times d FLOPs:

2×6.71×107×4096=5.50×1011=549.8 GFLOP2 \times 6.71\times10^{7} \times 4096 = 5.50\times10^{11} = 549.8\ \text{GFLOP}

against the costliest activation’s 0.810.81 GFLOP — a ratio of about 680\mathbf{680}.

The conclusion, in two halves.

Arithmetically, activations are free. Even GELU with erf is under 0.2%0.2\% 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. 4.294.29 GB of stored activations is a substantial fraction of an 8080 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 0.810.81 GFLOP — which is free — against 134134 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 BTdBTd values and writes BTdBTd values to do cBTdc \cdot BTd FLOPs, giving an arithmetic intensity of about c/4c/4 FLOPs per byte in bf16 — for ReLU that is 0.250.25, against roughly 200200 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: 6.71×1076.71\times10^{7} elements, 134.2134.2 MB in bf16, 268.4268.4 MB in fp32. Across 3232 layers, 4.29\mathbf{4.29} GB in bf16.

Arithmetic ranges from 0.070.07 GFLOP (ReLU) to 0.810.81 GFLOP (GELU-erf) per layer, against 549.8\mathbf{549.8} GFLOP for the accompanying matmul — a ratio of about 680680.

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 * d

Prints every figure above, including the 682×682\times 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: 134.2134.2 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. 134.2×2=268.4134.2 \times 2 = 268.4. Two bytes against four.

The matmul ratio is d/2d/2 times the activation constant. Matmul is 2BTdd2 \cdot BTd \cdot d and the activation is cBTdc \cdot BTd, so the ratio is 2d/c=8192/12=6832d/c = 8192/12 = 683 for GELU-erf. The snippet printed 682682, 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 4d4d, so its activation tensor is four times the size computed here. The 4.294.29 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 4d=163844d = 16384. State the new per-layer memory and say what fraction of an 8080 GB accelerator 3232 such layers would occupy.

Draws on