The advice for a second GPU is "use DDP and you get roughly 2x". That advice assumes an interconnect. On two Tesla T4s over PCIe with no NVLink the link runs at 4.05 GB/s measured, about a 175th of the bandwidth each GPU has to its own memory, and the arithmetic stops working.
The crossover runs the opposite direction from the one I went looking for. The second GPU pays on small models and stops paying on large ones. Drag the model size below and watch speedup cross one.
Anything below the horizontal line means one GPU was faster. The bars behind the curve are the fraction of each step spent moving gradients rather than computing them.
Gradient bytes grow with parameter count and so does arithmetic, so the ratio between them is roughly fixed. On a fast link that ratio is small enough not to matter. On this one, gradient sync costs about 5x the compute it buys at every width measured.
The models that appear to scale are the ones too small to be doing real work, where the second GPU is parallelizing launch overhead rather than arithmetic.
Four standard answers to "my DDP is slow", measured at every width rather than argued about. Each is shown at its own best setting, since the question is whether it can help at all.
Only gradient accumulation rescues it. At x8 it turns every losing width into a win, because syncing once per eight micro-batches divides communication by eight: the share of the step spent on comms falls from 65% to 15%. It is also the bluntest instrument here, multiplying the effective batch by the same eight, which changes the optimization problem rather than only its execution.
fp16 compression is necessary and insufficient. Halving the bytes on the wire buys about 1.3x and still lands below one GPU at the sizes where it matters.
bucket_cap_mb, the knob most often suggested, does nothing:
across a hundredfold sweep, throughput at 537M parameters spans
0.3%. And FSDP is worse than plain DDP at every size,
because it adds an all-gather of parameters on top of the gradient
reduce-scatter, and all-gather runs at 1.73 GB/s here against all-reduce's
4.05.