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,864≈6.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 c⋅BTdc \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 2⋅BTd⋅d2 \cdot BTd \cdot d and the activation is c⋅BTdc \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