Pipeline Parallelism with AutoPipeline
Introduction
As large language models continue to grow in size, training and fine-tuning them efficiently across multiple GPUs has become increasingly challenging. While data parallelism works well for smaller models, models with billions of parameters require more sophisticated parallelization strategies to overcome memory constraints and communication overhead.
Pipeline parallelism addresses these challenges by splitting a model’s layers across different devices and processing them in a pipelined fashion. Each device processes a different stage of the model, enabling training of models that wouldn’t fit on a single device while maintaining high GPU utilization through overlapped computation.
AutoPipeline is NeMo AutoModel’s high-level pipeline parallelism interface for Hugging Face-compatible models. Built on PyTorch’s native torch.distributed.pipelining, it expects an HF-style model with a config, a supported decoder-layer layout, and pipeline-compatible forwards. Recipe-driven pipeline parallelism also requires the selected model implementation to advertise a _pp_plan (or use the supported MoE path) and pass the repository’s pipeline validation; encoder-decoder models and models with actually tied input/output embedding weights are rejected.
The functional module at nemo_automodel.components.distributed.pipelining.functional exposes the same HF-specific pipeline_model() splitter plus lower-level stage and schedule helpers. Custom PyTorch architectures can reuse helpers such as stage_ids_this_rank(), calculate_virtual_stages(), and build_pipeline_schedule() after their own code constructs valid PipelineStage objects; pipeline_model() is not a generic nn.Module splitter.
This guide walks you through AutoPipeline for compatible Hugging Face models and the lower-level helpers for manually constructed stages. You’ll learn how to configure pipeline stages, integrate with existing training workflows, optimize performance, and combine pipeline parallelism with other parallelization strategies.
Prerequisites:
Before proceeding with this guide, please ensure that you have NeMo AutoModel installed on your machine. For a complete guide and additional options please consult the AutoModel Installation Guide.
Key Features
AutoPipeline provides the following capabilities:
- Hugging Face-Compatible Model Support: Works with decoder-only causal language models and explicitly supported VLMs that satisfy the pipeline model contract
- PyTorch Native Integration: Built on PyTorch’s
torch.distributed.pipeliningfor optimal performance - Flexible Configuration: Multiple scheduling strategies, configurable microbatch sizes, and automatic or manual layer splitting
- Mixed Parallelism Support: Combine pipeline parallelism with data parallelism, tensor parallelism, and FSDP
- Modular Functional API: Lower-level stage and schedule helpers can be reused with manually constructed
PipelineStageobjects - Minimal Opinions: Easy to extend and integrate with existing training workflows
Quick Start with AutoPipeline (Hugging Face Models)
Here’s a minimal example to get started with AutoPipeline using 2 pipeline stages with a Hugging Face model:
Run the Quick Start Example
Save the above code as pipeline_example.py and run with:
For a complete training example:
Configuration Options
Basic Configuration
AutoPipeline provides comprehensive control over pipeline behavior:
Model Patching (patch_inner_model, patch_causal_lm_model)
AutoPipeline splits a model by deep-copying it per stage and pruning away modules that don’t belong to that stage. Many Hugging Face models assume the full module tree is present and return ModelOutput objects; after pruning, their original forward() often breaks (or returns objects that are awkward to pipeline).
These two flags switch AutoPipeline to lightweight, pipeline-friendly forward() implementations that return tensors (see nemo_automodel.components.distributed.pipelining.hf_utils.patch_hf_model_for_pp):
-
patch_inner_model: patches the decoder module (model.modelfor...ForCausalLM, otherwise the module itself) so each stage can run even after pruning.- Stage 0 (has
embed_tokens): takes token IDs and produces hidden states. - Middle stages (no
embed_tokens): take hidden states from the previous stage (usinginputs_embeds, or a float tensor passed throughinput_ids) and produce hidden states. - Handles sliced layer containers (e.g.,
layersbecoming dict-like after stage pruning) and returns a tensor of hidden states so stages can be chained.
For compilation/performance, this patched forward prefers a precomputed
causal_mask_mappingdict (it will fall back to computing masks and warn if you don’t provide it). - Stage 0 (has
-
patch_causal_lm_model: patches the...ForCausalLMwrapper forward (the module that ownslm_head) so pipeline stages return tensors:- Returns hidden states when
lm_headis absent on that stage. - Returns logits when
lm_headis present (typically only the last stage). - Supports
logits_to_keepto compute logits for only the lastktokens.
Note: this is only used when the module you pipeline is a
...ForCausalLM-style wrapper (i.e., it has a.modelattribute). If you pass a base decoder module directly,patch_causal_lm_modeltypically has no effect. - Returns hidden states when
When Should I Change These?
- Leave both
True(default) for standard Hugging FaceAutoModelForCausalLM/...ForCausalLMmodels. This is the common case and gives the expected behavior: token IDs -> hidden states -> logits across stages. - Set both
Falsewhen your model already has a pipeline-friendly forward (returns tensors and can accept hidden states when embeddings are absent) or it needs custom kwargs/paths that the HF patch doesn’t preserve (common for NeMo AutoModel-native model implementations, packed-sequence/thdpaths, extra args likepadding_mask, etc.). Many benchmark configs for NeMo-native models do this (for exampleexamples/llm_benchmark/qwen/qwen3_moe_30b_torch.yaml). - Set
patch_inner_model=False, patch_causal_lm_model=Truewhen your inner model is already stage-friendly, but the wrapper forward still returns aModelOutputand you only want the wrapper simplified to “hidden states or logits”.
If you disable patch_causal_lm_model, your last stage will typically output hidden states instead of logits; in that case, make sure your loss_fn (or your last-stage module) applies the LM head explicitly.
Automatic vs. Manual Layer Distribution
AutoPipeline offers flexible control over how your model is split across pipeline stages:
Automatic Distribution
Let AutoPipeline automatically balance layers across stages:
Manual Distribution
Specify exactly which modules go to each stage:
Understand Model Splitting
When AutoPipeline splits your model, it intelligently distributes components across pipeline stages. Here’s how a typical model gets split:
Example: 32-Layer Model Across 2 Stages
Example: 32-Layer Model Across 4 Stages
Key observations:
- Embeddings only exist on the first stage
- Language modeling head only exists on the last stage
- Rotary embeddings are shared across all stages (for position encoding)
- Transformer layers are evenly distributed
Use Lower-Level Functional Helpers
AutoPipeline and the functional pipeline_model() convenience path both use Hugging Face-specific model splitting. For a custom architecture, construct the stage modules and PyTorch PipelineStage objects in model-owned code, then reuse the lower-level scheduling helpers. The examples below illustrate those lower-level pieces; they do not make pipeline_model() a generic model splitter.
Key Functional API Components
The functional API provides several utilities for building custom pipeline parallel systems:
Stage ID Calculation
Module Name Generation
Virtual Stage Calculation
Pipeline Schedule Build
Pseudocode: Pipeline Parallelism for Custom Models
The following fence is intentionally non-runnable pseudocode. A real custom model must provide model-owned
create_stage_model splitting, a dataloader, pipeline-compatible stage input/output contracts, and process-group
setup before reusing these scheduling helpers. It is an architectural outline, not a script to run unchanged.
Add a Parallelization Callback to HF-Compatible Splitting
When using the HF-compatible pipeline_model() path, a callback can apply additional parallelism to each model part. Its signature must match ParallelizeFnProtocol:
Tips for Manually Staged Custom Models
The lower-level helpers can support a custom model after its model-owned splitting code has created the stage modules:
- Module Naming: Ensure your model has consistent module naming that can be mapped to stages
- State Management: Handle model state (embeddings, buffers) carefully across stages
- Communication: First and last stages need special handling for inputs/outputs
- Splitting Ownership: Keep custom model splitting in the model implementation; do not pass an arbitrary
nn.Moduletopipeline_model() - Testing: Start with a small model and verify correct splitting before scaling up
The schedule helpers are reusable, but the repository’s automatic splitting path retains Hugging Face-specific assumptions.
Mixed Parallelism
AutoPipeline can be combined with other parallelization strategies for optimal performance:
Monitor and Debug
AutoPipeline provides comprehensive tools for understanding your pipeline configuration:
Pipeline Information
Analysis
Gradient Management
Add Pipeline Parallelism to Supported Configurations
Pipeline parallelism can be added to an FSDP2 training configuration only when the model satisfies the repository’s PP
contract and the PP, TP, CP, and inferred DP sizes compose to the total worker count. DDP and Megatron FSDP do not
support PP. For recipe-driven model implementations, use a model with a _pp_plan or the explicitly supported MoE path.
Command-Line Override Method
For an FSDP2 config whose model supports PP, use command-line overrides such as:
Key parameters to override:
--distributed.pp_size: Number of pipeline ranks; PP, TP, CP, and inferred DP sizes must compose to the total worker count--step_scheduler.local_batch_size: Runtime batch size used for both the pipeline schedule and dataloader--distributed.pipeline.pp_schedule: Pipeline schedule (1f1b,interleaved1f1b,LoopedBFS, etc.)
YAML Configuration Method
Add these sections to a compatible FSDP2 YAML config:
Mixed Parallelism Examples
Pipeline + Data Parallelism (4 GPUs Total)
Pipeline + Tensor Parallelism (4 GPUs Total)
Full Hybrid: PP + DP + TP (8 GPUs Total)
Integrate with Training Recipes
AutoPipeline seamlessly integrates with NeMo AutoModel’s recipe system. Here’s a complete example YAML configuration:
Run training with:
Troubleshooting
Common Issues
Model doesn’t fit in memory:
- Increase number of pipeline stages
- Reduce microbatch size
- Enable gradient checkpointing
Pipeline bubbles reducing efficiency:
- Increase
step_scheduler.local_batch_sizeto have more microbatches - Try different schedules (e.g.,
interleaved1f1b) - Adjust virtual stages configuration
Uneven stage distribution:
- Use manual module assignment for fine control
- Adjust
layers_per_stageparameter - Check parameter counts with
get_stage_param_counts()
Conclusion
AutoPipeline and pipeline_model() provide the repository’s HF-compatible splitting path, while lower-level functional helpers can be reused after custom code constructs its own pipeline stages.
Key takeaways:
- Pipeline parallelism enables training of models too large for a single GPU
- AutoPipeline provides a simple API for Hugging Face-compatible models with powerful customization options
- Lower-level functional helpers can schedule manually constructed stages for custom models
- Both can be combined with other parallelization strategies for optimal performance
- Use built-in monitoring tools to understand and optimize your pipeline