Recommended resources
The freely available resources this lesson will direct you to.
Introduction
Welcome back. In our previous lesson, we performed a detailed quantitative analysis of the three stages of the Zero Redundancy Optimizer (ZeRO). We established that ZeRO-1 and ZeRO-2 offer significant memory savings with no communication overhead, while ZeRO-3 achieves a massive, linear reduction in memory at the cost of a 1.5x increase in communication. This covered the what and why of memory partitioning.
Today, we transition from theory to practice and explore the how. Our goal is to understand the implementation of the most powerful sharding strategy—full model state partitioning—as realized in two leading frameworks: Microsoft's DeepSpeed (which introduced ZeRO) and PyTorch's native Fully Sharded Data Parallel (FSDP).
Understanding these implementations is crucial for designing and debugging large-scale training systems. You'll learn about the core algorithm they share and the specific design choices and optimizations that differentiate them, enabling you to make informed decisions when architecting AI systems.
The Core Mechanism: On-Demand Parameter Materialization
In the previous lesson, we saw that ZeRO Stages 1 and 2 still require each GPU to hold a full copy of the model parameters, which becomes the memory bottleneck. ZeRO-3 and FSDP solve this by partitioning the parameters themselves. This raises a fundamental question: how can a GPU perform a forward pass for a layer if it doesn't have the complete weights for that layer?
The answer is a clever, dynamic process of on-demand parameter materialization. Instead of holding the entire model in memory, each GPU retrieves the parameters it needs just-in-time for computation and discards them immediately afterward.
Let's watch a short segment that introduces this concept and contrasts it with the limitations of standard data parallelism.
Distributed ML Talk @ UC Berkeley
To begin, this video provides a high-level overview of the problem with data parallelism for large models and introduces FSDP/DeepSpeed as the solution that shards the model itself.
Please watch from 9:45 to 15:20. The first part (until 11:00) is a quick review of the standard training loop. The second part (11:00 to 15:20) explains data parallelism and its core limitation: the model must fit on each GPU. This sets the stage perfectly for the sharding solution we will now discuss.
Now, let's look at the detailed mechanics of how this on-demand materialization works. The process can be broken down into the forward and backward passes.
The diagram below illustrates this workflow. Each GPU (or "rank") holds only a shard of the model's parameters.

Here is the step-by-step process, which applies to both DeepSpeed ZeRO-3 and FSDP:
During the Forward Pass (for each block of layers):
- Gather Parameters: Right before computation, all GPUs participate in an
AllGathercollective operation. Each GPU broadcasts its local shard of the parameters for the current layer(s) to all other GPUs. At the end of this step, every GPU momentarily has the full, unsharded parameters for that specific layer block. - Perform Computation: With the full parameters now materialized in memory, the GPU executes the standard forward pass for that block.
- Discard Parameters: Immediately after the computation, each GPU discards the parameter shards it received from its peers, freeing that memory. It only retains its original, assigned shard.
During the Backward Pass (for each block of layers):
- Gather Parameters: Just like in the forward pass, an
AllGatheroperation re-materializes the full parameters for the current layer block, as they are needed to compute the gradients. - Perform Computation: The backward pass computation is executed for that block, producing local gradients.
- Reduce and Scatter Gradients: Instead of synchronizing the full gradient tensor with
AllReduce(as in standard DDP), the frameworks use aReduceScattercollective. This single, efficient operation achieves two things: it averages the gradients from all GPUs and shards the result, delivering to each GPU only the partition of the averaged gradient that corresponds to its parameter partition. - Discard Parameters: The gathered parameters are discarded.
- Update Weights (Locally): Each GPU now has everything it needs to update its local partition of the model: its shard of the averaged gradients and its shard of the optimizer states. It performs the optimizer step on its local data only.
To see this dynamic process explained in detail, let's watch another excellent video.
Multi GPU Fine tuning with DDP and FSDP
This video provides a very clear, step-by-step walkthrough of how FSDP assembles the model on-the-fly. It's one of the best explanations of the core mechanism.
Please watch from 26:09 to 30:36. The narrator explains exactly how weights, gradients, and optimizer states are sharded and then dynamically gathered and scattered during the forward and backward passes.
This on-demand materialization is the fundamental technique that allows data-parallel training of models that are far too large to fit on a single GPU.
Framework Implementations: DeepSpeed vs. PyTorch FSDP
While both DeepSpeed and FSDP use the same core mechanism, they are distinct libraries with different origins and some unique features.
DeepSpeed ZeRO
DeepSpeed is a comprehensive library from Microsoft for large-scale model training. It introduced the ZeRO optimizer and its three stages. As you recall from the last lesson:
- ZeRO-1: Shards optimizer states.
- ZeRO-2: Shards optimizer states and gradients.
- ZeRO-3: Shards optimizer states, gradients, and model parameters.
When we talk about full memory sharding, we are referring to ZeRO-3. In practice, using DeepSpeed often involves simply adding a configuration file to your training script that specifies which ZeRO stage to use.
Everything about Distributed Training and Efficient Finetuning
This blog post clearly summarizes the three ZeRO stages. Let's focus on the description for Stage 3, as it corresponds to the full sharding mechanism we just discussed.
Please read the section titled 'ZeRO Stage 3 / Pos+g+p (Optimizer State + Gradient + Parameter Partitioning)'. It provides a concise explanation and a helpful visualization of how parameters are partitioned across GPUs.
The key takeaway is that DeepSpeed provides a mature, battle-tested implementation of the ZeRO stages, accessible as configuration options.
PyTorch FSDP
Fully Sharded Data Parallel (FSDP) is PyTorch's native implementation of the ZeRO-3 paradigm. It was co-designed with PyTorch's core components, such as the autograd engine and memory allocator, for tight integration and high performance. FSDP introduces a few key concepts that are important to understand from a system design perspective.
[PDF] PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel
The official FSDP paper from Meta AI provides the best insights into its design principles. We will focus on the section describing its system design and sharding strategies.
Please read Section 3, 'SYSTEM DESIGN' (pages 3850-3852). Pay close attention to: The overview in the first few paragraphs and Figure 1. Section 3.2.1 'Full Sharding': Note the discussion of FlatParameter for coalescing parameters into a single large tensor to improve communication efficiency. Section 3.2.2 'Hybrid Sharding': This is a crucial concept. Understand how it combines sharding and replication to adapt to network topology (e.g., fast intra-node vs. slower inter-node communication).
Let's summarize the key FSDP concepts from the paper:
-
Wrapping Policy and FSDP Units: FSDP requires you to "wrap" your model's layers. This wrapping partitions the model into FSDP Units. The
AllGatherandReduceScatteroperations work on a per-unit basis. The size of these units creates a trade-off:- Fine-grained units (e.g., wrapping each layer individually) result in lower peak memory, as only a small part of the model is materialized at once. However, this can lead to higher communication overhead due to many small, less efficient
AllGathercalls. - Coarse-grained units (e.g., wrapping large blocks of layers) have higher peak memory but can be more efficient by issuing fewer, larger communication calls.
- Fine-grained units (e.g., wrapping each layer individually) result in lower peak memory, as only a small part of the model is materialized at once. However, this can lead to higher communication overhead due to many small, less efficient
-
FlatParameter: To optimize communication, FSDP coalesces all parameters within a single FSDP unit into a single, contiguous 1D tensor called aFlatParameter. This ensures that communication primitives likeAllGatherandReduceScatteroperate on a single, large chunk of data, which is much more efficient for the underlying networking hardware (e.g., NCCL) than issuing many small calls. -
Sharding Strategies: FSDP offers two primary strategies that are vital for system architects:
- Full Sharding (
FULL_SHARD): This is the direct equivalent of ZeRO-3. All model states are sharded across all available GPUs in the world. This provides the maximum memory savings but sends all communication traffic across the entire network. - Hybrid Sharding (
HYBRID_SHARD): This is a more sophisticated strategy designed for multi-node clusters. It allows you to define subgroups of GPUs for sharding. A common use case is to shard within a node (e.g., across 8 GPUs connected by high-speed NVLink) and replicate across nodes (connected by slower InfiniBand or Ethernet). This minimizes expensive inter-node communication for theAllGatheroperations, restricting it to the faster intra-node interconnect. Gradient synchronization still happens across all nodes, but the volume of parameter communication is drastically reduced. This is the same concept as "hierarchical partitioning" in DeepSpeed's ZeRO++.
- Full Sharding (
To solidify the distinction between these frameworks, the following video provides a concise comparison.
Multi GPU Fine tuning with DDP and FSDP
This final video explicitly contrasts PyTorch FSDP with the DeepSpeed stages, clarifying that FSDP is equivalent to DeepSpeed Stage 3.
Please watch from 31:35 to 33:36. This section directly compares the naming conventions and functionalities, helping you map the concepts between the two frameworks.
In modern practice, high-level libraries like Hugging Face Accelerate provide a unified interface, allowing you to switch between DeepSpeed and FSDP often with just a change in a configuration file. This abstraction makes it easier to experiment with these powerful techniques without deep code changes.
Conclusion
In this lesson, we demystified how full memory sharding is implemented in practice. We moved beyond the quantitative benefits of ZeRO-3 to the underlying mechanisms that enable it.
Key Takeaways:
- Both DeepSpeed ZeRO-3 and PyTorch FSDP implement full model state partitioning to enable data-parallel training of models that exceed single-GPU memory.
- The core mechanism is on-demand parameter materialization: parameters for a given layer are gathered via
AllGatherjust before computation and discarded immediately after. - Gradients are efficiently synchronized and sharded using a
ReduceScatteroperation, avoiding the need to store the full gradient tensor on every device. - FSDP introduces explicit concepts like
FlatParameterfor communication efficiency and aHYBRID_SHARDstrategy, which is critical for optimizing performance on multi-node clusters by aligning the communication pattern with the network topology. - While FSDP is equivalent to DeepSpeed Stage 3 in its sharding goal, its native integration and hybrid sharding model are key design features within the PyTorch ecosystem.
Preview of the Next Lesson:
So far, our memory optimization efforts have focused entirely on the model states: parameters, gradients, and optimizer states. However, another significant portion of GPU memory is consumed by activations—the intermediate outputs of each layer stored during the forward pass for use in the backward pass. In the next lesson, we will explore activation checkpointing, a powerful technique that trades a small amount of re-computation for substantial memory savings by not storing all activations.