Results and write-up
The sweep across world size, the figures it produces, and what the write-up has to argue. These are measured numbers from the reference implementation, not predictions.
Resident bytes per parameter, measured
| N | DP | ZeRO-1 | ZeRO-2 | ZeRO-3 |
|---|---|---|---|---|
| 1 | 16.0000 | 16.0000 | 16.0000 | 16.0000 |
| 2 | 16.0000 | 10.0000 | 9.0000 | 8.0000 |
| 4 | 16.0000 | 7.0000 | 5.5000 | 4.0000 |
| 8 | 16.0000 | 5.5000 | 3.7500 | 2.0000 |
| 16 | 16.0000 | 4.7500 | 2.8750 | 1.0000 |
| 32 | 16.0000 | 4.3750 | 2.4375 | 0.5000 |
Three things to point at in the write-up. The DP column never moves — adding hardware does nothing for it. ZeRO-1 and ZeRO-2 flatten toward 4 and 2, their replicated floors, and will never go below them. ZeRO-3 keeps halving, because it replicates nothing.
At N = 1 all four are identical, which is worth including rather than skipping: with one rank there is nothing to shard, and a correct implementation should say so.
Peak, which is the number that decides fit
| N | DP | ZeRO-1 | ZeRO-2 | ZeRO-3 resident | ZeRO-3 peak |
|---|---|---|---|---|---|
| 4 | 16.000 | 7.000 | 5.500 | 4.000 | 8.000 |
| 8 | 16.000 | 5.500 | 3.750 | 2.000 | 6.000 |
| 16 | 16.000 | 4.750 | 2.875 | 1.000 | 5.000 |
| 32 | 16.000 | 4.375 | 2.438 | 0.500 | 4.500 |
The ZeRO-3 peak falls much more slowly than its resident figure, and at N = 32 it is worse than ZeRO-2's resident 2.44. That is an artefact of the toy model gathering all 33,088 parameters at once, and it is worth saying so plainly: a real ZeRO-3 gathers one layer, so the added term is the largest layer rather than the whole model. The prototype overstates this cost, and pretending otherwise would be the easiest way to draw a wrong conclusion from correct code.
An improvement worth making if there is time
Gather per layer rather than per model. The MLP already stores per-layer
slices, so ZeRO-3 can loop over them, gather one, use it, release it. Peak then becomes
16/N + 4 × (largest layer / n), which is the real behaviour, and the peak
column stops being misleading.
The figures to produce
| Figure | Shows | Why it is there |
|---|---|---|
| loss curves, all four overlaid | one visible line | the implementation is ZeRO, not an approximation of it |
| max parameter difference vs step | a flat line at zero | the claim above, as a number |
| bytes per parameter vs N, log-log | two curves flattening, one descending | replicated state sets a floor |
| traffic per step vs N | three curves identical, one 1.5× higher | stages 1 and 2 are free; stage 3 is not |
import numpy as np
from zerosim.model import MLP
from zerosim.schemes import SCHEMES
m, STEPS = MLP(), 3
rows = {}
for N in (1, 2, 4, 8, 16, 32):
rng = np.random.default_rng(3)
batches = [[(rng.standard_normal((4, 64)).astype(np.float32),
rng.standard_normal((4, 64)).astype(np.float32))
for _ in range(N)] for _ in range(STEPS)]
out = []
for S in SCHEMES:
s = S(m, N)
for t in range(STEPS):
s.step(batches[t])
out.append((s.bytes_per_param(),
s.peak_bytes_per_param(),
s.bytes_comm_per_rank() / STEPS / (m.n_params * 4)))
rows[N] = out
The write-up
The numbers and the structure come from the experiment. The reasoning is the part that has to be yours, and the most useful thing to write about is the places where the measurements do not match the tidy story.
| Question | What to point at |
|---|---|
| What does each stage shard? | the three flags, and which of the sixteen bytes each one covers |
| Why are stages 1 and 2 free? | the traffic table: three identical columns |
| Why does stage 3 cost more? | two gathers instead of one, because the gathered copy is released between passes |
| Why can ZeRO-1 never fit some models? | the sweep flattening at 4 bytes, and what that is as GiB for a real model |
| Which would you choose? | your own peak-memory and traffic numbers, and which constraint bound first |
| Where does ZeRO stop? | the peak column — when one gathered layer exceeds a card, state sharding is done |
Three things worth admitting in the README
Traffic never reaches 2P. It is 2(N−1)/N, so 1.9375P at N = 32.
The quoted 2P is an upper bound that gets truer as N grows.
The peak figure is pessimistic. Gathering the whole model rather than one layer inflates ZeRO-3's peak, for the reason given above.
Bitwise equality is a property of this implementation, not of ZeRO. A real framework reduces in a different order and will differ in the last few bits.
Each of these is a place where the honest answer is more informative than the clean one, and noticing them is the difference between running the code and understanding it.