6.1.9 · D3Scaling & Efficient Architectures

Worked examples — FSDP and sharded training

3,362 words15 min readBack to topic

This page is the practice arena for FSDP and sharded training. We do not re-derive theory here — we throw every kind of situation at FSDP and grind through the numbers. If a symbol shows up you haven't met, it was built in the parent note; we recall it in one line the first time it appears.

Two symbols we reuse constantly:

  • = number of parameters in the model (a plain count, e.g. ).
  • = number of data-parallel devices sharing the work (GPUs, e.g. ).

The scenario matrix

Every FSDP exam/interview question lands in one of these cells. The worked examples below are each labelled with the cell they cover, so together they touch every box.

# Cell class The question being asked Degenerate / edge behaviour to watch
A Memory, fp32 Persistent bytes per GPU, no mixed precision → FSDP = plain DP (no saving)
B Memory, mixed precision Bytes per param when fp16 + fp32 master weights coexist People forget the fp32 master copy
C Activation blind spot Total memory including activations (which FSDP does not shard) Activations can dwarf the sharded part
D Communication volume Bytes each device sends/receives in all-gather & reduce-scatter → cost per device full tensor size
E Comm-vs-compute trade-off Is FSDP faster or slower than plain DP here? Small model on fast link → FSDP loses
F Slow-interconnect limit Wall-clock cost of a layer's all-gather on a real network Bandwidth, not GPU, is the bottleneck
G ZeRO stage picker Which ZeRO stage (1/2/3) gives just-enough memory? Over-sharding wastes bandwidth
H 3-D parallelism bookkeeping Split over a fixed GPU count Product must equal total GPUs exactly

Prerequisite links used along the way: Mixed Precision Training, Activation Checkpointing, Communication Collectives, ZeRO Optimizer, Data Paralelism, Pipeline Parallelism, Model Parallelism, Gradient Accumulation.

Reading order note: the examples are numbered by matrix cell, so Example 5 = Cell E and Example 6 = Cell F. If you jump straight to a cell, match on the letter, not the position.


Example 1 — Cell A: pure fp32 memory, and the edge

Forecast: Guess before reading — how many bytes per parameter does fp32 + Adam need? Write your number down.

We name one quantity so the formulas read cleanly:

  • = persistent memory per GPU, in bytes — the buffers a device must hold for the whole training run (parameters, gradients, optimizer states), not counting temporary all-gather scratch.
  1. Count the bytes each parameter drags along. Why this step? Memory is dominated by four buffers, and each parameter needs a fixed number of bytes in each. In fp32 every number is bytes.

    • parameter : B
    • gradient : B
    • Adam momentum : B
    • Adam variance : B

    Total bytes per parameter.

    Recall Why not

    like the parent note? The parent note stored momentum/variance in fp64 ( B each) → B. Here we keep Adam states in fp32 ( B each) → B. Byte counts depend on the precision you assume — always state it. We use B (all-fp32) throughout this example. Example 5 will use a different B count — watch for its own derivation there.

  2. No-FSDP memory (full replica on every device). Why this step? Plain Data Paralelism copies all four buffers onto every GPU, so here is the whole model's byte count. Write for this "full replica" value. Does not fit on 80 GB. ❌

  3. FSDP with — the degenerate case. Why this step? This is the edge the matrix flagged. FSDP divides that persistent memory by ; write for the sharded value. Sharding "across one device" gives that device everything. Identical to no-FSDP. FSDP at buys nothing — sharding needs at least two workers to divide anything.

  4. FSDP with . Why this step? Now the persistent buffers split ways. Fits comfortably (plus a small temporary all-gather buffer). ✅

Verify: Sanity check the limit: with must equal the full-replica number — it does, confirming the formula degrades gracefully. Units: bytes/param × params = bytes → GB. ✔


Example 2 — Cell B: mixed precision, the fp32 master trap

Forecast: Is mixed precision more or less bytes per param than pure fp32? (Trap: it's often more, because you keep two copies of the weights.)

  1. List every buffer and its precision. Why this step? Mixed precision doesn't delete buffers, it changes their width — and adds a master copy.

    • fp16 parameter: B
    • fp16 gradient: B
    • fp32 master parameter (for stable updates): B
    • fp32 Adam momentum: B
    • fp32 Adam variance: B
  2. Sum. Same total as pure fp32 here — the fp16 savings on params+grads are exactly eaten by the extra fp32 master. This is the trap: mixed precision saves activation/compute memory, not optimizer memory.

  3. Persistent memory per GPU at . Why this step? All five buffers are sharded by FSDP (ZeRO-3), so persistent memory is the per-param bytes times , divided by .

Verify: GB total; GB. Cross-check against Example 1's result: , and — halving-by-8 twice matches. ✔


Example 3 — Cell C: the activation blind spot

Forecast: Which is bigger — the sharded weights you cleverly split, or the activations you forgot?

  1. Activation tensor size for one layer. Why this step? Backprop must remember each layer's output, shaped .

  2. Convert to bytes (fp16).

  3. Compare to sharded persistent memory. Why this step? This is the whole point — activations are not divided by .

    • Sharded persistent (all layers, per GPU): GB.
    • Activations for a -layer model without checkpointing: GB — nearly ten times the sharded weights.
  4. Fix. Why this step? Activation Checkpointing stores activations only at layer boundaries and recomputes the rest. Let = the number of layers in the model (here ). Instead of keeping all layers' activations, checkpointing keeps roughly of them, recomputing the rest during the backward pass — trading ~33% extra compute for a ~10× activation-memory cut.

Verify: elements; B B GB. Times layers GB GB persistent. The blind spot dominates. ✔


Example 4 — Cell D: communication volume, and the limit

Forecast: As you add more GPUs, does each GPU send more or less per collective?

The figure below plots the per-device all-gather cost as grows; we walk it in step 2. Look at the blue curve rising toward the red dashed ceiling, and the two orange dots marking the numbers we compute.

Figure — FSDP and sharded training

Figure: per-device all-gather traffic versus number of devices . Blue = the cost curve; red dashed = the limit MB; orange dots = the worked values at ( MB) and ( MB). The curve never crosses the ceiling — you can never receive more than the whole tensor.

  1. All-gather cost per device. Why this step? Each device already holds its own shard; it must receive the other shards to rebuild the full tensor. From Communication Collectives:

  2. Plug in (this is the curve in the figure).

    • : MB received per device — the left orange dot.
    • : MB — the right orange dot, already almost touching the ceiling.
    • : , so cost MB (the red dashed line). Each device essentially receives the whole tensor — you never pay more than the full size, no matter how many GPUs.
  3. Reduce-scatter cost. Why this step? Backward pass sums each shard's gradient across devices and hands device only its slice. Same volume as all-gather by symmetry:

  4. Per-layer total — one all-gather + one reduce-scatter. Why this step? In standard FSDP the layer's parameters are already resident in fp16 from the forward all-gather when the backward reaches it (or are re-gathered once), so a well-implemented step pays one all-gather (to materialise the full weights) and one reduce-scatter (to shard the summed gradients) per layer:

Verify: At : MB. Per-layer total MB. Limit check: , matching MB. ✔


Forecast: Memory is fine either way (we'll confirm). Does FSDP still help — or just slow you down?

  1. First check: does plain DP already fit? Why this step? FSDP only pays off when memory overflows, so we must first size the plain-DP footprint. This scenario matches the parent note's mixed-precision GPT-3 accounting: per parameter you keep fp16 param ( B) + fp16 grad ( B) + fp32 master ( B) + fp32 momentum ( B) + fp32 variance ( B) B/param (the parent note uses fp64-width B Adam states here).

    Recall Why

    here but in Examples 1–2? The byte count is your choice of precision. Examples 1–2 kept Adam states at fp32 width ( B each) → B. This example follows the parent note's convention with B-wide momentum/variance → B. Always state the width before you multiply.

    Plain-DP memory per GPU (full replica, not sharded): The model already fits without sharding — so FSDP's memory benefit is zero here.

  2. Compute the comm/compute ratio. Why this step? The parent note's rule of thumb: if , FSDP likely hurts.

  3. Apply the rule. → FSDP overhead is not amortized. Since memory already fits without sharding, you are paying communication for a benefit you don't need.

  4. Estimate the slowdown — worst case vs. overlapped case. Why this step? The raw number and the parent note's "" differ because of overlap, so we must quantify both.

    • Worst case (no overlap): communication runs after compute, so the layer takes ms. Slowdown .
    • Overlapped case: FSDP prefetches the next layer's all-gather while the current layer's matmul is still running (compute and communication happen at the same time). Only the part of that cannot hide behind compute is charged. If a fraction of the ms overlaps, the exposed cost is ms. To land at the parent note's ms exposed (a slowdown), we need — i.e. about half the communication hidden. That is a realistic overlap fraction on NVLink, which is exactly why the measured slowdown () is smaller than the naive .
  5. Decision. Use plain Data Paralelism here; reserve FSDP for when memory actually overflows.

Verify: GB ; ; worst-case slowdown ; overlap fraction to hit : . ✔


Example 6 — Cell F: slow-interconnect wall-clock, bandwidth-bound

Forecast: Gbps sounds fast. Guess the milliseconds to move MB.

  1. Unit-align bandwidth. Why this step? Bits vs bytes is the classic slip. Gbps bits per second bytes per second ().

  2. Transfer time = data ÷ bandwidth. Why this step? Time on a bandwidth-bound link is just "how many bytes" divided by "bytes per second" — the same idea as "distance ÷ speed = time". Both numbers must be in bytes, which is why step 1 converted Gbps to bytes/s first.

  3. Interpret and conclude. Why this step? We now compare that ms to compute. A transformer layer's matrix multiplies on an A100 finish in a handful of milliseconds. So ms of communication per layer compute → the GPU sits idle waiting for bytes. Conclusion: on this slow link FSDP is bandwidth-bound, and its memory win is drowned by communication — exactly the parent note's warning about PCIe / cross-datacenter links.

Verify: s ms. ✔


Example 7 — Cell G: pick the smallest ZeRO stage that fits

Forecast: You don't always need full FSDP. Guess the cheapest stage that clears GB.

Break the B/param into what each stage shards. Buffers: fp16 param , fp16 grad , fp32 master , momentum , variance . Optimizer states = master + momentum + variance = B; gradients = B; params (fp16) = B.

  1. ZeRO-1 (shard the B optimizer states only). Why this step? Stage 1 leaves params and grads fully replicated (the B stay whole) and divides only the B of optimizer states by . GB. ❌ (>10)

  2. ZeRO-2 (shard optimizer states + grads = B). Why this step? Stage 2 additionally shards the B gradient, leaving only the B fp16 param replicated. GB. ❌ (still >10, barely)

  3. ZeRO-3 / FSDP (shard all B). Why this step? Stage 3 shards everything — nothing stays replicated, so the whole B/param divides by . GB. ✅ Fits under GB.

  4. Decision. Only ZeRO-3 (full FSDP) clears the budget here. Choosing the smallest stage that fits minimises communication (parent note: higher stages = more collectives).

Verify: GB; GB; GB. Monotonic decrease, only stage 3 . ✔


Example 8 — Cell H: 3-D parallelism must multiply to your GPU count

Forecast: Given the constraint , solve for the missing factor.

  1. Write the constraint. Why this step? Every GPU belongs to exactly one coordinate, so the three counts must multiply to the total.

  2. Solve for . Why this step? Two of the three factors are fixed by the hardware plan, so the data-parallel width is whatever makes the product exact.

  3. Consistency check for the "tensor inside a node" rule. Why this step? Tensor parallelism needs the fastest link, so its group must fit within one -GPU node — and indeed equals the node size exactly. ✅ FSDP's all-gathers then run across the data-parallel replicas, its heaviest traffic kept off the critical tensor-parallel path.

  4. Sanity-multiply back. Why this step? Never trust a division without re-multiplying: , matching the GPU budget exactly, so no device is left unassigned or double-counted.

Verify: . And node size . ✔


Recall Quick self-test

All-gather cost per device for tensor size over devices ::: Per-layer FSDP communication volume (all-gather + reduce-scatter) ::: Does FSDP shard activations? ::: No — combine with activation checkpointing. At , FSDP memory vs plain DP ::: Identical; sharding needs . Mixed-precision bytes/param with fp32 master + fp32 Adam ::: B. Rule of thumb: FSDP hurts when exceeds ::: . 3-D parallelism constraint ::: total GPUs.