Spaces:
Running on L40S
Custom Datasets for Generator and Reasoner Training
This guide explains how to bring your own dataset into Cosmos training using
DataPackerDataLoader and JointDataPackerDataLoader β the OSS-facing data
layer that works without any internal infrastructure (no WebDataset, no
object-store credentials).
Contents
Overview
The data pipeline has two parts you control:
Your dataset (Dataset or IterableDataset)
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β DataPackerDataLoader β
β β
β map-style Dataset (any shuffle setting): β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β _ShuffledMapIterableDataset β β
β β β’ per-epoch randperm (shuffle=True) β β
β β or sequential (shuffle=False) β β
β β β’ DP Γ worker sharding β β
β β β’ position metadata for stateful resume β β
β ββββββββββββββββββββ¬ββββββββββββββββββββββββ β
β β β
β IterableDataset: β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β _IterableWrapper β β
β β β’ DP Γ worker sharding only β β
β β β’ no stateful resume β β
β ββββββββββββββββββββ¬ββββββββββββββββββββββββ β
β β raw item β
β ββββββββββββββββββββΌββββββββββββββββββββββββ β
β β _DataPackerIterableDataset β β
β β (subclass of PackingIterableDataset) β β
β β β β
β β β’ fill pool (pool_size samples) β β
β β β’ greedy bin-pack within max_tokens β β
β β β’ cap at max_batch_size β β
β β β β
β β β DataPacker.sft_process_sample β you β β
β β β DataPacker.compute_num_tokens β you β β
β β β DataPacker.sft_collate_fn β you β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β fully-collated batch dict
βΌ
Trainer / model.forward()
Key point: all map-style datasets (whether shuffle=True or shuffle=False)
are routed through _ShuffledMapIterableDataset, which attaches position
metadata to every sample. This means stateful checkpoint/resume works regardless
of whether shuffle is enabled.
DataPackerDataLoader
Step 1 β Prepare your data source
DataPackerDataLoader accepts either a map-style torch.utils.data.Dataset
or an iterable-style torch.utils.data.IterableDataset. Plain lists and
generators are rejected with a TypeError.
| Type | Notes |
|---|---|
torch.utils.data.Dataset (map-style) |
Pass directly. Supports shuffle=True/False and stateful checkpoint/resume. |
torch.utils.data.IterableDataset |
Pass directly. No shuffle, no stateful resume β shuffle externally if needed. |
HuggingFace Dataset |
Is a torch.utils.data.Dataset subclass β pass directly, shuffle=True works. |
HuggingFace IterableDataset (streaming) |
Is a torch.utils.data.IterableDataset β pass directly, use .shuffle() externally. |
Loading from HuggingFace (simplest)
from cosmos_framework.data.vfm.data_packer_dataloader import load_data_source
# HuggingFace Hub dataset (downloaded, map-style)
data_source = load_data_source("liuhaotian/LLaVA-Instruct-150K", split="train")
# Dataset saved with dataset.save_to_disk()
data_source = load_data_source("/path/to/my_saved_dataset", split="train")
# Then pass with shuffle for per-epoch shuffling + stateful resume
DataPackerDataLoader(data_source=data_source, ..., shuffle=True, seed=42)
Streaming from HuggingFace (no disk space)
from datasets import load_dataset
data_source = load_dataset(
"lmms-lab/LLaVA-OneVision-Data", name="si", split="train", streaming=True
)
# shuffle before passing β IterableDataset does not support internal shuffle
data_source = data_source.shuffle(seed=42, buffer_size=10_000)
Custom map-style dataset
class MyMapDataset(torch.utils.data.Dataset):
def __len__(self): return 10_000
def __getitem__(self, idx): return {"video": ..., "text": ...}
# Pass directly β DataPackerDataLoader handles sharding and shuffle internally
DataPackerDataLoader(data_source=MyMapDataset(), ..., shuffle=True, seed=42)
Step 2 β Write your DataPacker
DataPacker is an abstract base class. Implement all three methods, then place
the class in the same experiment config file that uses it.
from cosmos_framework.data.vfm.data_packer import DataPacker
class MyDataPacker(DataPacker):
def sft_process_sample(self, item: dict) -> dict:
"""
Convert one raw item from data_source into a training-ready sample.
Called inside DataLoader workers β tokenization, decoding, transforms go here.
"""
...
return {"input_ids": ..., "labels": ..., ...}
def compute_num_tokens(self, sample: dict) -> int:
"""
Return the token cost of one processed sample.
Used by the packing engine to decide how many samples fit in a batch.
"""
return int(sample["input_ids"].shape[0])
def sft_collate_fn(self, samples: list[dict], max_len: int,
ignore_label_id: int = -100) -> dict:
"""
Collate a list of processed samples into one batch dict.
max_len is the longest token sequence in this batch (for padding).
"""
...
return {"input_ids": ..., "labels": ..., ...}
Note on extra batch keys: For map-style datasets,
DataPackerDataLoaderautomatically appendssample_worker_id,sample_epoch, andsample_indexto every batch dict. These are used byDataLoaderStateCallbackfor stateful checkpoint/resume and are transparent to the model as long astraining_stepaccesses the batch by key (not**kwargsunpack).
Token counting for Generator models
import math
def compute_num_tokens(self, sample: dict) -> int:
tokens = 1 + len(sample.get("text_token_ids", []))
v = sample.get("video") # shape [C, T, H, W]
if v is not None:
_, T, H, W = v.shape
latent_h = math.ceil(H / (self.spatial_compression * self.patch_spatial))
latent_w = math.ceil(W / (self.spatial_compression * self.patch_spatial))
latent_t = 1 + (T - 1) // self.temporal_compression
tokens += latent_h * latent_w * latent_t + 2
return tokens
Typical values: spatial_compression=16, temporal_compression=4, patch_spatial=2.
Step 3 β Wire everything into an experiment config
from cosmos_framework.utils.lazy_config import LazyCall as L, LazyDict
from cosmos_framework.data.vfm.data_packer_dataloader import DataPackerDataLoader, load_data_source
from cosmos_framework.callbacks.dataloader_state import DataLoaderStateCallback
from hydra.core.config_store import ConfigStore
cs = ConfigStore.instance()
my_experiment = LazyDict(dict(
defaults=[...], # inherit model, optimizer, scheduler from a base
trainer=dict(
callbacks=dict(
# Tracks per-worker (epoch, position) for checkpoint/resume.
# Works with both shuffle=True and shuffle=False for map-style datasets.
dataloader_state=L(DataLoaderStateCallback)(distributor_type="data_packer"),
),
),
dataloader_train=L(DataPackerDataLoader)(
data_source=L(load_data_source)(name="my-org/my-dataset", split="train"),
data_packer=L(MyDataPacker)(...),
max_tokens=16000,
pool_size=16,
max_batch_size=1,
shuffle=True, # per-epoch randperm, different order every epoch
seed=42, # epoch e uses seed+e β reproducible permutations
num_workers=4,
prefetch_factor=4,
persistent_workers=True,
pin_memory=True,
),
dataloader_val=None,
), flags={"allow_objects": True})
cs.store(group="experiment", package="_global_", name="my_experiment", node=my_experiment)
Launch:
torchrun --nproc_per_node=8 -m cosmos_framework.scripts.train \
--config=cosmos_framework/configs/base/config.py -- \
experiment=my_experiment \
trainer.max_iter=1000
Key parameters
| Parameter | What it controls |
|---|---|
max_tokens |
Token budget per batch. Packing stops when adding one more sample would exceed this. For Generator, counts video latent tokens; for Reasoner, counts input_ids length. |
pool_size |
Samples to buffer before bin-packing. Larger pool β better packing efficiency, more memory. Default: 16. |
max_batch_size |
Hard cap on samples per batch regardless of token budget. Use 1 for Reasoner (one image per step), 128β256 for action policy training. |
shuffle |
True β per-epoch randperm shuffle for map-style datasets (no effect on IterableDataset, a warning is logged). False β sequential, still resumable. |
seed |
Base seed for the shuffle permutation. Epoch e uses seed + e β reproducible, different ordering every epoch. Default: 0. |
name |
Optional string that namespaces resume env vars. Required when multiple DataPackerDataLoader instances share the same process (i.e., inside JointDataPackerDataLoader). Each inner loader must have a unique name matching its key in the dataloaders dict. Leave empty (default) for single-loader setups. |
long_threshold |
Samples with token count β₯ this are emitted as singleton batches, bypassing packing. Default: 6400. |
batching_strategy |
"prefer_closest" (default) picks candidates nearest in token length. "prefer_first" picks the first that fits. |
num_workers |
DataLoader workers for sft_process_sample. Use 0 for debugging. |
persistent_workers |
Automatically promoted to True for all map-style datasets when num_workers > 0 (required for correct resume behaviour). |
Shuffle and stateful checkpoint/resume
For map-style datasets, DataPackerDataLoader tracks each worker's position and
resumes training from exactly where it left off after a checkpoint. This works
for both shuffle=True and shuffle=False.
How it works
- Each epoch, a permutation is generated with
torch.randperm(n, generator=torch.Generator().manual_seed(seed + epoch))(orlist(range(n))whenshuffle=False). - Each
(dp_rank, worker_id)pair sees a disjoint stride:perm[stream_id :: total_streams]wherestream_id = dp_rank * num_workers + worker_id. - After each training step,
DataLoaderStateCallbackreadssample_epochandsample_indexfrom the batch and tracks the high-water mark per worker. - At checkpoint, the DCP checkpointer saves the state to
iter_XXXXXXXXX/dataloader/rank_{rank}.pkl. - On resume,
load_state_dictsetsDP_STATE_WORKER_{worker_id}_EPOCH/INDEXenv vars before workers start, and workers fast-forward past already-seen samples.
At most pool_size (default 16) samples are re-processed at each resume (they pass through sft_process_sample again but are trained on only once).
Required wiring
from cosmos_framework.callbacks.dataloader_state import DataLoaderStateCallback
exp["trainer"]["callbacks"]["dataloader_state"] = L(DataLoaderStateCallback)(
distributor_type="data_packer"
)
Use ckpt_type=dcp (the default) β not ckpt_type=dummy which disables all checkpointing.
Limitations
- Map-style datasets only. Stateful resume is not supported for
IterableDatasetsources. forkstart method required (the default for Linux/CUDA).spawnis not supported.persistent_workers=Truerequired whennum_workers > 0(auto-enforced for all map-style datasets).
Data-parallel sharding
DataPackerDataLoader automatically shards data_source across ranks and
DataLoader workers. Each (dp_rank, worker_id) pair receives a disjoint subset β
a strided slice of the (shuffled) permutation.
If your dataset already shards internally (like SFTDataset), disable its
sharding before passing it to DataPackerDataLoader:
def get_my_dataset_no_dp(**kwargs):
dataset = MyDataset(**kwargs)
dataset.shard_world_size = 1 # disable internal sharding
dataset.shard_rank = 0
return dataset
For FSDP + TP/PP: pass parallel_dims so the correct DP rank is used
(global rank β DP rank in these setups):
DataPackerDataLoader(..., parallel_dims=parallel_dims)
JointDataPackerDataLoader
When to use it
JointDataPackerDataLoader wraps multiple DataPackerDataLoader instances
with ratio-based seeded selection. Use it when training on multiple datasets with
different modalities or formats β for example, video + action data at a 3:1 ratio.
Semantics mirror IterativeJointDataLoader:
- One batch = one dataset β samples from different datasets never share a packed batch.
- Ratios control how frequently each dataset is visited (per batch, not per sample).
- Selection is deterministic: step
ialways picks the same dataset given the sameseed. - Stateful checkpoint/resume: both the outer step counter (
global_id) and each inner loader's per-worker position are saved and restored.
How to wire it up
Each inner DataPackerDataLoader must be given a unique name that matches its
key in the dataloaders dict. The name namespaces the resume env vars to
prevent conflicts between concurrent loaders.
from cosmos_framework.data.vfm.data_packer_dataloader import DataPackerDataLoader, JointDataPackerDataLoader
from cosmos_framework.callbacks.dataloader_state import JointDataLoaderStateCallback
from cosmos_framework.utils.lazy_config import LazyCall as L
# Build the joint loader
joint_loader = JointDataPackerDataLoader(
dataloaders={
"video": {
"dataloader": DataPackerDataLoader(
data_source=MyVideoDataset(...),
data_packer=MyVideoDataPacker(...),
max_tokens=45056,
shuffle=True,
seed=0,
name="video", # must match the key above
num_workers=4,
persistent_workers=True,
pin_memory=True,
),
"ratio": 3, # video 3Γ, action 1Γ
},
"action": {
"dataloader": DataPackerDataLoader(
data_source=MyActionDataset(...),
data_packer=MyActionDataPacker(...),
max_tokens=999_999,
max_batch_size=128,
shuffle=True,
seed=0,
name="action", # must match the key above
num_workers=4,
persistent_workers=True,
pin_memory=True,
),
"ratio": 1,
},
},
seed=42, # controls outer dataset selection sequence
)
# Wire into the experiment config
exp["dataloader_train"] = joint_loader
exp["trainer"]["callbacks"]["dataloader_state"] = JointDataLoaderStateCallback(
outer_loader=joint_loader,
distributor_type="data_packer",
)
Reserved name:
"global_id"cannot be used as a dataset name β it is reserved by the checkpoint state format.
JointDataPackerDataLoader parameters
| Parameter | What it controls |
|---|---|
dataloaders |
Dict mapping dataset name β {"dataloader": DataPackerDataLoader, "ratio": int}. Entries with ratio <= 0 are skipped. |
seed |
Base seed for outer dataset selection. Step i uses np.random.RandomState(seed + i) β same sequence on every rank. |
JointDataLoaderStateCallback
This single callback replaces the per-inner-loader DataLoaderStateCallback
instances. It saves:
global_idβ the outer step counter, which determines which dataset fires at each step on resume.- Per-dataset, per-worker
(epoch, index)β each inner loader's position.
All state is written to a single DCP checkpoint entry (checkpoint_component="dataloader").
Stateful checkpoint/resume
At checkpoint step N:
global_id = Nis saved.- Each inner loader saves its per-worker
(epoch, index)under itsnamekey.
On resume:
JointDataLoaderStateCallback.load_state_dictcallsset_start_iteration(N)on the outer loader β selection sequence resumes from stepN.- Each inner
DataLoaderStateCallback.load_state_dictsets namespaced env vars (DP_STATE_{name}_WORKER_{id}_EPOCH/INDEX) β workers fast-forward to the saved position.
Inner loader iterators are created lazily on the first __iter__ call (not at
__init__ time), ensuring workers fork after env vars have been set.
Real-world examples
Reasoner β HuggingFace image-text dataset
File: cosmos_framework/configs/base/vlm/experiment/llava_ov_datapacker_experiment.py
data_source: lmms-lab/LLaVA-OneVision-Data (streaming IterableDataset)
DataPacker: VLMDataPacker
sft_process_sample: ShareGPT β OpenAI messages β Qwen3-VL processor
compute_num_tokens: len(input_ids)
sft_collate_fn: unsqueeze batch dim, keep pixel_values flat
max_batch_size: 1
max_tokens: ~16000
shuffle: False (streaming IterableDataset β use .shuffle() externally)
Action Policy β Robot learning (LIBERO)
File: cosmos_framework/configs/base/experiment/action/posttrain_config/libero_policy_datapacker_experiment.py
data_source: LIBERODataset (map-style Dataset, passed directly)
DataPacker: ActionDataPacker
sft_process_sample: full ActionTransformPipeline (resize, tokenize, pad action)
compute_num_tokens: VAE video tokens + text tokens
sft_collate_fn: action/domain_id/sequence_plan fields + video + text
max_batch_size: 128 (token budget disabled β batch bounded by max_batch_size)
max_tokens: 999999
shuffle: True, seed=0
LIBERODataset is a map-style Dataset passed directly. shuffle=True enables
per-epoch shuffling and stateful checkpoint/resume. This pattern (high max_tokens
- bounded
max_batch_size) is standard for action policy training where you want a fixed number of demonstrations per step.
Checklist for a new dataset
Single dataset (DataPackerDataLoader)
- Choose a
data_source: map-styleDatasetorIterableDataset(no plain lists/generators) - For map-style: pass directly; use
shuffle=True, seed=<N>for per-epoch shuffle - For iterable: shuffle externally before passing (e.g.
.shuffle(buffer_size=N)) - If dataset has internal DP sharding, disable it (
shard_world_size=1) - Subclass
DataPackerand implementsft_process_sample,compute_num_tokens,sft_collate_fn - Choose
max_tokensandmax_batch_sizefor your modality - Add
DataLoaderStateCallback(distributor_type="data_packer")to the experiment's callbacks (works for bothshuffle=Trueandshuffle=Falseon map-style datasets) - Use
ckpt_type=dcp(notdummy) for real checkpoint/resume - Register in Hydra ConfigStore with
cs.store(group="experiment", ...) - Smoke-test with
ckpt_type=dummy trainer.max_iter=10before a full run
Multiple datasets (JointDataPackerDataLoader)
- Give each inner
DataPackerDataLoadera uniquenamematching its key indataloaders - Set appropriate
ratiofor each dataset (controls visit frequency per batch) - Use
JointDataLoaderStateCallback(outer_loader=joint_loader)instead ofDataLoaderStateCallback - Do not also register standalone
DataLoaderStateCallbackfor inner loaders βJointDataLoaderStateCallbackhandles all of them - Avoid using
"global_id"as a dataset name (reserved) - Use
ckpt_type=dcpfor real checkpoint/resume