You enable FSDP through Hugging Face Accelerate, watch per-GPU memory drop, and the bigger model finally fits across your GPUs. Then the step time climbs, and the easy conclusion is that this is simply what sharding costs. It isn’t. Fully Sharded Data Parallel does not remove your bottleneck — it moves it, usually from memory capacity to inter-GPU communication, and only a profiler tells you where it actually landed. That distinction matters because the mental model most teams carry into FSDP is wrong in a specific, expensive way. The naive framing treats sharding as a free lunch: split the model across devices, get the memory back, accept whatever throughput you’re handed. The correct framing is a trade. You are paying communication cost — all-gather and reduce-scatter traffic on every layer — to buy memory headroom. Whether that trade is worth it, and whether you’ve configured it well, is an empirical question, not a default. What does Accelerate FSDP actually do? FSDP is PyTorch’s implementation of fully sharded data parallelism, and Hugging Face Accelerate is the wrapper that makes it configurable without rewriting your training loop. Under plain Distributed Data Parallel (DDP), every GPU holds a full copy of the model parameters, gradients, and optimizer state; the only thing sharded is the input batch. That’s simple and communication-light, but it caps your model size at whatever fits on a single device. FSDP shards the model state itself. Parameters, gradients, and optimizer state are partitioned across the data-parallel group, so each GPU stores only a slice. The consequence is that no single device ever needs to hold the whole model — which is the entire reason you reach for it. When a layer needs to run its forward pass, FSDP performs an all-gather to reconstruct that layer’s full parameters on every GPU just in time, runs the computation, then frees the gathered copy. On the backward pass it all-gathers parameters again for the gradient computation, then does a reduce-scatter to average gradients and hand each GPU back only its shard. That is the mechanism, and it is also the cost. Every wrapped unit of the model incurs communication on both the forward and backward pass. The memory you save is real; the network traffic you take on is equally real. This is the same reason peak FLOPs rarely predict achieved throughput — the number on the spec sheet describes compute the interconnect may never let you reach. What FSDP shards, and how much memory it recovers The memory saving is not uniform across the three things FSDP shards, and understanding the split tells you what to expect before you profile. Sharded component What it holds Where the saving is largest Parameters Model weights Proportional to model size; recovered between layers via all-gather Gradients Per-parameter gradients Recovered by reduce-scatter; each GPU keeps only its shard Optimizer state Adam moments, master weights Often the biggest saving — Adam stores roughly 2x the parameter count in fp32 For a model trained with the Adam optimizer in mixed precision, the optimizer state is frequently the dominant memory consumer — the first and second moment estimates plus fp32 master weights can add up to several times the raw parameter footprint. Sharding that state across the group is often where the largest headroom comes from. This is a structural property of Adam, not a benchmarked figure for a specific model; the exact ratio depends on your precision and optimizer configuration. The practical implication: if your out-of-memory error came from optimizer state rather than activations, FSDP addresses it directly. If it came from activations, you need activation checkpointing (which FSDP can combine with) — sharding parameters alone won’t help. How do the Accelerate FSDP options actually control behaviour? Accelerate exposes FSDP through accelerate config or an FSDP plugin, and two option families do most of the work. Getting them wrong is the most common cause of “FSDP made things slower.” Sharding strategy decides how aggressively state is partitioned: FULL_SHARD — parameters, gradients, and optimizer state all sharded. Maximum memory saving, maximum communication. SHARD_GRAD_OP — only gradients and optimizer state sharded; parameters replicated. Less communication, less saving. Useful when the model fits but the optimizer state doesn’t. NO_SHARD — equivalent to DDP. A useful baseline, not a production setting. HYBRID_SHARD — full sharding within a node, replication across nodes. This is the setting that most often rescues multi-node runs, because it keeps the heavy all-gather traffic on fast intra-node NVLink and only replicates across the slower inter-node fabric. Wrapping policy decides the granularity at which FSDP applies. This is the option teams most often leave at a poor default. If you wrap the entire model as one unit, FSDP must all-gather every parameter before the forward pass can start — you lose the memory benefit and gain none of the overlap. The correct approach is transformer-aware wrapping (TRANSFORMER_BASED_WRAP), which wraps each transformer block as its own FSDP unit so that communication for layer N+1 can overlap with computation of layer N. The overlap is what keeps GPUs busy instead of idle. If you take one configuration lesson from this: the wrapping policy is not cosmetic. It determines whether all-gather can be hidden behind compute or whether it stalls the pipeline. Why does FSDP sometimes slow training down? Because the communication it introduces can exceed the compute it overlaps with. When that happens, GPUs sit idle waiting for all-gather to complete before the next layer can run. The symptom looks like low GPU utilization despite a “full” workload — the classic signature of a communication-bound run rather than a compute-bound one. Three conditions push a run into that regime, in our experience across GPU-optimization engagements (an observed pattern, not a benchmarked threshold): Small per-GPU compute. If each layer’s matrix multiply finishes faster than its all-gather, there’s nothing to hide the communication behind. Larger batch sizes or fewer, larger GPUs often help more than more GPUs. Slow interconnect. FSDP over PCIe behaves very differently from FSDP over NVLink or NVSwitch. Inter-node traffic on Ethernet without RDMA is worse still. The interconnect topology is a first-order variable, not a detail. Poor wrapping granularity. As above — wrap too coarsely and you serialize communication; wrap too finely and you pay per-unit overhead on tiny transfers. The reason this is worth naming precisely is that all three failure modes present the same way on a dashboard: memory looks healthy, GPUs look under-utilized, and step time is disappointing. You cannot distinguish them without looking inside the step. How do I profile an FSDP run to find the real bottleneck? The question that actually resolves the “is this slow because of sharding?” doubt is a measurement question: what fraction of step time is spent in communication versus compute? PyTorch’s built-in profiler, exposed through torch.profiler, records CUDA kernels and NCCL collectives on a shared timeline, and the NCCL operations — ncclAllGather, ncclReduceScatter — are exactly the ones FSDP generates. When you see wide gaps on the compute stream that line up with active NCCL kernels, your GPUs are waiting on communication. A profiling pass for an FSDP run should establish three numbers: Communication-to-compute ratio per step. The fraction of wall-clock step time inside NCCL collectives. High ratios mean the sharding strategy or interconnect is the constraint. Achieved throughput versus a single-GPU baseline. Tokens/sec or samples/sec per GPU compared to the same model on one device (where it fits). If per-GPU throughput collapses as you add GPUs, communication is not being hidden. Memory headroom recovered per sharding strategy. How much you actually got back, which tells you whether you can afford SHARD_GRAD_OP (cheaper communication) or genuinely need FULL_SHARD. This is the same discipline behind the three pillars of observability applied to GPU utilisation: a utilization number alone is a symptom, and the timeline is where the cause lives. Nsight Systems gives the same NCCL-versus-kernel timeline at the system level if you need to correlate across GPUs and the host. When is FSDP the right choice versus DDP? The decision is driven by model size relative to device memory, not by a general preference for “more parallelism.” Situation Better choice Why Model + optimizer state fit on one GPU DDP No communication overhead beyond gradient all-reduce; simplest path Model fits but optimizer state doesn’t SHARD_GRAD_OP Recovers the largest saving with the least added communication Model doesn’t fit on one GPU FULL_SHARD The reason FSDP exists; accept the communication cost Multi-node, model too large for one node HYBRID_SHARD Keeps all-gather on intra-node NVLink, replicates across slower fabric Fast interconnect, large per-GPU compute FSDP scales well Communication hides behind compute Slow interconnect, small per-GPU compute Reconsider Communication dominates; more GPUs won’t help The honest version of this decision is that FSDP is not a throughput optimization — it is a capacity enabler that you pay for in communication. If your model already fits under DDP, adding FSDP usually costs you throughput for no benefit. This sits inside the broader picture of what Hugging Face Accelerate does across multi-GPU training and inference, of which FSDP is one strategy among several — DeepSpeed ZeRO being the closest alternative with a similar sharding philosophy. FAQ How does accelerate fsdp actually work? Accelerate FSDP is Hugging Face Accelerate’s configuration layer over PyTorch’s Fully Sharded Data Parallel. It partitions model parameters, gradients, and optimizer state across your GPUs so no single device holds the whole model, then all-gathers each layer’s parameters just in time to compute it. In practice it lets a model that won’t fit on one GPU train across several — at the cost of communication traffic on every layer. What does FSDP shard — parameters, gradients, optimizer state — and how does that reduce per-GPU memory? FSDP shards all three. Each GPU stores only its slice of the parameters, gradients, and optimizer state, reconstructing full parameters temporarily via all-gather when a layer runs. The optimizer state is often the largest saving, because optimizers like Adam store several times the parameter count in fp32 moments and master weights. How do I configure FSDP through Hugging Face Accelerate, and what do the wrapping and sharding-strategy options actually control? You configure it via accelerate config or an FSDP plugin. The sharding strategy (FULL_SHARD, SHARD_GRAD_OP, HYBRID_SHARD, NO_SHARD) controls how aggressively state is partitioned and therefore how much communication you incur. The wrapping policy controls granularity — transformer-aware wrapping lets communication for one block overlap with computation of the previous one, which is what keeps GPUs busy. Why does FSDP sometimes slow training down, and how do I tell whether communication is the bottleneck? FSDP slows training when the all-gather and reduce-scatter traffic it adds exceeds the compute it can overlap with, leaving GPUs idle. The symptom is low GPU utilization despite a full workload. You confirm communication is the cause by profiling: if step-time gaps on the compute stream line up with active NCCL collectives, the interconnect or sharding strategy is your bottleneck. How do I profile an FSDP run to see whether GPUs are idle waiting on all-gather or reduce-scatter? Use torch.profiler or Nsight Systems to capture CUDA kernels and NCCL collectives on one timeline. Look for ncclAllGather and ncclReduceScatter operations and measure the fraction of step time spent inside them. Wide compute-stream gaps aligned with active NCCL kernels mean the GPUs are stalled waiting on communication. When is FSDP the right choice versus DDP or other parallelism strategies for my model size? Use DDP when the model and optimizer state fit on one GPU — FSDP would only add communication cost. Use SHARD_GRAD_OP when the model fits but optimizer state doesn’t, FULL_SHARD when the model itself doesn’t fit, and HYBRID_SHARD for multi-node runs where you want to keep heavy traffic on fast intra-node links. DeepSpeed ZeRO is the closest alternative with a similar sharding approach. What sharding strategy and communication-overlap settings give the best throughput once I’ve found the bottleneck? There’s no universal answer — it depends on your interconnect and per-GPU compute, which is why profiling comes first. As a rule, transformer-aware wrapping to enable overlap, the least aggressive sharding strategy that still fits the model, and HYBRID_SHARD on multi-node systems tend to give the best throughput. Verify each change against the communication-to-compute ratio you measured, not against memory alone. FSDP is a good example of why capacity and speed are separate problems that a single dashboard number can blur together. The moment you can fit a bigger model, it’s tempting to stop measuring — but the interesting question has only just started. Whether the real constraint on your run is inter-GPU communication, host-side scheduling, or genuine compute is exactly what a GPU performance audit is built to answer before any config gets tuned.