File size: 14,868 Bytes
9f818c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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()