Training Loop
About
The d9d.loop package provides the execution engine for distributed training. The Trainer separates the definition of a job (models, tasks, data) from its execution (synchronization, checkpointing, profiling). So the same code runs on a single GPU or on a large pipeline-parallel cluster without changes.
Configuration and Construction
You do not create a Trainer from loose objects. You build it with the TrainingConfigurator, using dependency injection.
The TrainingConfigurator combines:
- the infrastructure configuration,
- the job configuration,
- the user logic (providers).
It returns a Trainer that holds a prepared TrainJobState.
The Configuration Lifecycle
TrainingConfigurator.configure() runs these steps:
-
Distributed Context Initialization:
- Builds the global DistributedContext. This creates the NCCL process groups and
DeviceMeshobjects.
- Builds the global DistributedContext. This creates the NCCL process groups and
-
Seeding:
- Sets distributed seeds from the configured
base_seed. Model initialization and other initial states are then deterministic. More info.
- Sets distributed seeds from the configured
-
Task Instantiation:
- Creates the
TrainTaskwith theTrainTaskProvider.
- Creates the
-
Event Bus Initialization:
- Creates the global
EventBus. The model provider and the task use it to register custom hooks. - Triggers the
EVENT_TRAIN_CONFIG_STARTEDevent.
- Creates the global
-
Data Stream Construction:
- Calls the
DataProviderto build theMicrobatchPackStream. The stream yields one pack (the microbatches of one step) per iteration. - Triggers the
EVENT_TRAIN_DATA_STREAM_READYevent.
- Calls the
-
Model Materialization:
- The
ModelStageFactorybuilds each model stage:- Meta Init: The
ModelProvidercreates the model on themetadevice, so no memory is allocated. - Parallelization: The
ModelProvidershards or replicates the parameters asDTensor. - Materialization: Uninitialized tensors are allocated on the GPU.
- Parameter Reset:
model.reset_parameters()fills the weights with random values on the GPU. - Source Loading (Optional): If configured, a pretrained checkpoint (e.g. from Hugging Face) is streamed into the model through its
ModelStateMapper.
- Meta Init: The
- Triggers the
EVENT_TRAIN_MODEL_STAGES_READYevent.
- The
-
Optimizer and LR Scheduler Setup:
- The
OptimizerFactorycalls theOptimizerProviderand theLRSchedulerProvideronce per local model stage. - Triggers the
EVENT_TRAIN_OPTIMIZER_READYandEVENT_TRAIN_LR_SCHEDULER_READYevents.
- The
-
State Assembly:
- All components, including internal ones, are packed into the
TrainJobState. - The
Traineris created with this state and returned.
- All components, including internal ones, are packed into the
The Training Lifecycle
Trainer.train() runs the lifecycle below. Knowing this order helps when you debug distributed issues or look for side effects.
1. Initialization and Recovery
Before the loop starts:
- Global Synchronization: The trainer waits for all ranks (a barrier).
- State Loading: The
StateCheckpointerlooks for the latest checkpoint incheckpointing.save_dir/run.name(see Checkpointing).- If a checkpoint exists, it loads it into all
Statefulobjects of the job state. - If no checkpoint exists, the job starts from the first step.
- If a checkpoint exists, it loads it into all
- Context Entry: The trainer enters several context managers:
- UI: Shows a progress bar.
- Logging: Starts a new run in the experiment tracker and logs the run hyperparameters. More info.
- Garbage Collector: Disables automatic Python garbage collection.
- Profiler: Starts the
torch.profilerhooks. More info. - Gradient Manager: Installs the backward hooks that all-reduce gradients.
- Gradient Clipper: Groups the parameters for global gradient norm computation.
- Metric Collector: Binds the async metric collector to the device.
- Ready Hook Trigger: Triggers the
EVENT_TRAIN_READYevent. Its context holds theTrackerWriter, so event handlers can log their own values. More info.
2. The Step Loop
The loop runs until it reaches JobSchedule.total_steps (see Data Loading). For every step, the trainer runs these actions in this order:
- Triggers the
EVENT_TRAIN_STEP_PREevent. -
Microbatch Execution
- The
DevicePackStreamhands out a pack of \(N\) microbatches, already on the device (see Data Prefetching). - Triggers the
EVENT_TRAIN_FORWARD_BACKWARD_PREevent. - The
TrainTaskmaps each microbatch to model inputs. - The pipeline program runs the forward and backward passes over all microbatches of the pack. Without pipeline parallelism, the program has a single stage.
- Gradients accumulate locally. Between the forward and backward passes, the
TrainTaskcomputes the loss. - The last accumulation of each gradient bucket starts its all-reduce, which overlaps with the remaining backward work.
- The
TrainTaskupdates the local metrics (e.g. token counts, accuracy) for each microbatch. - Triggers the
EVENT_TRAIN_FORWARD_BACKWARD_POSTevent.
- The
-
Metric Synchronization
JobLoggerstarts an async reduction of all metrics across the ranks. More info.
-
Gradient Synchronization
- Wait & Scale: The
GradientManagerwaits for all backward hooks to finish. It then sums the loss weights across the ranks and divides all gradients by this total. This gives a correct average when the number of tokens varies, e.g. due to masking or packing. More info.
- Wait & Scale: The
-
Gradient Clipping
- The
GradientClippercomputes the global L2 norm of all gradients. - If
max_normis set, the gradients are clipped in place. - More info.
- The
-
Optimization
- Triggers the
EVENT_TRAIN_OPTIMIZER_STEP_PREevent. - Step: The optimizer updates the model parameters.
- Triggers the
EVENT_TRAIN_OPTIMIZER_STEP_POSTevent. - Schedule: The LR scheduler updates the learning rate for the next step.
- Triggers the
-
Logging & Maintenance
- Log:
JobLoggerlogs the loss and the gradient norm of the step and, on logging steps, the metrics. The values reach the tracker after the GPU has computed them, so the step does not wait for the GPU. - Zero Grad: The
GradientManagerclears the gradients for the next step. - GC:
ManualGarbageCollectorruns if the current step matches the GC period. - Event-Based Logic: Triggers the
EVENT_TRAIN_STEP_POSTevent. - Advance: The
JobScheduleincrements the step counter.
- Log:
-
Checkpointing
- If the step matches
checkpointing.period_stepsor is the last step, the trainer saves a checkpoint. This is a global barrier. See Checkpointing.
- If the step matches
3. Finalization
- Task-specific: The
TrainTaskruns itsfinalizemethod. - Event-specific: Triggers the
EVENT_TRAIN_FINISHEDevent.
Data Prefetching
While a step runs, a background thread prepares the next data_prefetch.prefetch_factor packs. It loads and pins them, then copies them to the device on a side CUDA stream. All of this overlaps with compute.
- Background thread: The data stream is iterated on this thread. With
num_workers: 0, this includes the dataset and collator code. - Pinned memory makes the copies asynchronous. Set
pin_memoryinAutoDataConfig, or wrap a custom stream inPinMemoryMicrobatchPackStream. Without prefetching, pinning only adds a host-side copy. - Memory: Every prefetched pack occupies device memory.
prefetch_factor: 0disables prefetching. - Checkpoints save the data position of the last completed step. Packs that were prefetched but not consumed are read again after a restart. A checkpoint can be loaded with any
prefetch_factor.
Sleep and Wake (Colocated RL)
In colocated RL, a rollout or inference engine shares the same GPUs as the Trainer. The two cannot occupy the device at once, so the Trainer must hand the GPU back for a while. Trainer.sleep() releases the training state to host memory and frees the device cache. Trainer.wake() restores it. Both are built on the State Offloading subsystem.
Both calls are collective: every rank must call them with the same tags. Call sleep() only between steps, e.g. from an EVENT_TRAIN_STEP_POST handler.
What Gets Offloaded
sleep() selects subsystems with a SleepTag. The default, SleepTag.TENSOR_STATES, offloads all GPU tensor state as a single unit:
- Model parameters and buffers (
TrackedModules), - Optimizer state (
PipelinedOptimizer), - Gradient buckets and the residual loss accumulator (
GradientManager).
Data packs are not offloaded. The pack of the finished step and up to prefetch_factor prefetched packs stay on the device.
SleepTag.COMMS (NCCL process groups) is reserved but not implemented yet. Requesting it raises NotImplementedError.
The round trip keeps object identity, optimizer state dict keys and DTensor metadata. So external references, e.g. a frozen reference model held by a task, stay valid after waking. See the round-trip invariant for details.
Order of Operations
sleep() runs, in order:
- Triggers
EVENT_TRAIN_SLEEP_PRE(the state is still on the GPU), then a barrier. - Offloads the gradient state, then the optimizer state, then the model parameters and buffers.
- A barrier, then triggers
EVENT_TRAIN_SLEEP_POST.
wake() restores in the reverse order (model, optimizer, gradient state). Barriers and the EVENT_TRAIN_WAKE_PRE / EVENT_TRAIN_WAKE_POST events surround it.
Usage
Build the trainer with a TrainingConfigurator, then call train():
API Reference
d9d.loop.run.TrainingConfigurator
Configurator that assembles the distributed training environment.
It combines the device mesh parameters, the TrainerConfig and the user-defined providers
into a Trainer that is ready to run the training loop.
__init__(mesh, parameters, task_provider, model_provider, data_provider, optimizer_provider, lr_scheduler_provider)
Constructs the TrainingConfigurator object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
DeviceMeshParameters
|
Definition of the distributed device mesh topology. |
required |
parameters
|
TrainerConfig
|
The global configuration object for the trainer. |
required |
task_provider
|
TrainTaskProvider
|
Factory for creating the training task logic. |
required |
model_provider
|
ModelProvider
|
Factory for defining and creating model stages. |
required |
data_provider
|
DataProvider
|
Factory for building the microbatch pack stream. |
required |
optimizer_provider
|
OptimizerProvider
|
Factory for creating the optimizer. |
required |
lr_scheduler_provider
|
LRSchedulerProvider
|
Factory for creating the learning rate scheduler. |
required |
configure()
Builds all training components and returns a configured Trainer.
It creates the distributed context, sets seeds and builds the model, the optimizer and the data stream. It also builds the auxiliary components (logging, profiling, checkpointing).
Returns:
| Type | Description |
|---|---|
Trainer
|
A ready-to-use trainer that holds the job state. |
d9d.loop.run.Trainer
The main execution engine for running a distributed training job.
This class manages the training loop, lifecycle events, distributed synchronization, and periodic side-effects (logging, checkpointing).
__init__(state)
Constructs the Trainer object from a pre-built job state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
TrainJobState
|
The encapsulated state object containing all initialized components (model, optimizer, dist_context, etc.). |
required |
export(export_to, load_checkpoint)
Exports the current model state to the specified directory.
The exported model can be loaded later, e.g. for inference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
export_to
|
Path
|
The directory to save the model files to. |
required |
load_checkpoint
|
bool
|
If |
required |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the model provider does not implement |
is_sleeping(tag)
sleep(tags=DEFAULT_SLEEP_TAGS)
Releases the GPU-resident training state selected by tags to host memory.
This frees the GPU for a colocated workload, such as a rollout engine in colocated RL. The call is collective: every rank must invoke it with identical tags. Requesting a tag whose subsystem is already offloaded is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tags
|
Iterable[SleepTag]
|
The subsystems to offload. Defaults to |
DEFAULT_SLEEP_TAGS
|
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If |
RuntimeError
|
If called during an in-flight gradient accumulation. |
train()
Executes the full training workflow.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the data stream ends before |
wake(tags=DEFAULT_SLEEP_TAGS)
Restores the training state released by sleep to the GPU.
The call is collective: every rank must invoke it with identical tags. Requesting a tag whose subsystem is not offloaded is a no-op.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tags
|
Iterable[SleepTag]
|
The subsystems to restore. Defaults to |
DEFAULT_SLEEP_TAGS
|
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If |