Spaces:
Running on L40S
Running on L40S
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: OpenMDW-1.1 | |
| """ | |
| Minimal inference demo β drive Cosmos's OmniMoTModel directly. | |
| β THIS IS A WIRING DEMO. It shows the smallest worked example of the inference | |
| call sequence for each generation mode, not a production serving recipe. | |
| For batched / streaming / Ray-Serve deployment, see | |
| `cosmos_framework.inference.inference.OmniInference` and `cosmos_framework.inference.ray.*`. | |
| β THE MAIN TRANSFORMER IS RANDOM-INITIALIZED β the demo never loads the | |
| ~30 GB Cosmos3-Nano DCP shards. Pixel / sound outputs are therefore noise | |
| regardless of mode; the point is to show the call sequence and tensor | |
| shapes, not to produce meaningful samples. For real weight loading see | |
| `cosmos_framework.inference.model.Cosmos3OmniModel.from_pretrained_dcp` | |
| and the production CLI in `cosmos_framework.scripts.inference`. | |
| β For `--mode action_fdm` and `--mode t2vs` we additionally feed the model | |
| RANDOM conditioning tensors (no real video / action files on disk). Plug | |
| in real conditioning via your own loader if you port the wiring. | |
| ================================================================================ | |
| SCOPE | |
| ================================================================================ | |
| This is NOT "extracting the model into another framework". The cosmos_framework package | |
| must be installed. What this script demonstrates is the smallest possible | |
| inference path per generation mode: load β batch β generate β decode β save. | |
| What we USE from cosmos_framework: | |
| cosmos_framework.inference.model.Cosmos3OmniModel β model class (random-init in this demo; | |
| use `.from_pretrained_dcp(...)` for real weights) | |
| cosmos_framework.inference.common.init.init_script β 1-line torch.distributed init | |
| cosmos_framework.inference.{args,inference} β OmniSampleOverrides + | |
| get_sample_data (T2I/T2V only) | |
| cosmos_framework.data.vfm.{action,sequence_packing} β SequencePlan helpers (action/sound) | |
| cosmos_framework.model.vfm.vlm.qwen3_vl.utils.tokenize_caption | |
| model.generate_samples_from_batch(batch, seed) β THE inference call (CFG + sampler) | |
| model.decode(latent) β VAE decode | |
| What we DO NOT use: | |
| cosmos_framework.scripts.inference β CLI entry point | |
| cosmos_framework.inference.inference.OmniInference β serving/batching pipeline | |
| cosmos_framework.inference.ray.* β Ray serving | |
| ================================================================================ | |
| RUN | |
| ================================================================================ | |
| PYTHONPATH=. python examples/integration/trainer_level_inference.py # T2I | |
| PYTHONPATH=. python examples/integration/trainer_level_inference.py --mode t2v # T2V | |
| PYTHONPATH=. python examples/integration/trainer_level_inference.py --mode action_fdm # action (fake input) | |
| PYTHONPATH=. python examples/integration/trainer_level_inference.py --mode t2vs # sound+video (fake input) | |
| """ | |
| from cosmos_framework.inference.common.init import init_script | |
| init_script() # init torch.distributed + DCP wrappers (required even on 1 GPU) | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import attrs | |
| import torch | |
| from cosmos_framework.configs.base.defaults.compile import CompileConfig | |
| from cosmos_framework.configs.base.defaults.parallelism import ParallelismConfig | |
| from cosmos_framework.data.vfm.action.domain_utils import get_domain_id | |
| from cosmos_framework.data.vfm.action.transforms import build_sequence_plan_from_mode | |
| from cosmos_framework.data.vfm.sequence_packing import SequencePlan | |
| from cosmos_framework.inference.args import DEFAULT_CHECKPOINT, OmniSampleOverrides | |
| from cosmos_framework.inference.inference import get_sample_data | |
| from cosmos_framework.inference.model import Cosmos3OmniConfig, Cosmos3OmniModel | |
| from cosmos_framework.model.vfm.vlm.qwen3_vl.utils import tokenize_caption | |
| from cosmos_framework.tools.visualize.video import save_img_or_video | |
| def _load_omni_model(*, config_dir_arg: str | None): | |
| """Build OmniMoTModel with RANDOM main-transformer weights β wiring demo only. | |
| This helper exists so the demo can run without downloading the ~30 GB transformer | |
| DCP. Only ``config.json`` is fetched (single ~5 KB file) and the main net is | |
| instantiated via ``hydra.utils.instantiate`` with random parameters. Auxiliary | |
| sub-models (Qwen3-VL tokenizer, Wan2.2 VAE, AVAE) still load from the HF cache | |
| during ``Cosmos3OmniModel.__init__`` β they are not stubbed out. | |
| For REAL weight loading, see | |
| :func:`cosmos_framework.inference.model.Cosmos3OmniModel.from_pretrained_dcp` | |
| and the production CLI in :mod:`cosmos_framework.scripts.inference`. | |
| """ | |
| if config_dir_arg is None: | |
| from huggingface_hub import hf_hub_download | |
| config_dir = Path(hf_hub_download( | |
| repo_id=DEFAULT_CHECKPOINT.hf.repository, | |
| filename="config.json", | |
| revision=DEFAULT_CHECKPOINT.hf.revision, | |
| )).parent | |
| else: | |
| config_dir = Path(config_dir_arg) | |
| # Shipped DCPs nest config.json one level deeper under model/. | |
| if not (config_dir / "config.json").exists() and (config_dir / "model" / "config.json").exists(): | |
| config_dir = config_dir / "model" | |
| print(f"Loading config from: {config_dir / 'config.json'}") | |
| # Shipped configs carry stale `cosmos3._src.*` dotted module strings in `_type` / `_target_` | |
| # fields. cosmos_framework's CONFIG_REPLACEMENTS_INVERSE only rewrites the slash-form | |
| # paths, so we rewrite the dotted form here before constructing the config. | |
| config_text = (config_dir / "config.json").read_text() | |
| for _old, _new in [ | |
| ("cosmos3._src.vfm.configs.base.", "cosmos_framework.configs.base."), | |
| ("cosmos3._src.vfm.models.", "cosmos_framework.model.vfm."), | |
| ("cosmos3._src.vfm.tokenizers.", "cosmos_framework.model.vfm.tokenizers."), | |
| ("cosmos3._src.imaginaire.", "cosmos_framework."), | |
| ]: | |
| config_text = config_text.replace(_old, _new) | |
| config = Cosmos3OmniConfig(model=json.loads(config_text)["model"]) | |
| config.parallelism = attrs.asdict(ParallelismConfig()) | |
| config.compile = attrs.asdict(CompileConfig(enabled=False)) | |
| return Cosmos3OmniModel(config).model | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Per-mode batch builders. T2I and T2V reuse cosmos_framework's `get_sample_data` helper | |
| # (which also stamps default sampler args). action_fdm and t2vs are built by | |
| # hand using the same dict contract as trainer_level_training.py. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _tokenize(model, caption: str, device) -> torch.Tensor: | |
| ids = tokenize_caption(caption, model.vlm_tokenizer, is_video=False, | |
| use_system_prompt=model.vlm_config.use_system_prompt) | |
| return torch.tensor(ids, dtype=torch.long, device=device).unsqueeze(0) | |
| def build_t2iv_batch(model, output_dir, prompt: str, num_frames: int) -> dict: | |
| """T2I (num_frames=1) or T2V (num_frames>1) via cosmos_framework's inference batch helper.""" | |
| sample_args = OmniSampleOverrides( | |
| name="integration_demo", output_dir=output_dir, | |
| prompt=prompt, num_frames=num_frames, | |
| ).build_sample(model_config=model.config) | |
| return get_sample_data(sample_args, model) | |
| def build_action_fdm_batch(model, *, caption: str, num_video_frames: int = 5, | |
| action_chunk: int = 4, raw_action_dim: int = 7, | |
| h: int = 128, w: int = 128, | |
| domain_name: str = "bridge_orig_lerobot", device="cuda") -> dict: | |
| """Forward-dynamics inference batch (RANDOM video + actions; output = noise).""" | |
| video = (torch.randn(1, 3, num_video_frames, h, w, device=device) * 0.3).clamp(-1, 1) | |
| action = torch.zeros(action_chunk, model.config.max_action_dim, device=device) | |
| action[:, :raw_action_dim] = torch.randn(action_chunk, raw_action_dim, device=device) * 0.1 | |
| sequence_plan = build_sequence_plan_from_mode( | |
| mode="forward_dynamics", video_length=num_video_frames, | |
| action_length=action_chunk, has_text=True, | |
| ) | |
| return { | |
| model.input_video_key: [video], | |
| "action": [action], | |
| "raw_action_dim": [torch.tensor(raw_action_dim, dtype=torch.long, device=device)], | |
| "mode": ["forward_dynamics"], | |
| model.input_caption_key: [caption], | |
| "text_token_ids": [_tokenize(model, caption, device)], | |
| "image_size": [torch.tensor([[h, w, h, w]], dtype=torch.float32, device=device)], | |
| "fps": torch.tensor([16.0], device=device), | |
| "conditioning_fps": torch.tensor([16.0], device=device), | |
| "num_frames": torch.tensor([num_video_frames], device=device), | |
| "domain_id": [torch.tensor(get_domain_id(domain_name), dtype=torch.long, device=device)], | |
| "sequence_plan": [sequence_plan], | |
| "is_preprocessed": True, | |
| } | |
| def build_t2vs_batch(model, *, caption: str, num_video_frames: int = 5, | |
| audio_hop_count: int = 8, h: int = 128, w: int = 128, | |
| device="cuda") -> dict: | |
| """Textβvideo+sound inference batch (RANDOM conditioning; output = noise).""" | |
| waveform = (torch.randn(2, audio_hop_count * 1920, device=device) * 0.1).clamp(-1, 1) | |
| video = (torch.randn(1, 3, num_video_frames, h, w, device=device) * 0.3).clamp(-1, 1) | |
| sequence_plan = SequencePlan(has_text=True, has_vision=True, has_sound=True) | |
| return { | |
| model.input_video_key: [video], | |
| "sound": [waveform], | |
| model.input_caption_key: [caption], | |
| "text_token_ids": [_tokenize(model, caption, device)], | |
| "image_size": [torch.tensor([[h, w, h, w]], dtype=torch.float32, device=device)], | |
| "fps": torch.tensor([16.0], device=device), | |
| "conditioning_fps": torch.tensor([16.0], device=device), | |
| "num_frames": torch.tensor([num_video_frames], device=device), | |
| "sequence_plan": [sequence_plan], | |
| "is_preprocessed": True, | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # main | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--config-dir", type=str, default=None, | |
| help="Local directory containing config.json (architecture only β weights are " | |
| "randomly initialized). If omitted, fetches Cosmos3-Nano's config.json from HF.") | |
| parser.add_argument("--mode", type=str, default="t2i", | |
| choices=["t2i", "t2v", "action_fdm", "t2vs"], | |
| help="Generation mode. action_fdm and t2vs use random conditioning β noise output.") | |
| parser.add_argument("--prompt", type=str, | |
| default="A neon city street at night, rain reflecting the signs.") | |
| parser.add_argument("--num-frames", type=int, default=None, | |
| help="Number of video frames. Defaults: 1 for t2i, 33 for t2v, 5 for action/sound.") | |
| parser.add_argument("--seed", type=int, default=0) | |
| parser.add_argument("--num-steps", type=int, default=35, | |
| help="Sampling steps. Lower β faster + noisier.") | |
| args = parser.parse_args() | |
| output_dir = Path(f"outputs/trainer_level_inference/{args.mode}").absolute() | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| # 1) Build the bare OmniMoTModel (random weights β see module docstring) --- | |
| model = _load_omni_model(config_dir_arg=args.config_dir) | |
| model.eval() | |
| # 2) Build a batch per mode -------------------------------------------- | |
| if args.mode == "t2i": | |
| nframes = args.num_frames if args.num_frames is not None else 1 | |
| data_batch = build_t2iv_batch(model, output_dir, args.prompt, nframes) | |
| elif args.mode == "t2v": | |
| nframes = args.num_frames if args.num_frames is not None else 33 | |
| data_batch = build_t2iv_batch(model, output_dir, args.prompt, nframes) | |
| elif args.mode == "action_fdm": | |
| nframes = args.num_frames if args.num_frames is not None else 5 | |
| data_batch = build_action_fdm_batch(model, caption=args.prompt, num_video_frames=nframes) | |
| elif args.mode == "t2vs": | |
| nframes = args.num_frames if args.num_frames is not None else 5 | |
| data_batch = build_t2vs_batch(model, caption=args.prompt, num_video_frames=nframes) | |
| print(f"Mode: {args.mode} num_frames={nframes}") | |
| # 3) Generate. THE only model call needed ------------------------------ | |
| with torch.no_grad(): | |
| outputs = model.generate_samples_from_batch( | |
| data_batch, seed=[args.seed], num_steps=args.num_steps, | |
| ) | |
| # 4) Decode vision (and sound if present) ------------------------------ | |
| pixels = model.decode(outputs["vision"][0]) # [1, 3, T, H, W] in [-1, 1] | |
| pixels = (pixels.clamp(-1, 1) + 1.0) / 2.0 # β [0, 1] | |
| fps = float(data_batch["fps"][0].item()) | |
| save_img_or_video(pixels[0], str(output_dir / "output"), fps=fps) | |
| if args.mode == "t2vs" and "sound" in outputs and outputs["sound"] is not None: | |
| # Sound latents β waveform via AVAE decode. Save as a raw .pt; users plug | |
| # their own audio writer (torchaudio.save / soundfile) for .wav output. | |
| sound_latent = outputs["sound"][0] # [C_sound, T_sound] | |
| waveform = model.decode_sound(sound_latent) # [C_audio, N_samples] | |
| torch.save(waveform.cpu(), output_dir / "sound.pt") | |
| print(f" sound waveform: shape={tuple(waveform.shape)} β sound.pt") | |
| print(f"Saved to: {output_dir}") | |
| if __name__ == "__main__": | |
| main() | |