Step 10 Β· Making it fast
Same model, same GPUs, three times the throughput. Most training runs leave that on the table.
In 60 seconds
Step 10 Β· Making it fast
Same model, same GPUs, three times the throughput. Most training runs leave that on the table.
The ranked list
| Technique | Typical gain | Effort |
|---|---|---|
| bfloat16 mixed precision | 2β3x | One line |
| torch.compile | 1.3β2x | One line |
| FlashAttention | 1.5β3x at long context | Free via scaled_dot_product_attention |
| Fused optimiser | 5β10% | One argument |
| Gradient checkpointing | Enables larger batches | One line, costs ~30% compute |
| Sequence packing | 10β40% on varied-length data | Moderate |
| Better data loading | Sometimes 2x | Pre-tokenise and memory-map |
| FP8 on recent hardware | 1.3β1.8x | Significant care required |
Why FlashAttention matters so much
Sequence packing
def pack(sequences, block_size, eos_id):
buf, out = [], []
for seq in sequences:
buf.extend(seq + [eos_id])
while len(buf) >= block_size:
out.append(buf[:block_size])
buf = buf[block_size:]
return out
# NOTE: pass document boundaries to the attention mask, or examples
# will attend across each other and quietly learn nonsense.Measure before you optimise
import time
torch.cuda.synchronize(); t0 = time.time()
for _ in range(20):
x, y = get_batch("train")
with torch.autocast("cuda", dtype=torch.bfloat16):
_, loss = model(x, y)
loss.backward(); opt.step(); opt.zero_grad(set_to_none=True)
torch.cuda.synchronize()
tok_per_s = 20 * x.numel() / (time.time() - t0)
flops = 6 * n_params * tok_per_s
print(f"{tok_per_s:,.0f} tok/s MFU {flops / peak_flops:.1%}")Watch and read more
Lab
A 2x speedup on the same hardware, itemised.
The problem
You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1Packing gave you 35% on varied-length data. Name the bug it introduces and how you detect it.Reveal
Questions people ask
bf16 or fp16?
bfloat16 on anything from Ampere onward. It has the same exponent range as fp32, so it does not overflow and needs no loss scaling. fp16 has more mantissa precision but a narrow range, and it makes large-model training fragile.
Is FP8 worth it?
On hardware that supports it, yes, for large runs β but it needs per-tensor scaling and careful handling of sensitive layers. Do not start here. Get bf16 and compile working first.
Why is my GPU utilisation high but throughput low?
Utilisation percentage only says the GPU is busy, not that it is doing useful arithmetic. It can be busy moving memory. MFU is the honest metric.
Does torch.compile ever hurt?
It adds compile time on first run and recompiles on every new input shape. With highly dynamic shapes you can spend more time compiling than computing. Fixed shapes plus compile is the fast path.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.
Sign in with your phone number to take the test and save your progress