Model State I/O
About
The d9d.model_state.io package reads and writes model checkpoints. It is built on the d9d.model_state.mapper framework, so it can transform model states on the fly while it streams them. It does not load the whole checkpoint into memory at once.
Checkpoint Format
d9d uses the Hugging Face checkpoint format. Parameter tensors live in sharded model-00001-of-XXXXX.safetensors files. The model.safetensors.index.json file maps every tensor to its file. The reader also accepts a single unindexed model.safetensors file.
Why I/O Supports Transformations
In d9d, reading and writing model states always goes through a mapper. This decouples the checkpoint architecture (how weights are stored on disk) from the model architecture (how weights are used in PyTorch code).
This matters for:
- Hugging Face compatibility: Your model can use a single packed
qkv_projtensor and still read community checkpoints that storeq_proj,k_projandv_projseparately. The mapper stacks them while it reads. You do not need conversion scripts or converted copies of large models. - Runtime structure changes, e.g. LoRA: Adapters such as LoRA wrap the original layers. A
some_linear.weightkey on disk then needs to load intosome_linear.base.weight. The mapper reroutes these keys without loading the full state dict first.
How Loading Works
Standard loading reads the whole state dict into CPU RAM, processes it and moves the results to the GPU. This uses a lot of memory and CPU-GPU transfers, and every pipeline-parallel rank repeats the same work.
d9d loads differently:
- Streaming: A tensor stays in memory only until its mapper group runs. For example, once the "stack q, k, v" group runs, d9d frees the source tensors.
- Selective reading: The reader inspects the
ModelStateMapperand reads only the files that contain its inputs.
How Saving Works
Standard saving often gathers all parameters on a single rank, which can run out of memory. It can also require manual handling of file names and indices across many GPUs.
d9d saves differently:
- Streaming: A source tensor stays in memory only until its mapper group runs. Output tensors stay in memory only until they are flushed to a
.safetensorsfile. - Distributed writing: Besides local saving, the writer accepts a
ProcessGroupor aDeviceMesh. With pipeline parallelism, only one rank per pipeline stage writes, so every parameter is written exactly once.
Usage
These examples load and save model states without transforming them. For complex mappings, see the Model State Mapper page.
Raw I/O: Streamed Loading
This example reads a checkpoint tensor by tensor.
Raw I/O: Streamed Saving
This example saves a model locally in shards of at most 1 GiB.
Raw I/O: Distributed Checkpoint Conversion
You can convert a checkpoint offline on a cluster. For example, you can convert a Hugging Face checkpoint into a format with packed qkv tensors. You do not need a single machine with enough RAM for the whole model. Each of N GPUs processes 1/N of the keys and writes its part of a new sharded checkpoint.
PyTorch Module I/O: Streamed Loading
This example loads a checkpoint whose keys match the model keys. identity_mapper_from_module makes sure that only the model's own states are loaded.
PyTorch Module I/O: Streamed Saving with a DeviceMesh
save_model_state_pipeline_parallel saves a model that is distributed over a PyTorch DeviceMesh with any parallelism dimensions:
- DTensor gathering: It gathers
DTensorshards into full tensors before writing. - One writer per pipeline stage: With data or tensor parallelism, several GPUs hold copies or shards of the same parameters. Only the rank at coordinate 0 of every non-pipeline dimension writes to disk.
- Index merging: Each pipeline rank writes its own files. Pipeline rank 0 then merges their indices into one global index file.
Every rank must call the function, because the gathering is collective.
API Reference
d9d.model_state.io
Streaming readers and writers for model checkpoints.
load_model_state(src_dir, mapper, device, model, show_progress=True, position=None)
Streams a checkpoint from disk into a PyTorch module.
- Mapping:
mapperrenames, stacks or reshapes on-disk states into model states. - Distribution: If a model state is a
DTensor, the loaded tensor is distributed to match its device mesh and placements. - Injection: Each transformed state is loaded into
modelwithload_state_dictas soon as it is ready.
Only states listed in the outputs of mapper are loaded. To load every model state unchanged, build the
mapper with d9d.model_state.mapper.adapters.identity_mapper_from_module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src_dir
|
Path
|
Directory containing the checkpoint: either sharded |
required |
mapper
|
ModelStateMapper
|
The mapper from on-disk keys to model keys. |
required |
device
|
str
|
The device to load tensors onto, e.g. |
required |
model
|
Module
|
The model instance to load weights into. |
required |
show_progress
|
bool
|
Whether to display the loading progress bar. |
True
|
position
|
int | None
|
Row index for the tqdm bar. Pass the process local rank to stack one bar
per rank without interleaving. |
None
|
read_model_state(src_dir, mapper, device, show_progress=True, position=None)
Reads a model checkpoint from disk and transforms it on the fly with a state mapper.
Only files that hold inputs of mapper are read. A tensor stays in memory only until the mapper has
processed its group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src_dir
|
Path
|
The directory containing the checkpoint: either sharded |
required |
mapper
|
ModelStateMapper
|
The mapper from on-disk keys to output keys. |
required |
device
|
str
|
The device to load tensors onto, e.g. |
required |
show_progress
|
bool
|
Whether to display a progress bar. |
True
|
position
|
int | None
|
Row index for the tqdm bar. Pass the process local rank to stack one bar
per rank without interleaving. |
None
|
Yields:
| Type | Description |
|---|---|
Iterable[tuple[str, Tensor]]
|
Pairs of transformed state name and tensor. |
save_model_state(dest_dir, mapper, model, shard_size_gb=4.0, show_progress=True)
Saves a PyTorch module to disk from a single process.
DTensor states are gathered into full tensors before saving.
Only states listed in the inputs of mapper are saved. To save every model state unchanged, build the
mapper with d9d.model_state.mapper.adapters.identity_mapper_from_module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dest_dir
|
Path
|
The directory to save |
required |
mapper
|
ModelStateMapper
|
The mapper from model keys to on-disk keys. |
required |
model
|
Module
|
The PyTorch module to save. |
required |
shard_size_gb
|
float
|
Maximum size of a shard file in GiB. |
4.0
|
show_progress
|
bool
|
Whether to display a progress bar. |
True
|
save_model_state_pipeline_parallel(dest_dir, mapper, device_mesh, pipeline_dim_name, models, shard_size_gb=4.0, show_progress=True, position=None)
Saves a pipeline-parallel model to disk.
Every rank must call this function.
- Gathering:
DTensorstates are gathered into full tensors before saving. - Single writer: For each pipeline stage, only one rank writes files, so ranks do not overwrite each other.
- Index merging: The indices of all pipeline stages are merged into one global index file.
Only states listed in the inputs of mapper are saved. To save every model state unchanged, build the
mapper with d9d.model_state.mapper.adapters.identity_mapper_from_module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dest_dir
|
Path
|
The directory to save |
required |
mapper
|
ModelStateMapper
|
The mapper from model keys to on-disk keys. |
required |
device_mesh
|
DeviceMesh
|
The device mesh of the whole job. |
required |
pipeline_dim_name
|
str
|
The name of the pipeline-parallel dimension in |
required |
models
|
list[Module]
|
The modules (pipeline stages) held by this rank. |
required |
shard_size_gb
|
float
|
Maximum size of a shard file in GiB. |
4.0
|
show_progress
|
bool
|
Whether to display a progress bar. |
True
|
position
|
int | None
|
Row index for the tqdm bar. Pass the process local rank to stack one bar
per rank without interleaving. |
None
|
write_model_state_distributed(dest_dir, mapper, state_generator, process_group, shard_size_gb=4.0, show_progress=True, position=None)
Saves model states from multiple processes.
A source tensor stays in memory only until all inputs of its mapper group arrive and the group is processed.
Every rank in process_group must call this function and writes its own shards. Rank 0 gathers the
indices and finalizes the checkpoint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dest_dir
|
Path
|
Destination directory. |
required |
mapper
|
ModelStateMapper
|
Mapping to apply to states before saving. |
required |
state_generator
|
Iterable[tuple[str, Tensor]]
|
Stream of |
required |
process_group
|
ProcessGroup
|
The process group of all writing ranks. |
required |
shard_size_gb
|
float
|
Maximum shard size in GiB. |
4.0
|
show_progress
|
bool
|
Whether to show the progress bar. |
True
|
position
|
int | None
|
Row index for the tqdm bar. Pass the process local rank to stack one bar
per rank without interleaving. |
None
|
write_model_state_local(dest_dir, mapper, state_generator, shard_size_gb=4.0, show_progress=True)
Saves model states to disk in a single local process.
A source tensor stays in memory only until all inputs of its mapper group arrive and the group is processed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dest_dir
|
Path
|
Destination directory. |
required |
mapper
|
ModelStateMapper
|
Mapping to apply to states before saving. |
required |
state_generator
|
Iterable[tuple[str, Tensor]]
|
Stream of |
required |
shard_size_gb
|
float
|
Maximum size of a single |
4.0
|
show_progress
|
bool
|
Whether to show the progress bar. |
True
|
write_model_state_pipeline_parallel(dest_dir, mapper, state_generator, device_mesh, pipeline_dim_name, shard_size_gb=4.0, show_progress=True, position=None)
Saves model states from a pipeline-parallel setup with any other mesh dimensions.
A source tensor stays in memory only until all inputs of its mapper group arrive and the group is processed.
Every rank in device_mesh must call this function. Only one rank per pipeline stage writes to disk,
so no data is written twice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dest_dir
|
Path
|
Destination directory. |
required |
mapper
|
ModelStateMapper
|
Mapping to apply to states before saving. |
required |
state_generator
|
Iterable[tuple[str, Tensor]]
|
Stream of |
required |
device_mesh
|
DeviceMesh
|
The device mesh of the whole job. |
required |
pipeline_dim_name
|
str
|
The name of the pipeline-parallel dimension in |
required |
shard_size_gb
|
float
|
Maximum shard size in GiB. |
4.0
|
show_progress
|
bool
|
Whether to show the progress bar. |
True
|
position
|
int | None
|
Row index for the tqdm bar. Pass the process local rank to stack one bar
per rank without interleaving. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |