Pipeline Parallelism
The d9d Approach
d9d implements a modern, highly modular pipelining engine designed for performance, stability and customization.
Dynamic Shapes & Algorithmic Shape Inference
To run P2P (Point-to-Point) communication, the receiver must know the shape of the incoming tensor to pre-allocate buffers. d9d asks your model to implement a lightweight protocol (ModuleSupportsPipelining) to calculate the shape of the payload transferred between stages mathematically, without performing a heavy forward pass or doing a distributed graph tracing.
This allows supporting Dynamic Shapes (e.g., varying sequence lengths) efficiently across runs.
Construction Consistency (No Patching)
A common anti-pattern in distributed training is "Instantiate-then-Delete": creating a huge model on CPU/Meta device and then hacking it apart del model.layers[N:].
We reject this pattern because of:
- Fragility: Changes to model architecture require changes to the external slicing script.
- Leaky Abstractions: Forward methods become full of
if self.layer is not None. - Invalid States: The model object exists in a "zombie" state until sliced.
In d9d, models are Pipeline-Aware. Each pipeline rank constructs only the sub-graph it owns. The object returned is compliant, complete, and valid immediately.
Making Models Compatible
The Four IO 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 wire, 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 incomingStageTransferotherwise; it returns the outgoingStageTransferon non-last stages and thePipelineOutputon the last stage. The stage knows its position from thePipelineStageInfoit received at construction, so it branches onis_current_stage_first/is_current_stage_lastexplicitly.stage_transfer_spec(pipeline_input, boundary)— returns a PyTree structurally identical toStageTransferwith every tensor leaf replaced by aTensorSpec. Theboundary(StageBoundary.incoming/outgoing) selects which inter-stage edge to size.
Because stage N's outgoing transfer and stage N+1's incoming transfer are the same
dataclass type, pytree.tree_flatten yields identical leaf orderings on both ends. Sender and
receiver therefore agree on the wire order by construction — no name/shape handshake is needed.
Example
Below is a skeleton of a Transformer-like model implemented for d9d pipelining.
Using the Pipeline
Supported Schedules
| Example JSON | Description |
|---|---|
{"schedule": "inference"} |
Configuration for inference-only pipeline execution. Runs all forward passes sequentially without any backward passes. |
{"schedule": "gpipe"} |
Standard GPipe execution. Assumes a single stage per rank and processes all microbatches for the forward pass before switching to the backward pass. |
{"schedule": "looped_bfs", "num_stages_per_rank": 2} |
Looped Breadth-First Search execution. Supports multiple stages per rank (virtualization) and executes all work for a specific stage before moving to the next. |
{"schedule": "1f1b", "num_stages_per_rank": 1, "zero_bubble": true} |
Interleaved 1F1B and Interleaved Zero Bubble execution. Supports multiple stages per rank. Handles sharding backward passes to dI and dW when zero_bubble is enabled. |
{"schedule": "zero_bubble_v"} |
Zero Bubble V (ZBV) execution. A specialized V-shape topology schedule that splits backward passes into Input and Weight gradients. Requires exactly 2 stages per rank. |
{"schedule": "dual_pipe_v"} |
DualPipeV execution. A bidirectional pipeline schedule for high-throughput training using V-shape topology and reciprocal forward/backward scheduling. |
Microbatches and packs
Pipelining consumes a pack: a sequence of ready microbatches for one step. The pack length (and therefore the batch size) may vary from step to step. Buffers are sized per microbatch — each stage infers shapes for every microbatch in the pack independently — so the microbatches within a single pack may also differ in shape. The schedule recompiles its program when the microbatch count changes and reallocates buffers when any microbatch's shape changes.
Usage within the Trainer
Pipelining is available in the Trainer framework. When configuring the Trainer, simply provide an AnyPipelineScheduleConfig in your training arguments. The Trainer handles the construction of the schedule and the distribution of layers automatically.
Advanced - Manual Usage
If you want to use pipelining outside the Trainer (e.g., custom loops), you use the build_schedule factory.
The build_schedule function requires a Model Provider logic. Instead of passing an instantiated model, you pass a function that accepts PipelineStageInfo and returns the nn.Module for that stage. This ensures construction consistency.
d9d.pipelining.api
Pipelining API that is intended to be accessible by end user.
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 micro-batch being processed. |
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 micro-batch being processed. |
required |
Returns:
| Type | Description |
|---|---|
|
Anything - not used. |
ModuleSupportsPipelining
Bases: Protocol[TPipelineInput, TStageTransfer, TSharedInput, TPipelineOutput]
Protocol for modules that can be split across pipeline stages.
A pipelined module carries four distinct IO 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 every stage's forward. |
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 is structurally identical to the StageTransfer itself, with every
tensor leaf replaced by its TensorSpec. Shapes are derived by cheap arithmetic on
pipeline_input; the forward body is never executed.
The engine only calls this for boundaries that actually transfer, deciding terminality from
the pipeline topology — it is never called for the first stage's incoming edge nor the
last stage's outgoing edge. Implementations therefore do not need to special-case terminal
boundaries.
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 defining the interface 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 schedule receives the 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
Holds information about the current position within 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
Determines if this is the first stage in the pipeline.
Returns:
| Type | Description |
|---|---|
bool
|
True if current_stage is 0. |
is_current_stage_last
property
Determines if this is the last stage in the pipeline.
Returns:
| Type | Description |
|---|---|
bool
|
True if current_stage is the last index. |
StageBoundary
Bases: Enum
Identifies which inter-stage edge of a stage 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 distributes a given number of layers across multiple pipeline stages as evenly as possible. It accounts for additional, non-layer computational load on the first and last stages (e.g., embeddings and the LM head) by using the concept of 'virtual layers' to reserve capacity.
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 representing the computational cost of modules on the first stage, before the main layers (e.g., token and positional embeddings). |
required |
num_virtual_layers_post
|
int
|
The number of 'virtual' layers representing the computational cost of modules on the last stage, after the main layers (e.g., the final layer normalization and LM head). |
required |
stage
|
PipelineStageInfo
|
An object containing total stages and current stage index. |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int]
|
A tuple (start_index, end_index), representing the slice of layers for the given stage. The start_index is inclusive and the end_index is exclusive. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the pipeline configuration results in a stage having zero or negative layers assigned (pipeline too long for the model size). |
d9d.pipelining.factory
AnyPipelineScheduleConfig = Annotated[PipelineScheduleInferenceConfig | PipelineScheduleGPipeConfig | PipelineScheduleLoopedBFSConfig | PipelineSchedule1F1BConfig | PipelineScheduleZeroBubbleVConfig | PipelineScheduleDualPipeVConfig, Field(discriminator='schedule')]
module-attribute
Union of all supported pipeline schedule configuration types.
This type alias uses a Pydantic discriminator on the schedule field to allow
polymorphic validation and serialization of specific schedule configs (e.g.
Inference, GPipe, 1F1B, ZeroBubble, etc.).
PipelineSchedule1F1BConfig
Bases: BaseModel
Configuration for Interleaved 1F1B and Interleaved Zero Bubble execution.
Supports assigning multiple stages per rank and sharding backward to dI and dW to reduce pipeline bubbles.
PipelineScheduleDualPipeVConfig
Bases: BaseModel
Configuration for DualPipeV execution.
A bidirectional pipeline schedule for high-throughput training, utilizing V-shape topology and reciprocal forward/backward scheduling.
PipelineScheduleGPipeConfig
Bases: BaseModel
Configuration for GPipe execution.
This assumes a single stage per rank and processes all microbatches for the forward pass before switching to the backward pass.
PipelineScheduleInferenceConfig
Bases: BaseModel
Configuration for inference-only pipeline execution.
This schedule runs all forward passes sequentially without any backward passes.
PipelineScheduleLoopedBFSConfig
Bases: BaseModel
Configuration for Looped Breadth-First Search execution.
Similar to GPipe, but supports multiple stages per rank (virtualization). It executes all available work for a specific stage before moving to the next.
PipelineScheduleZeroBubbleVConfig
Bases: BaseModel
Configuration for Zero Bubble V (ZBV) execution.
A specialized V-shape topology schedule that splits backward passes into Input and Weight gradients to maximize overlap. Requires exactly 2 stages per rank.
build_schedule(dist_context, schedule_config, model_provider)
Constructs the pipeline schedule and instantiates model stages.
This function coordinates the creation of the pipeline. If the context is
distributed, it builds a parallel schedule (PipelineScheduleExecutor) by
calculating topology and creating stages for the current rank. If the
context is local, it builds an offline schedule (OfflinePipelineExecutor)
for direct execution. The number of microbatches is decided per step, when
the schedule receives a pack, so it is not fixed here.
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 an |
required |
Returns:
| Type | Description |
|---|---|
PipelineScheduleInfo
|
A tuple containing the schedule info (executor and metadata) and a list |
list[Module]
|
of local PyTorch modules created for this rank. |