|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# SPDX-License-Identifier: OpenMDW-1.1 |
| 3 | + |
| 4 | +"""``action_policy_robocasa_nano`` — RoboCasa mobile-base action policy. |
| 5 | +
|
| 6 | +Trains Cosmos3-Nano into a mobile-manipulation policy on the 18 RoboCasa ``target/atomic`` |
| 7 | +tasks. The action contract is 15-dimensional and carries RoboCasa's native base command |
| 8 | +unchanged (``base_encoding="raw"``):: |
| 9 | +
|
| 10 | + [base_motion(4), control_mode(1), eef_pos(3), eef_rot6d(6), gripper(1)] |
| 11 | +
|
| 12 | +``base_motion`` is the command sent to the base controller, so the closed-loop client writes it |
| 13 | +straight back (``env[7:11] = action[0:4]``) and a recorded demonstration replays exactly. It is |
| 14 | +already normalised to [-1, 1], i.e. the same scale as the arm block, which matters because |
| 15 | +``action_normalization=None``: the state token is absolute, so delta statistics do not apply and |
| 16 | +the raw channel scales are what the loss sees. |
| 17 | +
|
| 18 | +Observation is ``camera_set="left_wrist"`` — ``agentview_left`` and ``eye_in_hand`` concatenated |
| 19 | +horizontally at their native 256x256 each — plus end-effector proprioception prepended as a |
| 20 | +clean condition token (``use_state=True``). Action chunk length 32 at 20 fps, with |
| 21 | +``tokenizer.encode_exact_durations=[33]`` pinned to the matching 33-frame observation window |
| 22 | +(chunk + 1). |
| 23 | +
|
| 24 | +Closed-loop evaluation must use the same contract:: |
| 25 | +
|
| 26 | + ACTION_HORIZON=32 CAMERA_SET=left_wrist USE_STATE=1 |
| 27 | + USE_BASE_ACTION=1 BASE_ENCODING=raw RAW_ACTION_DIM=15 |
| 28 | +""" |
| 29 | + |
| 30 | +import copy |
| 31 | + |
| 32 | +from hydra.core.config_store import ConfigStore |
| 33 | + |
| 34 | +from cosmos_framework.configs.base.experiment.sft.models.nano_model_config import NANO_MODEL_CONFIG |
| 35 | +from cosmos_framework.data.generator.action.datasets.action_sft_dataset import get_action_robocasa_sft_dataset |
| 36 | +from cosmos_framework.data.generator.action.datasets.robocasa_lerobot_dataset import DEFAULT_ALL_ATOMIC_TASKS |
| 37 | +from cosmos_framework.data.generator.joint_dataloader import ( |
| 38 | + PackingDataLoader, |
| 39 | + RankPartitionedDataLoader, |
| 40 | +) |
| 41 | +from cosmos_framework.utils.lazy_config import LazyCall as L |
| 42 | +from cosmos_framework.utils.lazy_config import LazyDict |
| 43 | + |
| 44 | +cs = ConfigStore.instance() |
| 45 | + |
| 46 | + |
| 47 | +def _lw_state_model_config() -> dict: |
| 48 | + cfg = copy.deepcopy(NANO_MODEL_CONFIG) # action_gen=True, max_action_dim=64 |
| 49 | + cfg["max_num_tokens_after_packing"] = 74000 |
| 50 | + cfg["activation_checkpointing"]["mode"] = "selective" |
| 51 | + cfg["diffusion_expert_config"]["load_weights_from_pretrained"] = False |
| 52 | + cfg["rectified_flow_training_config"]["loss_scale"] = 10.0 |
| 53 | + cfg["rectified_flow_training_config"]["image_loss_scale"] = None |
| 54 | + # chunk_length=32 -> 33 observation frames; pin the VAE encode duration to match |
| 55 | + # (mirrors action_policy_droid_nano, which also trains with a 32-step chunk). |
| 56 | + cfg["tokenizer"]["encode_exact_durations"] = [33] |
| 57 | + return cfg |
| 58 | + |
| 59 | + |
| 60 | +action_policy_robocasa_nano = LazyDict( |
| 61 | + dict( |
| 62 | + defaults=[ |
| 63 | + {"override /model": "mot_fsdp"}, |
| 64 | + {"override /data_train": None}, |
| 65 | + {"override /data_val": None}, |
| 66 | + {"override /optimizer": "fusedadamw"}, |
| 67 | + {"override /scheduler": "lambdalinear"}, |
| 68 | + {"override /checkpoint": "s3"}, |
| 69 | + {"override /callbacks": ["basic", "optimization", "job_monitor"]}, |
| 70 | + {"override /ema": "power"}, |
| 71 | + {"override /tokenizer": "wan2pt2_tokenizer"}, |
| 72 | + {"override /sound_tokenizer": None}, |
| 73 | + {"override /vlm_config": None}, |
| 74 | + {"override /ckpt_type": "dcp"}, |
| 75 | + "_self_", |
| 76 | + ], |
| 77 | + job=dict( |
| 78 | + project="cosmos3", |
| 79 | + group="action_sft", |
| 80 | + name="action_policy_robocasa_nano", |
| 81 | + wandb_mode="disabled", |
| 82 | + ), |
| 83 | + model=dict(config=_lw_state_model_config()), |
| 84 | + optimizer=dict( |
| 85 | + betas=[0.9, 0.99], |
| 86 | + eps=1.0e-08, |
| 87 | + fused=True, |
| 88 | + keys_to_select=[ |
| 89 | + "moe_gen", "time_embedder", "vae2llm", "llm2vae", |
| 90 | + "action2llm", "llm2action", "action_modality_embed", |
| 91 | + ], |
| 92 | + lr=5.0e-05, |
| 93 | + lr_multipliers={"action2llm": 5.0, "llm2action": 5.0, "action_modality_embed": 5.0}, |
| 94 | + optimizer_type="FusedAdam", |
| 95 | + weight_decay=0.05, |
| 96 | + ), |
| 97 | + scheduler=dict( |
| 98 | + lr_scheduler_type="LambdaLinear", |
| 99 | + cycle_lengths=[100], |
| 100 | + f_max=[1.0], f_min=[0.0], f_start=[1.0e-06], |
| 101 | + verbosity_interval=0, warm_up_steps=[0], |
| 102 | + ), |
| 103 | + trainer=dict( |
| 104 | + distributed_parallelism="fsdp", |
| 105 | + grad_accum_iter=1, logging_iter=1, max_iter=100, max_val_iter=None, |
| 106 | + run_validation=False, run_validation_on_start=False, |
| 107 | + save_zero_checkpoint=False, seed=42, timeout_period=999999999, |
| 108 | + validation_iter=100, |
| 109 | + compile_config=dict(recompile_limit=8, use_duck_shape=False), |
| 110 | + cudnn=dict(benchmark=True, deterministic=False), |
| 111 | + ddp=dict(broadcast_buffers=True, find_unused_parameters=False, static_graph=True), |
| 112 | + grad_scaler_args=dict(enabled=False), |
| 113 | + callbacks=dict( |
| 114 | + dataloader_speed=dict(every_n=100, save_s3=False, step_size=1), |
| 115 | + device_monitor=dict(every_n=200, log_memory_detail=True, save_s3=False, step_size=1, upload_every_n_mul=5), |
| 116 | + grad_clip=dict(clip_norm=1.0, force_finite=True), |
| 117 | + heart_beat=dict(every_n=200, save_s3=False, step_size=1, update_interval_in_minute=20), |
| 118 | + iter_speed=dict(every_n=1, hit_thres=50, save_s3=False, save_s3_every_log_n=500), |
| 119 | + low_precision=dict(update_iter=1), |
| 120 | + manual_gc=dict(every_n=5, gc_level=1, warm_up=1), |
| 121 | + param_count=dict(save_s3=False), |
| 122 | + skip_nan_step=dict(max_consecutive_nan=100), |
| 123 | + training_stats=dict(log_freq=100), |
| 124 | + ), |
| 125 | + ), |
| 126 | + checkpoint=dict( |
| 127 | + broadcast_via_filesystem=False, |
| 128 | + dcp_async_mode_enabled=False, |
| 129 | + enable_gcs_patch_in_boto3=True, |
| 130 | + keys_not_to_resume=[], |
| 131 | + keys_to_skip_loading=[ |
| 132 | + "net_ema.", "action2llm", "llm2action", "action_modality_embed", "action_pos_embed", |
| 133 | + ], |
| 134 | + load_ema_to_reg=False, |
| 135 | + load_path="???", |
| 136 | + load_training_state=False, |
| 137 | + only_load_scheduler_state=False, |
| 138 | + save_iter=100, |
| 139 | + strict_resume=False, |
| 140 | + verbose=True, |
| 141 | + hf_export=dict(enabled=False, export_every_n=1, hf_repo_id=None, |
| 142 | + upload_to_object_store=dict(bucket="", credentials="", enabled=False)), |
| 143 | + jit=dict(device="cuda", dtype="bfloat16", enabled=False, input_shape=None, strict=True), |
| 144 | + load_from_object_store=dict(bucket="", credentials="", enabled=False), |
| 145 | + save_to_object_store=dict(bucket="", credentials="", enabled=False), |
| 146 | + ), |
| 147 | + dataloader_train=L(PackingDataLoader)( |
| 148 | + audio_sample_rate=48000, |
| 149 | + dataset_name="action_robocasa", |
| 150 | + max_samples_per_batch=128, |
| 151 | + max_sequence_length=None, |
| 152 | + patch_spatial=2, |
| 153 | + sound_latent_fps=0, |
| 154 | + tokenizer_spatial_compression_factor=16, |
| 155 | + tokenizer_temporal_compression_factor=4, |
| 156 | + dataloader=L(RankPartitionedDataLoader)( |
| 157 | + batch_size=1, in_order=False, num_workers=4, |
| 158 | + persistent_workers=True, pin_memory=True, prefetch_factor=4, sampler=None, |
| 159 | + datasets=dict( |
| 160 | + robocasa=dict( |
| 161 | + ratio=1, |
| 162 | + dataset=L(get_action_robocasa_sft_dataset)( |
| 163 | + root="${oc.env:ROBOCASA_ROOT}", |
| 164 | + task_names=DEFAULT_ALL_ATOMIC_TASKS, # all 18 atomic tasks (NavigateKitchen included) |
| 165 | + use_base_action=True, |
| 166 | + base_encoding="raw", # 15D: native base_motion(4) + control_mode + arm |
| 167 | + fps=20, |
| 168 | + chunk_length=32, # 32-step action chunk (DROID recipe uses 32); eval must use ACTION_HORIZON=32 |
| 169 | + mode="wam", |
| 170 | + viewpoint="concat_view", |
| 171 | + camera_set="left_wrist", # agentview_left + wrist, full-res 256x512 |
| 172 | + use_state=True, # EEF proprioception -> prepended clean condition token |
| 173 | + action_normalization=None, # absolute state token: delta-stats do not apply |
| 174 | + split="train", |
| 175 | + split_val_ratio=0.01, |
| 176 | + iterable_shuffle=True, |
| 177 | + episode_shuffle_seed=42, |
| 178 | + resolution=None, # keep 256x512 horizontal concat un-squished |
| 179 | + max_action_dim="${model.config.max_action_dim}", |
| 180 | + cfg_dropout_rate=0.1, |
| 181 | + format_prompt_as_json=True, |
| 182 | + tokenizer_config="${model.config.vlm_config.tokenizer}", |
| 183 | + ), |
| 184 | + ), |
| 185 | + ), |
| 186 | + ), |
| 187 | + ), |
| 188 | + dataloader_val=None, |
| 189 | + upload_reproducible_setup=False, |
| 190 | + ), |
| 191 | + flags={"allow_objects": True}, |
| 192 | +) |
| 193 | + |
| 194 | + |
| 195 | +for _item in [action_policy_robocasa_nano]: |
| 196 | + _name = [k for k, v in globals().items() if v is _item][0] |
| 197 | + cs.store(group="experiment", package="_global_", name=_name, node=_item) |
0 commit comments