A model that trains fine on one GPU one week can OOM on eight the next, and the reflex is to blame the hardware. The real change is usually the optimizer state. When Adam’s moment estimates and the fp32 master copy pile on top of parameters and gradients, data parallelism stops helping — every rank still holds a full copy of everything. Fully Sharded Data Parallel (FSDP) is the technique that breaks that copy up, and Hugging Face Accelerate is the wrapper most teams reach for to turn it on without rewriting their training loop. The confusion worth clearing up early: FSDP is not “data parallelism, but bigger.” It is a different memory model. Standard Distributed Data Parallel (DDP) replicates the whole model on every rank and synchronises gradients. FSDP shards the model across ranks and reconstructs full layers only for the brief window each one is actually computing. That distinction is the whole point, and it decides whether your training run fits at all. What does FSDP actually shard, and how is that different from DDP? Three things dominate the memory footprint of a training job: the parameters, the gradients, and the optimizer state. Under DDP, all three live in full on every GPU. Add a fourth GPU and you get more throughput, but not one byte of relief on per-device memory — the model copy is identical on rank 0 and rank 3. FSDP partitions all three across the ranks. Instead of holding the full parameter tensor, each rank holds a slice. When a layer needs to run its forward pass, the ranks perform an all-gather to reconstruct the full parameters for that layer, do the compute, then discard the reconstructed copy. The same happens in reverse for the backward pass, with a reduce-scatter distributing the sharded gradients. The full layer exists in memory transiently — during its own forward or backward step — and nowhere else. This is where the ROI comes from, and it is worth being precise about the numbers. For a model trained with the Adam optimizer in mixed precision, the optimizer state alone runs to roughly 2x the parameter count in fp32 — one buffer for the first moment, one for the second, plus the fp32 master weights (this follows directly from Adam’s published update rule, not a benchmarked figure). On a billion-parameter model, that term dwarfs the parameters themselves. Shard it across N ranks and that dominant term drops by a factor of N. That is the mechanism that lets a fixed GPU budget train a model several times larger than DDP allows. Here is the split laid out plainly: State DDP (per GPU) FSDP (per GPU, N ranks) Notes Parameters Full copy ~1/N (gathered transiently) Full layer materialised only during its step Gradients Full copy ~1/N Reduce-scatter replaces all-reduce Optimizer state Full copy ~1/N The dominant term for Adam — where the win lives Activations Full Full (unless checkpointed) FSDP does not shard activations The last row catches people out. FSDP does nothing for activation memory — that is a separate axis you address with activation (gradient) checkpointing. If your OOM is activation-driven rather than state-driven, sharding the wrong thing will not save you. How do you configure FSDP through Hugging Face Accelerate? The appeal of Hugging Face Accelerate as a multi-GPU training and inference layer is that the same training loop runs single-GPU, DDP, or FSDP depending on a config file — you do not touch the loop body. But the config is not a formality. Two settings do the real work, and getting them wrong is the difference between a job that fits and one that crawls or crashes. The auto-wrap policy decides the granularity at which FSDP shards. FSDP wraps the model in nested units; each unit is the atom that gets gathered and freed together. Wrap too coarsely — the whole model as one unit — and you gain nothing, because reconstructing “the whole model” for a step means holding the whole model. Wrap too finely and communication overhead from constant all-gathers eats your throughput. The standard approach is a transformer-based policy that wraps each transformer block as its own unit, so exactly one block is materialised at a time. This has to match the model architecture: the policy needs to know which layer class marks a block boundary. The sharding strategy controls how aggressively state is partitioned. FULL_SHARD partitions parameters, gradients, and optimizer state across all ranks — maximum memory saving, maximum communication. SHARD_GRAD_OP keeps parameters replicated but shards gradients and optimizer state, trading some memory back for less communication. NO_SHARD degenerates to DDP behaviour. The right choice depends on where your memory pressure actually sits and how much interconnect bandwidth you have — the same tension that drives the accelerate config decisions for multi-GPU runs. Once the config is written, accelerate launch translates it into the distributed launch — spawning the ranks and wiring up the process group. Nothing in your Python changes. When should you enable CPU offload or activation checkpointing? These are the two levers people reach for when FULL_SHARD alone still OOMs, and both trade throughput for memory in ways worth naming explicitly. CPU offload pushes sharded parameters and optimizer state to host RAM when they are not being used, pulling them back over PCIe for each step. It can be the thing that makes an otherwise-impossible model fit on the GPUs you have. The cost is bandwidth: PCIe is far slower than HBM, and if offload sits on the critical path of every step, throughput can drop severalfold. In our experience it is a last resort for fitting, not a default for speed — enable it when the alternative is not training at all, and measure the tokens-per-second hit before you commit a long run. Activation checkpointing attacks the axis FSDP ignores. Instead of storing every intermediate activation for the backward pass, it stores a subset and recomputes the rest. The classic trade is memory for compute: you pay roughly one extra forward pass worth of work to reclaim activation memory. On transformer training this is often the cheaper win than offload, because recompute stays on the GPU rather than crossing PCIe. The decision rubric we apply: State-dominated OOM (Adam on a large model): FULL_SHARD first. This is the case FSDP is built for. Still OOM after full shard, activations are the pressure: add activation checkpointing before touching offload. Still OOM and you cannot add GPUs: CPU offload, accepting the throughput cost — and only after confirming the batch actually needs to be that size. Throughput-limited, memory has headroom: consider SHARD_GRAD_OP to cut communication. How much memory does it save, and how do you estimate it before launching? You can size this on paper before burning a launch. Take a model with P parameters trained in mixed precision with Adam. The rough per-GPU state footprint under FULL_SHARD across N ranks is: parameters (2 bytes each in bf16) plus gradients (2 bytes) plus optimizer state (Adam’s two moments plus fp32 master ≈ 12 bytes) — call it ~16 bytes per parameter total — divided by N, then add the transient cost of the single largest layer being gathered, plus activation memory (which N does not reduce unless checkpointed). For example, if a model measured 7 billion parameters, the full state term is on the order of 112 GB. On DDP that entire 112 GB must fit on one GPU — it will not fit on an 80 GB card. Shard across 8 ranks and the state term drops to roughly 14 GB per GPU, leaving room for activations and a larger batch. This is an illustrative estimate from the byte accounting, not a benchmarked measurement, and real footprint depends on the wrap granularity and how much transient gather overlap the implementation allows — but it is close enough to decide whether a cluster size is viable before you queue the job. The two outcomes to measure once it runs are peak per-GPU memory (the hard ceiling that decides whether training runs at all) and tokens-per-second at your batch size after communication overhead. The first tells you if you fit; the second tells you what the sharding and offload choices cost you. Both feed the interconnect and memory budgeting that a GPU audit validates before committing to a training cluster. What are the common failure modes with Accelerate FSDP? Most FSDP grief is not conceptual — it is configuration. The recurring ones we see: Mismatched wrapping policy. The transformer auto-wrap policy references a layer class that does not match the model’s actual block, so wrapping falls back to one giant unit and the memory win evaporates. Symptom: OOM that looks like FSDP “isn’t doing anything.” Fix: confirm the wrap policy names the correct decoder/encoder block class. Mixed-precision misconfiguration. FSDP has its own mixed-precision setting distinct from the trainer’s autocast. Set them inconsistently and you either lose the memory benefit (params kept in fp32) or hit numerical instability (reductions in the wrong precision). Keep the FSDP mixed-precision policy and the training precision aligned. Checkpoint save/load errors. A sharded model’s state dict is not a plain model’s state dict. Saving with the wrong state-dict type produces a checkpoint that cannot be reloaded, or one that only reloads under the identical rank count. Decide up front whether you want sharded or full (consolidated) checkpoints, and set the state-dict type to match. None of these are exotic. They are the tax for a memory model where the “full model” is a transient reconstruction rather than a persistent object. FAQ What’s worth understanding about accelerate fsdp first? Accelerate FSDP wraps your model so that parameters, gradients, and optimizer state are sharded across GPUs rather than replicated. Full layers are reconstructed via all-gather only during their own forward or backward step, then discarded. In practice you write your training loop once and switch between single-GPU, DDP, and FSDP through a config file — but the wrap policy and sharding strategy still have to match your model. What does FSDP actually shard, and how does that differ from DDP? FSDP shards three states — parameters, gradients, and optimizer state — across ranks, so each GPU holds roughly 1/N of each. DDP replicates all three in full on every rank, which means adding GPUs raises throughput but gives zero per-device memory relief. FSDP does not shard activations; that axis needs activation checkpointing. How do you configure FSDP through Hugging Face Accelerate, and what do the auto-wrap policy and sharding strategy control? You configure it through an Accelerate config file rather than code changes. The auto-wrap policy sets the granularity of sharding — typically wrapping each transformer block as a unit so only one block is materialised at a time — and must reference the correct block class for your architecture. The sharding strategy (FULL_SHARD, SHARD_GRAD_OP, NO_SHARD) controls how aggressively state is partitioned, trading memory savings against communication overhead. When should you enable CPU offload or activation checkpointing alongside FSDP, and what do they cost in throughput? Enable CPU offload as a last resort when the model will not fit even under FULL_SHARD — it moves state to host RAM over PCIe and can cut throughput severalfold. Activation checkpointing addresses the memory axis FSDP ignores, recomputing intermediate activations at the cost of roughly one extra forward pass. On transformer training, checkpointing is usually the cheaper win because recompute stays on the GPU. How much per-GPU memory does FSDP save, and how do you estimate it before launching? For Adam in mixed precision, state runs to roughly 16 bytes per parameter (params, gradients, and the two moments plus fp32 master). Under FULL_SHARD across N ranks that term drops by a factor of N, plus the transient cost of the single largest gathered layer and unsharded activation memory. Do the byte arithmetic on paper first to decide whether a given cluster size is viable before queueing the job. What are the common failure modes, and how do you avoid them? The three recurring ones are a mismatched wrap policy (which collapses sharding into one unit and reproduces OOM), mixed-precision settings that disagree between FSDP and the trainer, and checkpoint save/load errors from using the wrong state-dict type. Each is a configuration issue, not a conceptual one — confirm the wrap policy names your real block class, align the precision settings, and decide up front whether checkpoints are sharded or consolidated. When is FSDP the right choice versus DeepSpeed ZeRO or tensor/pipeline parallelism? FSDP is the natural fit when your memory pressure is state-dominated and a single model layer still fits on one GPU after sharding — that is, the model is large in parameter count but no individual layer is monstrous. DeepSpeed ZeRO covers similar ground with a different offload and configuration surface, and Accelerate can drive DeepSpeed instead of native FSDP. Tensor or pipeline parallelism becomes necessary when a single layer is too large to reconstruct on one device, which sharding across the batch dimension cannot solve. Where the decision actually lands The question that decides your configuration is not “how do I turn on FSDP” — Accelerate makes that a config line. It is “which state is dominating my per-GPU footprint, and does reconstructing one layer at a time fit within my interconnect budget?” Answer that and the wrap policy, sharding strategy, and offload choices follow. Get it wrong and you have added communication overhead without solving the memory problem you started with. The accelerate config step where those choices are set is where the training run is really won or lost, well before the first step ever executes.