FSDP › Wrapping Policies

Michael BrenndoerferJanuary 22, 20264 min read
Language AI HandbookAll chapters
Part 31 of 67Training Infrastructure
Chapter 226FSDP › Wrapping Policies
Reading section 6 of 13 · Wrapping Policies · 4 minSection 6 of 13 · 4 min

Book contents

Section 6 of 13 · 4 min read

FSDP › Wrapping PoliciesOne section · Sections in this chapter6 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

A key decision in FSDP setup is how to group model submodules into FSDP units. Each FSDP unit is the granularity at which all-gather and reduce-scatter operations occur: finer granularity means smaller all-gather buffers and lower peak memory, but more frequent communication and higher overhead from kernel launches.

Getting the wrapping policy right matters more than many practitioners expect. A policy that is too fine-grained can reduce throughput by 2-3x compared to an optimal policy, because GPU kernels have significant launch overhead that becomes dominant when individual operations are small. A policy that is too coarse can cause out-of-memory errors because the all-gather buffer for a single large unit might not fit.

Auto Wrap Policy

The transformer_auto_wrap_policy function in PyTorch's FSDP utilities wraps each transformer layer (self-attention block, FFN block, etc.) as a separate FSDP unit. This is the recommended starting point for transformer models.

The auto wrap policy accepts a set of module classes to wrap. For a standard transformer, you would typically pass the class corresponding to a single transformer block, for example GPTNeoXLayer for GPT-NeoX or LlamaDecoderLayer for Llama. The embedding and output head layers are wrapped at the top level (as part of the root FSDP module) unless you explicitly specify otherwise.

In[3]:
Code
import functools

from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy

# Example for a Llama-style model
auto_wrap_policy = functools.partial(
    transformer_auto_wrap_policy,
    transformer_layer_cls={LlamaDecoderLayer},
)

The reason this wrapping strategy works well for transformers is that each transformer layer is approximately equal in size (assuming the same hidden dimension and FFN expansion ratio across all layers), so all FSDP units have similar all-gather buffer sizes. Uniform unit sizes lead to uniform communication times, which maps well to overlapping communication with computation: while one layer runs its forward pass, FSDP can prefetch (all-gather) the next layer's parameters.

Size-Based Wrap Policy

The size_based_auto_wrap_policy wraps modules based on their parameter count. Modules with fewer than a threshold number of parameters are merged with their parent, reducing the total number of FSDP units and thus the communication frequency.

In[3]:
Code
import functools

from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy

size_wrap_policy = functools.partial(
    size_based_auto_wrap_policy,
    min_num_params=1_000_000,  # wrap modules with at least 1M parameters
)

This policy is useful for models that do not follow the uniform-transformer-layer pattern, such as encoder-decoder models with asymmetric components or models with large embedding tables followed by small classification heads.

Manual Wrapping

For full control, you can manually wrap specific submodules by calling FSDP() on them before wrapping the parent model. This is useful when different parts of the model require different sharding strategies or mixed precision settings. For example, you might want the main transformer layers to use FULL_SHARD with bfloat16, but keep the embedding table in float32 with NO_SHARD because it is accessed sparsely and does not benefit from sharding.

Communication Prefetching

A subtle but important performance feature of FSDP is prefetching: the ability to issue the all-gather for the next FSDP unit while the current unit is still computing. PyTorch FSDP implements this automatically for the forward pass by default, overlapping the communication for layer k+1k+1 with the computation for layer kk. This overlap is only possible when the wrapping granularity is at the layer level (one FSDP unit per layer), not at coarser granularity where there is no "next unit" to prefetch.

The backward_prefetch parameter controls whether the same overlap happens during the backward pass. By default, PyTorch prefetches during backward as well, which can significantly improve throughput on hardware where communication and compute can run concurrently.