Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9f66c63
DROID action SFT: GPU-side ColorJitter + lazy dataset index
fwd4 Jun 22, 2026
22751ff
Warm-start: seed net_ema from net when net_ema is skipped on load
fwd4 Jun 23, 2026
bc72312
DROID action SFT: CPU-side ColorJitter, JSON prompt, sharded/flat dat…
fwd4 Jul 1, 2026
b5827c0
Capture the exact 40%-SR working-tree state: 7d9b443 + uncommitted i4…
fwd4 Jul 4, 2026
ca33f29
tests: add DROID + LIBERO action-policy numerical regression test
fwd4 Jul 6, 2026
4089e7c
tests: add gb200 action-policy LIBERO loss golden (determinism-verified)
fwd4 Jul 6, 2026
6bbcd34
tests: run action-policy regression eager (compile off); reset golden…
fwd4 Jul 6, 2026
5f7a87f
tests: force fully-eager (TORCHDYNAMO_DISABLE) for action regression
fwd4 Jul 6, 2026
ad002a8
tests: add gb200 action-policy loss goldens (LIBERO + DROID, eager)
fwd4 Jul 6, 2026
f292ae5
tests: retarget action-policy regression to H200 (compile on), LIBERO…
fwd4 Jul 6, 2026
58f7462
tests: add H200 LIBERO action-policy loss goldens
fwd4 Jul 6, 2026
d92cd0c
chore: replace imaginaire4 proprietary banner with SPDX OpenMDW header
fwd4 Jul 6, 2026
8197530
recipe(action/droid): pin GB200 reference config in repro TOML
fwd4 Jul 6, 2026
19de528
recipe(action/droid): drop non-GB200 node-count mapping in TOML comment
fwd4 Jul 6, 2026
d2a9da7
recipe(action/droid): fold remaining reference overrides into TOML
fwd4 Jul 6, 2026
6829e84
cleanup(action/droid): remove dead flags flagged in review
fwd4 Jul 6, 2026
78d8ce3
cleanup(action): inline memprofile no-op shims, delete stub module
fwd4 Jul 6, 2026
a8537cf
Merge branch 'main' into haolia/droid-oss-40pct-full-rebased
lfengad Jul 7, 2026
c0f45f7
Merge branch 'main' into haolia/droid-oss-40pct-full-rebased
fwd4 Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion cosmos_framework/checkpoint/dcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@

from cosmos_framework.checkpoint.base import AbstractCheckpointer
from cosmos_framework.checkpoint.s3_filesystem import S3StorageReader, S3StorageWriter
from cosmos_framework.utils.config import CheckpointConfig, JobConfig
from cosmos_framework.model._base import ImaginaireModel
from cosmos_framework.utils import callback, distributed, log, misc
from cosmos_framework.utils.config import CheckpointConfig, JobConfig
from cosmos_framework.utils.easy_io import easy_io
from cosmos_framework.utils.generator.rand_state import get_rand_state_dict, set_rand_state_dict

Expand Down Expand Up @@ -866,6 +866,16 @@ def load(
raise ValueError(
f"Unexpected keys (found in checkpoint but not in model): {results.unexpected_keys}"
)
# Warm start that skipped net_ema (e.g. loading an EMA-only HF export
# with no net_ema.* keys): the EMA shadow would otherwise keep its random
# build-time generation pathway (init_moe is skipped when a checkpoint is
# present). Seed net_ema from the freshly loaded net so the EMA starts equal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this need to be synced with internal?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For posttrain I think not, this is mainly for loading the HF converted dcp, which does not have ema weights. Internal ckpts will have this.

# to net ("EMA warm-starts from net") instead of from random weights.
if warm_start and any("net_ema" in skip_key for skip_key in keys_to_skip_loading):
ema_worker = getattr(model, "net_ema_worker", None)
if ema_worker is not None and getattr(model, "net_ema", None) is not None:
ema_worker.copy_to(src_model=model.net, tgt_model=model.net_ema)
log.info("Warm start: re-seeded net_ema from net (net_ema was skipped on load).")

elif key == "optim":
log.info("- Loading the optimizer...")
Expand Down
10 changes: 9 additions & 1 deletion cosmos_framework/configs/base/defaults/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

import attrs

from cosmos_framework.utils.lazy_config import LazyDict
from cosmos_framework.configs.base.defaults.activation_checkpointing import ActivationCheckpointingConfig
from cosmos_framework.configs.base.defaults.compile import CompileConfig
from cosmos_framework.configs.base.defaults.ema import EMAConfig
from cosmos_framework.configs.base.defaults.parallelism import ParallelismConfig
from cosmos_framework.configs.base.defaults.reasoner import VLMConfig
from cosmos_framework.utils.lazy_config import LazyDict


@attrs.define(slots=False)
Expand Down Expand Up @@ -249,6 +249,14 @@ class OmniMoTModelConfig:
max_action_dim: int = 32 # maximum dimension of the action space, we need to pad the data to this dimension.
num_embodiment_domains: int = 32 # number of domains for the domain-aware linear layer

# Optional GPU-side photometric augmentation applied per-sample to the input video
# during training (in _normalize_video_databatch_inplace), as a dict of
# torchvision ColorJitter kwargs e.g. {"brightness":0.3,"contrast":0.4,"saturation":0.5,"hue":0.08}.
# None disables it. Moving ColorJitter here (GPU, batched) instead of in the CPU
# dataloader workers avoids the ~14x per-sample slowdown from float32 rgb<->hsv on
# ~1 core/worker (the dataloader's dominant cost for video action SFT).
train_color_jitter: dict | None = None

Comment thread
ychao-nvidia marked this conversation as resolved.
Outdated
# sound configs
sound_gen: bool = False # whether to use sound related parameters and condition/generate sound tokens
sound_tokenizer: LazyDict | None = None # Sound tokenizer config (e.g., AVAE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@

from hydra.core.config_store import ConfigStore

from cosmos_framework.utils.lazy_config import LazyCall as L
from cosmos_framework.utils.lazy_config import LazyDict

from cosmos_framework.configs.base.experiment.sft.models.nano_model_config import NANO_MODEL_CONFIG
from cosmos_framework.data.generator.action.datasets.action_sft_dataset import get_action_droid_sft_dataset
from cosmos_framework.data.generator.joint_dataloader import (
PackingDataLoader,
RankPartitionedDataLoader,
)
from cosmos_framework.data.generator.action.datasets.action_sft_dataset import get_action_droid_sft_dataset
from cosmos_framework.utils.lazy_config import LazyCall as L
from cosmos_framework.utils.lazy_config import LazyDict

cs = ConfigStore.instance()

Expand Down Expand Up @@ -203,16 +202,29 @@
iterable_shuffle=True, # rank x worker episode-shuffle stream
episode_shuffle_seed=42,
use_image_augmentation=True, # SR boost (random crop+rescale + color jitter)
# ColorJitter applied CPU-side in the dataloader augmentor, matching
# i4's pipeline stage exactly (model.config.train_color_jitter is None
# below). Slower (~14x rgb<->hsv on 1 core/worker) but eliminates the
# last structural OSS-vs-i4 training-path difference.
apply_color_jitter=True,
# keep_ranges_1_0_1.json window filter (drops idle/non-task frames). Off by default;
# set use_filter_dict=True + filter_dict_path to enable.
use_filter_dict=False,
filter_dict_path=None,
action_normalization=None,
# Sharded (per-lab) dataset layout. False -> read DROID_ROOT as a
# single flat LeRobot (prior behavior). Override to True (and point
# DROID_ROOT at the <...>_sharded dir, which holds success/<lab>/...)
# to reproduce the internal sharded run's per-shard index build.
sharded=False,
viewpoint="concat_view", # wrist 480p (top) + L/R shoulder 320x180 (bottom)
resolution="480", # 640x360 data @ 480p
max_action_dim="${model.config.max_action_dim}",
cfg_dropout_rate=0.1,
tokenizer_config="${model.config.vlm_config.tokenizer}",
# Match i4 GA (droid_lerobot_8b_ga / MR #9995): format the action
# prompt as JSON via ActionPromptJsonFormatter instead of plain text.
format_prompt_as_json=True,
),
),
),
Expand Down Expand Up @@ -243,6 +255,14 @@
action_policy_droid_nano["model"]["config"]["rectified_flow_training_config"]["loss_scale"] = 10.0


# GPU-side photometric augmentation DISABLED. ColorJitter now runs CPU-side in the
# dataloader (apply_color_jitter=True above), matching i4's exact pipeline stage so the
# OSS and i4 training forward paths are structurally identical. (To restore the faster
# GPU-side jitter, set this back to dict(brightness=0.3,contrast=0.4,saturation=0.5,hue=0.08)
# and flip the dataset's apply_color_jitter back to False.)
action_policy_droid_nano["model"]["config"]["train_color_jitter"] = None


Comment thread
ychao-nvidia marked this conversation as resolved.
Outdated
for _item in [action_policy_droid_nano]:
_name = [k for k, v in globals().items() if v is _item][0]
cs.store(group="experiment", package="_global_", name=_name, node=_item)
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

from torch.utils.data import Dataset, IterableDataset, get_worker_info

from cosmos_framework.data.generator.action.datasets.droid_lerobot_dataset import DROIDLeRobotDataset
from cosmos_framework.data.generator.action.datasets.droid_lerobot_dataset import (
DROIDLeRobotDataset,
)
from cosmos_framework.data.generator.action.datasets.libero_lerobot_dataset import LIBEROLeRobotDataset
from cosmos_framework.data.generator.action.transforms import ActionTransformPipeline

Expand Down Expand Up @@ -99,6 +101,7 @@ def get_action_droid_sft_dataset(
action_normalization: str | None = None,
viewpoint: str = "concat_view",
use_image_augmentation: bool = False,
apply_color_jitter: bool = True,
Comment thread
ychao-nvidia marked this conversation as resolved.
Outdated
use_filter_dict: bool = False,
filter_dict_path: str | None = None,
resolution: str | int = "256",
Expand All @@ -109,24 +112,46 @@ def get_action_droid_sft_dataset(
append_duration_fps_timestamps: bool = True,
append_resolution_info: bool = True,
append_idle_frames: bool = False,
format_prompt_as_json: bool = False,
iterable_shuffle: bool = False,
episode_shuffle_seed: int = 42,
sharded: bool = False,
lerobot_roots: list[str] | None = None,
Comment thread
ychao-nvidia marked this conversation as resolved.
Outdated
use_success_only: bool = True,
) -> Dataset:
"""Build the DROID action SFT dataset: ``action_space='joint_pos'`` (8D) +
``use_state`` (raw/un-normalized), concat_view, chunk_length 32."""
dataset = DROIDLeRobotDataset(
root=root,
``use_state`` (raw/un-normalized), concat_view, chunk_length 32.

``sharded=True`` consumes the per-lab sharded layout (``<root>/success/<lab>``)
via :class:`ShardedDROIDLeRobotDataset` — one ``DROIDLeRobotDataset`` per lab
concatenated into one flat index — reproducing the internal sharded run's
per-shard index construction. ``sharded=False`` (default) reads ``root`` as a
single flat LeRobot dataset (the prior behavior). ``lerobot_roots`` optionally
pins the shard sub-paths (relative to ``root``); otherwise they are
auto-discovered."""
if isinstance(sharded, str):
sharded = sharded.strip().lower() in ("1", "true", "yes", "on")
Comment thread
ychao-nvidia marked this conversation as resolved.
Outdated
shard_kwargs = dict(
fps=fps,
chunk_length=chunk_length,
viewpoint=viewpoint,
action_space=action_space,
mode=mode,
use_state=use_state,
action_normalization=action_normalization,
use_image_augmentation=use_image_augmentation,
use_image_augmentation=use_image_augmentation, # i4: bundles random-crop+resize+ColorJitter
use_filter_dict=use_filter_dict,
filter_dict_path=filter_dict_path,
use_success_only=use_success_only,
)
if sharded:
raise NotImplementedError(
"The ported i4 DROIDLeRobotDataset reads the merged/versioned root natively "
"(basename(root) must be a LEROBOT_ROOTS version key, e.g. "
"'droid_plus_lerobot_640x360_20260412'); use_success_only=True filters to success/. "
"Per-lab ShardedDROIDLeRobotDataset is not part of the i4 dataset."
)
Comment thread
ychao-nvidia marked this conversation as resolved.
Outdated
dataset: Dataset = DROIDLeRobotDataset(root=root, **shard_kwargs)
transform = ActionTransformPipeline(
tokenizer_config=tokenizer_config,
cfg_dropout_rate=cfg_dropout_rate,
Expand All @@ -135,6 +160,7 @@ def get_action_droid_sft_dataset(
append_duration_fps_timestamps=append_duration_fps_timestamps,
append_resolution_info=append_resolution_info,
append_idle_frames=append_idle_frames,
format_prompt_as_json=format_prompt_as_json,
)
sft = ActionSFTDataset(dataset, transform, resolution)
if iterable_shuffle:
Expand Down
42 changes: 28 additions & 14 deletions cosmos_framework/data/generator/action/datasets/base_dataset.py
Comment thread
ychao-nvidia marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,13 @@ def __init__(
# LeRobot v2.x stores task text in a "task" column; v3.0 stores it as the
# (unnamed) DataFrame index and keeps only "task_index" as a column.
task_texts = tasks_df["task"] if "task" in tasks_df.columns else tasks_df.index
self._tasks = {
int(task_index): str(task)
for task, task_index in zip(task_texts, tasks_df["task_index"])
}
self._rows = sorted(
(
row
for path in sorted((self._root / "data").glob("chunk-*/file-*.parquet"))
for row in pq.read_table(path).to_pylist()
),
key=lambda row: int(row["index"]),
)
self._tasks = {int(task_index): str(task) for task, task_index in zip(task_texts, tasks_df["task_index"])}
# ``self._rows`` (the flat, index-sorted list of every frame dict) is built
# lazily on first access — see the ``_rows`` property. Materializing all
# ~18M frames as Python dicts plus a full sort costs ~13 min and tens of GB;
# subclasses that build their own compact index (e.g. DROIDLeRobotDataset)
# never touch it, so they must not pay for it at construction.
self._rows_cache: list[dict[str, Any]] | None = None

@property
def fps(self) -> float:
Expand Down Expand Up @@ -140,8 +135,7 @@ def _stats_path(cls) -> Path:
def load_action_stats(cls) -> dict[str, torch.Tensor]:
"""Return action normalization stats for this dataset as torch tensors."""
return {
key: torch.from_numpy(value).float()
for key, value in load_action_stats(str(cls._stats_path())).items()
key: torch.from_numpy(value).float() for key, value in load_action_stats(str(cls._stats_path())).items()
}

@abstractmethod
Expand Down Expand Up @@ -218,5 +212,25 @@ def _build_result(
**extras,
}

@property
def _rows(self) -> list[dict[str, Any]]:
"""Flat, index-sorted list of every frame dict, built lazily on first access.

Only datasets that don't build their own compact index (bridge / agibot /
robomind) touch this; for them it materializes once and caches. Datasets with
a bespoke index (e.g. DROIDLeRobotDataset) never read it, so they skip the
~13 min / tens-of-GB construction entirely.
"""
if self._rows_cache is None:
self._rows_cache = sorted(
(
row
for path in sorted((self._root / "data").glob("chunk-*/file-*.parquet"))
for row in pq.read_table(path).to_pylist()
),
key=lambda row: int(row["index"]),
)
return self._rows_cache

def __len__(self) -> int:
return max(0, (len(self._rows) - self._chunk_length + self._sample_stride - 1) // self._sample_stride)
Loading