Pipeline Parallelism
About
Pipeline parallelism splits a model into stages and places them on the ranks of the pp mesh dimension. Each rank builds only the stages it hosts. A schedule runs the microbatches of a step through the stages and exchanges stage outputs and gradients over P2P communication. d9d supports several schedules, from GPipe to DualPipeV, and runs them all on one execution engine.
Shape Inference
A P2P receiver must know the shape of the incoming tensor to allocate its buffer. d9d asks your model to compute these shapes with a small protocol (ModuleSupportsPipelining). The model derives the shapes from the input arithmetically. d9d does not run a forward pass or trace the graph to find them.
Shapes can therefore change between steps, for example when the sequence length varies.
Construction Consistency
A common pattern in distributed training is "instantiate, then delete": build the full model on a CPU or meta device, then cut it apart with del model.layers[N:].
d9d rejects this pattern for three reasons:
- Fragility: A change to the model architecture requires a change to the external slicing script.
- Leaky abstractions: Forward methods fill up with checks like
if self.layer is not None. - Invalid states: The model object is half-built until it is sliced.
In d9d, models are pipeline-aware. Each pipeline rank builds only the stages it owns. The returned module is complete and valid right away.
Making Models Compatible
The Four I/O Roles
A pipelined model moves data across stage boundaries as four explicitly named, generic PyTree types. Dataclasses are the recommended form.
| Role | Meaning | Crosses P2P? |
|---|---|---|
PipelineInput |
Input to the first stage (built by the task) | no |
StageTransfer |
Payload between adjacent stages (out of N == in of N+1) | yes |
PipelineOutput |
Output of the last stage (to loss / result callback) | no |
SharedInput |
Value passed to every stage, rebuilt locally per rank | no |
Only StageTransfer crosses the network, so it is the only role that needs a TensorSpec.
The Protocol
To use pipeline parallelism, your model implements d9d.pipelining.api.ModuleSupportsPipelining[TPipelineInput, TStageTransfer, TSharedInput, TPipelineOutput]:
forward(inputs, shared):inputsis thePipelineInputon the first stage and the incomingStageTransferon the other stages. It returns the outgoingStageTransferon non-last stages and thePipelineOutputon the last stage. The stage knows its position from thePipelineStageInfoit gets at construction. It branches onis_current_stage_firstandis_current_stage_lastexplicitly.stage_transfer_spec(pipeline_input, boundary): Returns a PyTree with the same structure asStageTransfer, with every tensor leaf replaced by aTensorSpec. Theboundary(StageBoundary.incomingoroutgoing) selects which stage edge to describe.
The outgoing transfer of stage N and the incoming transfer of stage N+1 have the same dataclass type. So pytree.tree_flatten gives the same leaf order on both ends. Sender and receiver agree on the order by construction, and they need no handshake.
Example
Below is a skeleton of a Transformer-like model that supports d9d pipelining.
Supported Schedules
| Example JSON | Description |
|---|---|
{"schedule": "inference"} |
Inference only. Runs all forward passes and no backward passes. |
{"schedule": "gpipe"} |
GPipe. Hosts one stage per rank. Runs the forward pass of all microbatches before the backward pass. |
{"schedule": "looped_bfs", "num_stages_per_rank": 2} |
Looped Breadth-First. Hosts several stages per rank. Runs all work for one stage before it moves to the next. |
{"schedule": "1f1b", "num_stages_per_rank": 1, "zero_bubble": true} |
Interleaved 1F1B, or Interleaved Zero Bubble with zero_bubble. Hosts several stages per rank. Zero Bubble splits the backward pass into input-gradient and weight-gradient parts. |
{"schedule": "zero_bubble_v"} |
Zero Bubble V. Hosts 2 stages per rank in a V shape. Splits the backward pass into input-gradient and weight-gradient parts. |
{"schedule": "dual_pipe_v"} |
DualPipeV. A bidirectional schedule that hosts 2 stages per rank in a V shape and pairs forward and backward passes of different microbatches. |
Some schedules limit the number of microbatches per step:
dual_pipe_vneeds at least2 * pp_sizemicrobatches.1f1bneeds a microbatch count that is divisible bymax(1, num_microbatches // pp_size). A multiple ofpp_sizealways works.
Microbatches and Packs
Pipelining consumes a pack: a sequence of ready microbatches for one step. The pack length, and so the batch size, can change from step to step. Each stage infers buffer shapes per microbatch, so the microbatches in one pack can also differ in shape.
The schedule composes its program once per microbatch count and reuses it. It reallocates buffers when a microbatch shape differs from the previous step.
Usage
Within the Trainer
Pipelining is available in the Trainer framework. Set the schedule in the pipelining section of the Trainer config:
The Trainer builds the schedule and distributes the layers.
Manual Usage
To use pipelining outside the Trainer, for example in a custom loop, call the build_schedule factory.
build_schedule takes a model provider instead of a built model. The model provider is a function that accepts a PipelineStageInfo and returns the nn.Module for that stage. This keeps construction consistent.
API Reference
d9d.pipelining.api
Public pipelining API for end users.
PipelineLossFn = Callable[[TPipelineOutput, int], torch.Tensor]
module-attribute
Callback function type for calculating loss in the final pipeline stage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outputs
|
The |
required | |
microbatch_idx
|
The index of the current microbatch. |
required |
Returns:
| Type | Description |
|---|---|
|
The computed loss tensor (scalar). |
PipelineResultFn = Callable[[TPipelineOutput, int], Any]
module-attribute
Callback function type for handling results from a final pipeline stage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outputs
|
The |
required | |
microbatch_idx
|
The index of the current microbatch. |
required |
Returns:
| Type | Description |
|---|---|
|
Any value. The schedule ignores it. |
ModuleSupportsPipelining
Bases: Protocol[TPipelineInput, TStageTransfer, TSharedInput, TPipelineOutput]
Protocol for modules that can be split across pipeline stages.
A pipelined module carries four distinct I/O roles, each an arbitrary PyTree (dataclasses are the
recommended form). The module knows its position from the PipelineStageInfo it receives at
construction and branches on it explicitly.
Class Type Parameters:
| Name | Bound or Constraints | Description | Default |
|---|---|---|---|
TPipelineInput
|
Input consumed by the first stage. |
required | |
TStageTransfer
|
Payload transferred between adjacent stages. The outgoing transfer of stage
|
required | |
TSharedInput
|
Value passed to the forward pass of every stage. |
required | |
TPipelineOutput
|
Output produced by the last stage. |
required |
forward(inputs, shared)
Runs this stage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
TPipelineInput | TStageTransfer
|
|
required |
shared
|
TSharedInput
|
The value broadcast to every stage. |
required |
Returns:
| Type | Description |
|---|---|
TStageTransfer | TPipelineOutput
|
|
stage_transfer_spec(pipeline_input, boundary)
Describes the StageTransfer crossing the given boundary of this stage.
The returned PyTree has the same structure as the StageTransfer, with every tensor leaf
replaced by its TensorSpec. Implementations derive the shapes from pipeline_input
without running forward.
The engine calls this only for boundaries that transfer data. It never calls it for the
incoming edge of the first stage or the outgoing edge of the last stage, so
implementations need no special case for them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pipeline_input
|
TPipelineInput
|
A representative single microbatch of pipeline input. Only shapes and dtypes are read; values are never used. |
required |
boundary
|
StageBoundary
|
|
required |
Returns:
| Type | Description |
|---|---|
PyTree[TensorSpec]
|
A PyTree of |
PipelineSchedule
Bases: ABC, Generic[TPipelineInput, TSharedInput, TPipelineOutput]
Abstract base class for pipeline execution schedules.
Class Type Parameters:
| Name | Bound or Constraints | Description | Default |
|---|---|---|---|
TPipelineInput
|
The |
required | |
TSharedInput
|
The |
required | |
TPipelineOutput
|
The |
required |
step(inputs_microbatches, shared_microbatches, callback)
abstractmethod
Executes a single pipeline step over one pack of microbatches.
The number of microbatches in the step is len(inputs_microbatches) and may vary between steps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs_microbatches
|
tuple[TPipelineInput, ...]
|
Per-microbatch |
required |
shared_microbatches
|
tuple[TSharedInput, ...]
|
Per-microbatch |
required |
callback
|
PipelineLossFn[TPipelineOutput] | PipelineResultFn[TPipelineOutput]
|
Function to compute loss or process pipeline results. |
required |
PipelineStageInfo
dataclass
Position of the current stage in the distributed pipeline.
Attributes:
| Name | Type | Description |
|---|---|---|
current_stage |
int
|
The 0-based index of the current pipeline stage. |
num_stages |
int
|
The total number of stages in the pipeline. |
is_current_stage_first
property
Whether this is the first stage in the pipeline.
is_current_stage_last
property
Whether this is the last stage in the pipeline.
StageBoundary
Bases: Enum
Edge of a stage that a transfer spec describes.
Attributes:
| Name | Type | Description |
|---|---|---|
incoming |
The |
|
outgoing |
The |
TensorSpec
dataclass
distribute_layers_for_pipeline_stage(num_layers, num_virtual_layers_pre, num_virtual_layers_post, stage)
Calculates the layer index range for a specific pipeline stage.
This function spreads the layers across pipeline stages as evenly as possible. The first and last stages also run non-layer modules, such as embeddings and the LM head. Virtual layers reserve capacity for these modules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_layers
|
int
|
The total number of primary model layers to be distributed (e.g. the transformer blocks). |
required |
num_virtual_layers_pre
|
int
|
The number of virtual layers that represent the cost of modules on the first stage, before the main layers (e.g. token and position embeddings). |
required |
num_virtual_layers_post
|
int
|
The number of virtual layers that represent the cost of modules on the last stage, after the main layers (e.g. the final layer normalization and LM head). |
required |
stage
|
PipelineStageInfo
|
The position of the stage in the pipeline. |
required |
Returns:
| Type | Description |
|---|---|
int
|
A tuple |
int
|
inclusive and |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a stage gets zero or fewer layers because the pipeline is too long for the model. |
d9d.pipelining.factory
Pipeline schedule configurations and the factory that builds schedules from them.
AnyPipelineScheduleConfig = Annotated[PipelineScheduleInferenceConfig | PipelineScheduleGPipeConfig | PipelineScheduleLoopedBFSConfig | PipelineSchedule1F1BConfig | PipelineScheduleZeroBubbleVConfig | PipelineScheduleDualPipeVConfig, Field(discriminator='schedule')]
module-attribute
Union of all supported pipeline schedule configuration types.
Pydantic selects the config class by the schedule field.
PipelineSchedule1F1BConfig
Bases: BaseModel
Configuration for Interleaved 1F1B and Interleaved Zero Bubble execution.
This schedule supports several stages per rank. With zero_bubble, it splits the backward pass
into input-gradient and weight-gradient parts to reduce pipeline bubbles.
Attributes:
| Name | Type | Description |
|---|---|---|
schedule |
Literal['1f1b']
|
Discriminator field. Always |
num_stages_per_rank |
int
|
The number of stages hosted on each rank. |
zero_bubble |
bool
|
Whether to use the Interleaved Zero Bubble (ZB1P) variant. |
PipelineScheduleDualPipeVConfig
Bases: BaseModel
Configuration for DualPipeV execution.
This bidirectional schedule places stages in a V shape and pairs the forward pass of one microbatch with the backward pass of another. It always hosts 2 stages per rank. The number of microbatches per step must be at least twice the pipeline-parallel size.
Attributes:
| Name | Type | Description |
|---|---|---|
schedule |
Literal['dual_pipe_v']
|
Discriminator field. Always |
PipelineScheduleGPipeConfig
PipelineScheduleInferenceConfig
PipelineScheduleLoopedBFSConfig
Bases: BaseModel
Configuration for Looped Breadth-First execution.
This schedule works like GPipe but supports several stages per rank. It runs all work for one stage before it moves to the next.
Attributes:
| Name | Type | Description |
|---|---|---|
schedule |
Literal['looped_bfs']
|
Discriminator field. Always |
num_stages_per_rank |
int
|
The number of stages hosted on each rank. |
PipelineScheduleZeroBubbleVConfig
Bases: BaseModel
Configuration for Zero Bubble V (ZBV) execution.
This schedule places stages in a V shape and splits the backward pass into input-gradient and weight-gradient parts. It always hosts 2 stages per rank.
Attributes:
| Name | Type | Description |
|---|---|---|
schedule |
Literal['zero_bubble_v']
|
Discriminator field. Always |
build_schedule(dist_context, schedule_config, model_provider)
Constructs the pipeline schedule and instantiates model stages.
If the context is distributed, it builds a PipelineScheduleExecutor with the stages this
rank hosts. Otherwise, it builds an OfflinePipelineExecutor that runs the model directly.
The number of microbatches is not fixed here: each step takes it from the pack it receives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dist_context
|
DistributedContext
|
The distributed context. |
required |
schedule_config
|
AnyPipelineScheduleConfig
|
Configuration object determining the schedule strategy. |
required |
model_provider
|
Callable[[PipelineStageInfo], Module]
|
A factory function that accepts stage info and returns the |
required |
Returns:
| Type | Description |
|---|---|
tuple[PipelineScheduleInfo, list[Module]]
|
A tuple of the schedule info and the list of modules created on this rank. |