hand rolling flops for an llm¶
deriving the compute cost of an llm forward pass from first principles.
what is considered a flop?
a flop (floating-point operation) is one multiply or one add. a fused multiply-add (fma) does both in one instruction so counts as 2 flops.
example -
nice, lets start from matrix multiplication and then scale to each layer present in an llm.
1. matrix multiplication¶
suppose we multiply
cost of one output element¶
each element c[i][j] is a dot product of row i of a and column j of b:
- k multiplications (one per pair)
- k − 1 additions
so per element:
now for all output elements¶
the output matrix has m x n elements, so:
you can approximate it to -
great! lets do linear layers now
2. linear layer¶
a linear layer y = xw is just a matrix multiplication.
shapes¶
- input:
x = (b * s * d)where b = batch, s = sequence length, d = hidden dim - weight:
w = (d * d_out) - output:
y = (b * s * d_out)
this is essential still a matrix multiplication¶
we flatten the first two dimensions: (b*s) * d and multiply by d * d_out.
flops¶
when d_out = d (most transformer projections):
example¶
b = 1, s = 2048, d = 4096:
3. attention¶
a standard attention head does:
q = x * w_q k = x * w_k v = x * w_v (3 projections)
s = q * k^T (scores)
p = softmax(s) (per-row)
o = p * v (weighted sum)
y = o * w_o (output projection)
let's break it down. assume:
- b = batch
- s = sequence length
- d = hidden dimension
- h = number of heads
- d_h = d / h = head dimension
for qkv projections (3 linear layers)¶
each is a linear layer: (input d --> output d).
flops_q_proj = 2 * b * s * d * d = 2 * b * s * d^2
flops_k_proj = 2 * b * s * d^2
flops_v_proj = 2 * b * s * d^2
sum:
for attention scores (q * k^T)¶
we can view this per head, or in total.
per head: q has shape (b*s, d_h), k^T has shape (d_h, s). the result is (b*s, s).
all h heads:
for softmax¶
softmax operates on each row of the score matrix. a row of length s requires:
- max reduction: s comparisons (find max to subtract for numerical stability)
- subtract + exp: s fmas (exponential counts as ~1 flop by convention)
- sum reduction: s − 1 additions
- divide: s divisions
per row ≈ 4s flops (rough estimate).
there are b*s rows per head, times h heads:
in terms of d:
for typical d_h = 64–128, this is small relative to the matmuls. many analyses drop it.
for attention output (p * v)¶
this is another matmul. per head:
- p:
(b*s, s) - v:
(s, d_h)(v was originally(b*s, d), reshaped to(b*s, h, d_h)) - result:
(b*s, d_h)
all h heads:
for output projection (o * w_o)¶
total attention flops¶
flops_attention = 6 * b * s * d^2 (qkv projections)
+ 2 * b * s^2 * d (scores)
+ 2 * b * s^2 * d (weighted sum)
+ 2 * b * s * d^2 (output projection)
= 8 * b * s * d^2 + 4 * b * s^2 * d
4. feed-forward (ffn) layer¶
a standard ffn has two projections with an activation in between:
for a gated ffn (glu variants like swiglu used in llama, mistral, etc.):
z1 = x * w_gate (d to 4d)
z2 = x * w_up (d to 4d)
z = activation(z1) * z2 elementwise
y = z * w_down (4d to d)
swiglu flops¶
three matmuls, each d to 4d or 4d to d:
flops_ffn_gate = 2 * b * s * d * 4d = 8 * b * s * d^2
flops_ffn_up = 2 * b * s * d * 4d = 8 * b * s * d^2
flops_ffn_down = 2 * b * s * 4d * d = 8 * b * s * d^2
total:
6. one transformer layer¶
a standard decoder layer = attention + ffn
flops_layer = flops_attention + flops_ffn
= (8 * b * s * d^2 + 4 * b * s^2 * d) + (24 * b * s * d^2)
= 32 * b * s * d^2 + 4 * b * s^2 * d
7. full model forward pass¶
for l layers:
flops_forward = l * (32 * b * s * d^2 + 4 * b * s^2 * d)
= 32 * l * b * s * d^2 + 4 * l * b * s^2 * d
embedding layer¶
the embedding lookup has 0 flops.
the final lm head (unembedding) is a linear d to vocab_size. in many models the lm head shares weights with the embedding. this adds:
for large vocabularies (llama 128k), this would be quite significant comparable to a few transformer layers.
per token flops¶
often we care about flops per token (to compare with memory bandwidth or compute roofline).
total tokens processed in the forward pass = b * s.
two terms:
32 * l * d^2: per-token compute, independent of sequence length. this is ~2 * the total non-embedding parameter count (since each param is used once in a matmul withm = 1token).4 * l * s * d: attention overhead per token, grows linearly with s.
numeric example¶
llama 3 8b (l = 32, d = 4096):
the total non-embedding parameters are ~8 * 10⁹, and we got ~17 gflops/token. that's ~2.15 * parameter_count because the flop/param ratio of a d * d matmul is:
2 * d * d * d (for the matmul: m=k=n=d, flops = 2*d*d*d = 2d³)
= 2 * d per parameter (since there are d^2 params)
each parameter in a square linear layer is involved in 2*d flops per forward pass.
9. backward pass¶
rough rule of thumb: backward is ~2* forward for compute.
total training flops per token¶
flops_training_per_token = flops_forward + flops_backward
~ 3 * flops_forward_per_token
~ 3 * (32 * l * d^2 + 4 * l * s * d)
~ 96 * l * d^2 + 12 * l * s * d
summary formulas¶
| component | flops |
|---|---|
| linear layer (d to d_out) | 2 * b * s * d * d_out |
| qkv projections (total) | 6 * b * s * d^2 |
| attention scores | 2 * b * s^2 * d |
| attention weighted sum | 2 * b * s^2 * d |
| output projection | 2 * b * s * d^2 |
| total attention | 8 * b * s * d^2 + 4 * b * s^2 * d |
| swiglu ffn | 24 * b * s * d^2 |
| one transformer layer | 32 * b * s * d^2 + 4 * b * s^2 * d |
| full model (l layers), forward | 32 * l * b * s * d^2 + 4 * l * b * s^2 * d |
| full model, backward | ~2 * forward |
| full model, training (forward + backward) | ~3 * forward |