State Offloading
About
The d9d.core.offload package provides the low-level machinery for releasing GPU-resident training state to host (CPU) memory and restoring it later. It is the foundation of the Trainer's sleep / wake API, which frees the accelerator for a colocated workload - most notably a rollout engine sharing the same GPUs in colocated reinforcement learning.
The package defines two things:
- The
Offloadableprotocol - the contract a subsystem implements to declare that it owns GPU memory and knows how to release and reacquire it. - The tensor-swap primitives -
offload_tensorandonload_tensor, which move a single tensor's storage to and from the host while preserving the tensor (andDTensorwrapper) object.
The high-level user entry points - Trainer.sleep(), Trainer.wake() and Trainer.is_sleeping() - are documented in the Training Loop page. This page covers the primitives those methods are built on.
The Round-Trip Invariant
The central guarantee of this subsystem is that an offload followed by an onload is observationally a no-op. Across the round trip:
- Parameter and buffer object identity is preserved.
- Optimizer state-dict keys and the tensor objects they map to are preserved.
DTensorwrapper instances, theirdevice_mesh,placements, globalshape,strideanddtypeare preserved.
Only the underlying device storage is reallocated. This is what makes offloading safe in the presence of external references - gradient hooks, optimizer state keyed by parameter, or a frozen reference model held by a task all keep pointing at the same objects after waking up.
The trick is that the swap rebinds storage in place rather than creating new tensors:
- For a plain tensor,
tensor.datais rebound to a host (then back to a device) buffer. - For a
DTensor, only the local shard's storage (_local_tensor.data) is rebound. All distributed metadata lives on the wrapper and never leaves it, so any code still holding theDTensorsees the same object with its placements intact.
Usage
offload_tensor / onload_tensor operate on a single tensor and return an OffloadedTensor handle that you hold between the two calls. The same tensor object is passed to both.
DTensor is handled transparently - pass the wrapper and only its local shard is swapped:
Implementing Offloadable
Subsystems that own GPU state implement the protocol so the Trainer can fan offload/onload out to them as a unit. The pattern is to record the handles in a mirror, drain the asynchronous copies with torch.cuda.synchronize, and guard against double offload/onload.
The built-in Offloadable implementations - TrackedModules (model parameters and buffers), PipelinedOptimizer (optimizer state) and GradientManager (gradient buckets and the residual loss accumulator) - all follow this shape.
Sleep Tags
Offloading is selected by SleepTag, a subsystem selector shared by Trainer.sleep and Trainer.wake:
SleepTag.TENSOR_STATES- all GPU tensor state (model parameters and buffers, optimizer state, gradient buckets and the residual loss accumulator). Offloaded as a single unit. This is the only tag enabled byDEFAULT_SLEEP_TAGS.SleepTag.COMMS- NCCL process groups. Opt-in and not yet implemented; requesting it raisesNotImplementedError.
API Reference
d9d.core.offload
Offloading of GPU-resident training state to host memory and back.
DEFAULT_SLEEP_TAGS = frozenset({SleepTag.TENSOR_STATES})
module-attribute
The default tag set for Trainer.sleep and Trainer.wake: tensor state only, no comms.
OffloadContext
dataclass
Context passed to Offloadable.offload.
Attributes:
| Name | Type | Description |
|---|---|---|
dist_context |
DistributedContext
|
The distributed context the subsystem was built under. |
pin_memory |
bool
|
Whether to allocate the host buffer in pinned memory. |
Offloadable
Bases: Protocol
Protocol for subsystems that own GPU-resident state and can release it to host memory.
An "offload" followed by an "onload" must be observationally a no-op: parameter identities, optimizer state keys, DTensor wrapper instances, placements and dtypes are all preserved across the round trip. Only the underlying device storages are reallocated.
is_offloaded()
Reports whether this subsystem currently has its state on host memory.
Returns:
| Type | Description |
|---|---|
bool
|
True if the subsystem is offloaded, False otherwise. |
offload(ctx)
Releases the GPU memory owned by this subsystem, moving its state to host memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
OffloadContext
|
Context for this operation. |
required |
onload(ctx)
Restores GPU residency of the state previously released by "offload".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
OnloadContext
|
Context for this operation. |
required |
OffloadedTensor
dataclass
Handle to a tensor whose local storage has been swapped to host memory.
Produced by "offload_tensor" and consumed by "onload_tensor". Subsystems hold these as opaque handles between an offload and the matching onload.
Attributes:
| Name | Type | Description |
|---|---|---|
host |
Tensor
|
The host-memory mirror that currently backs the offloaded tensor's local storage. |
OnloadContext
dataclass
Context passed to Offloadable.onload.
Attributes:
| Name | Type | Description |
|---|---|---|
dist_context |
DistributedContext
|
The distributed context the subsystem was built under. |
SleepTag
Bases: StrEnum
Subsystem selector for Trainer.sleep and Trainer.wake.
Attributes:
| Name | Type | Description |
|---|---|---|
TENSOR_STATES |
All GPU tensor state - model parameters and buffers, optimizer state, gradient buckets and the residual loss accumulator. Always offloaded as a unit. |
|
COMMS |
NCCL process groups. Opt-in; its implementation is deferred to a second phase, so requesting it currently raises NotImplementedError. |
offload_tensor(tensor, *, pin_memory)
Swaps "tensor"'s local storage for a host-memory mirror, in place.
For a DTensor, the wrapper instance is preserved across the swap - only the local shard's underlying storage is rebound, via "_local_tensor.data". "device_mesh", "placements", global "shape" and global "stride" live on the wrapper and never leave it, so any external reference to the DTensor keeps pointing at the same object. For a plain tensor, "tensor.data" itself is rebound.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tensor
|
Tensor
|
The device tensor to offload. May be a plain tensor or a DTensor. |
required |
pin_memory
|
bool
|
Whether to allocate the host buffer in pinned memory. |
required |
Returns:
| Type | Description |
|---|---|
OffloadedTensor
|
A handle holding the host buffer; pass back to "onload_tensor" with the same tensor. |
onload_tensor(tensor, offloaded, *, device)
Restores "tensor"'s local storage to "device", in place, from a host buffer.
The tensor object (and, for a DTensor, its wrapper) is the same instance as before the offload; only the underlying device storage is freshly allocated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tensor
|
Tensor
|
The same tensor previously passed to "offload_tensor". |
required |
offloaded
|
OffloadedTensor
|
The handle returned by "offload_tensor". |
required |
device
|
device
|
The accelerator device to restore the local storage onto. |
required |