Add Cosmos3 action modality (#4102)

Signed-off-by: Maciej Bala <mbala@nvidia.com>
Signed-off-by: Bartosz Stefaniak <bstefaniak@nvidia.com>
Signed-off-by: lishunyang12 <lishunyang12@163.com>
Co-authored-by: Maciej Bala <mbala@nvidia.com>
Co-authored-by: lishunyang12 <lishunyang12@163.com>
Co-authored-by: SYLAR <125541396+lishunyang12@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-06-03 19:30:39 +00:00
1 parent 8949fdc440
commit 706bad253d
15 files changed
+1314 -90

No files matched your search

@@ -99,12 +99,16 @@ class StubCosmos3Transformer(nn.Module):
sound_gen: bool = False,
sound_dim: int = 3,
sound_latent_fps: float = 25.0,
action_gen: bool = False,
action_dim: int = 4,
) -> None:
super().__init__()
self.latent_channel_size = latent_channel_size
self.sound_gen = sound_gen
self.sound_dim = sound_dim
self.sound_latent_fps = sound_latent_fps
self.action_gen = action_gen
self.action_dim = action_dim
self.cached_kv: Any | None = None
self.cached_freqs_gen: Any | None = None
self.calls: list[dict[str, Any]] = []
@@ -139,7 +143,10 @@ class StubCosmos3Transformer(nn.Module):
marker = torch.tensor([token], dtype=torch.float32)
self.cached_kv = [(marker, marker + 100)]
self.cached_freqs_gen = (marker + 200, marker + 300)
action_latents = kwargs.get("action_latents")
outputs: list[torch.Tensor] = [torch.full_like(hidden_states, float(token))]
if action_latents is not None:
outputs.append(torch.full_like(action_latents, float(token + 20)))
if sound_latents is not None:
outputs.append(torch.full_like(sound_latents, float(token + 10)))
return outputs[0] if len(outputs) == 1 else tuple(outputs)
@@ -344,7 +351,7 @@ def test_pipeline_init_passes_tokenizer_attrs_into_transformer(
assert pipeline.transformer.audio_proj_out.out_features == 5
def test_preprocess_i2v_image_input() -> None:
def test_preprocess_i2v_image_and_action_video_inputs() -> None:
from vllm_omni.diffusion.models.cosmos3.pipeline_cosmos3 import get_cosmos3_pre_process_func
preprocess = get_cosmos3_pre_process_func(SimpleNamespace())
@@ -357,6 +364,16 @@ def test_preprocess_i2v_image_input() -> None:
assert (result.sampling_params.height, result.sampling_params.width) == (672, 1344)
assert tuple(result.prompts[0]["additional_information"]["preprocessed_image"].shape[-2:]) == (672, 1344)
frames = [Image.new("RGB", (8, 4), color) for color in ("red", "green", "blue")]
action = SimpleNamespace(
prompts=[{"prompt": "Move.", "multi_modal_data": {"video": frames}}],
sampling_params=SimpleNamespace(height=16, width=32, extra_args={"action_mode": "forward_dynamics"}),
)
additional = preprocess(action).prompts[0]["additional_information"]
assert tuple(additional["preprocessed_image"].shape) == (1, 3, 16, 32)
assert tuple(additional["preprocessed_video"].shape) == (1, 3, 3, 16, 32)
def test_postprocess_handles_image_video_audio_and_validation() -> None:
from vllm_omni.diffusion.models.cosmos3.pipeline_cosmos3 import get_cosmos3_post_process_func
@@ -434,7 +451,7 @@ def test_prompt_formatting_and_checkpoint_key_remap(make_cosmos3_pipeline) -> No
assert {key: Cosmos3OmniDiffusersPipeline._remap_ckpt_key(key) for key in remaps} == remaps
def test_prepare_latents_for_video_image_and_sound(make_cosmos3_pipeline) -> None:
def test_prepare_latents_for_video_image_sound_and_action(make_cosmos3_pipeline) -> None:
pipeline = make_cosmos3_pipeline()
latents = pipeline._prepare_latents(16, 24, 5, torch.Generator(device="cpu").manual_seed(0))
assert latents.shape == (1, 2, 2, 2, 3)
@@ -463,8 +480,20 @@ def test_prepare_latents_for_video_image_and_sound(make_cosmos3_pipeline) -> Non
assert (sound_latents.shape, latent_frames) == (torch.Size([1, 3, 6]), 6)
assert pipeline._decode_sound_latents(torch.zeros(1, 3, 6), target_audio_samples=21).shape == (1, 2, 21)
pipeline.transformer = pipeline.transformer.__class__(action_gen=True, action_dim=4)
action, action_mask, clean, raw_dim = pipeline._prepare_action_latents(
mode="forward_dynamics",
action_chunk_size=2,
raw_action_dim=None,
generator=torch.Generator(device="cpu").manual_seed(0),
sp=SimpleNamespace(extra_args={"action": [[1.0, 2.0], [3.0, 4.0]]}),
)
assert raw_dim == 2
assert action_mask.tolist() == [[[0.0], [0.0]]]
torch.testing.assert_close(action, clean)
def test_diffuse_covers_cfg_i2v_and_sound_steps(make_cosmos3_pipeline) -> None:
def test_diffuse_covers_cfg_i2v_and_multimodal_steps(make_cosmos3_pipeline) -> None:
pipeline = make_cosmos3_pipeline()
latents = torch.zeros(1, 2, 1, 1, 1)
@@ -496,20 +525,22 @@ def test_diffuse_covers_cfg_i2v_and_sound_steps(make_cosmos3_pipeline) -> None:
)
torch.testing.assert_close(i2v[:, :, 0:1], torch.full((1, 2, 1, 1, 1), 7.0))
pipeline.transformer = pipeline.transformer.__class__(latent_channel_size=2, sound_gen=True, sound_dim=3)
video_result, sound_result = pipeline.diffuse(
pipeline.transformer = pipeline.transformer.__class__(latent_channel_size=2, action_gen=True, action_dim=4)
video_result, action_result = pipeline.diffuse(
latents=latents,
sound_latents=torch.zeros(1, 3, 4),
action_latents=torch.zeros(1, 3, 4),
action_velocity_mask=torch.ones(1, 3, 1),
action_condition_latents=torch.zeros(1, 3, 4),
timesteps=torch.tensor([7, 3]),
cond_ids=_ids(2),
cond_mask=_mask(),
uncond_ids=_ids(1),
uncond_mask=_mask(),
guidance_scale=1.0,
shared_kwargs={"video_shape": (1, 1, 1), "fps": 24.0},
shared_kwargs={"video_shape": (1, 1, 1), "fps": 24.0, "action_domain_ids": torch.tensor([0])},
)
torch.testing.assert_close(video_result, torch.full_like(latents, 4.0))
torch.testing.assert_close(sound_result, torch.full((), 24.0).expand_as(sound_result))
torch.testing.assert_close(action_result, torch.full((), 44.0).expand_as(action_result))
def test_diffuse_keeps_paired_cfg_when_cache_dit_active(make_cosmos3_pipeline) -> None:
@@ -568,6 +599,8 @@ class TestForwardRouting:
def fake_diffuse(**kwargs):
captured["diffuse_calls"].append(kwargs)
outputs = [kwargs["latents"] + len(captured["diffuse_calls"])]
if kwargs.get("action_latents") is not None:
outputs.append(kwargs["action_latents"] + 3.0)
if kwargs.get("sound_latents") is not None:
outputs.append(kwargs["sound_latents"] + 2.0)
return outputs[0] if len(outputs) == 1 else tuple(outputs)
@@ -612,7 +645,7 @@ class TestForwardRouting:
assert captured["flow_shifts"] == expected["flow"]
assert [call[0] for call in pipeline.scheduler.set_timesteps_calls] == expected["steps"]
def test_forward_i2v_and_sound_routes(self, make_cosmos3_pipeline) -> None:
def test_forward_i2v_sound_and_action_routes(self, make_cosmos3_pipeline) -> None:
pipeline = make_cosmos3_pipeline()
captured = self._install_forward_stubs(pipeline)
image_tensor = torch.zeros(1, 3, 16, 16)
@@ -651,6 +684,31 @@ class TestForwardRouting:
assert captured["diffuse_calls"][-1]["sound_latents"] is sound_latents
assert output.output["audio_sample_rate"] == 10
pipeline.transformer = pipeline.transformer.__class__(latent_channel_size=2, action_gen=True, action_dim=4)
output = pipeline.forward(
SimpleNamespace(
prompts=[
{
"prompt": "Pick the block.",
"modalities": ["video"],
"additional_information": {"preprocessed_image": image_tensor},
}
],
sampling_params=make_sampling_params(
height=16,
width=16,
extra_args={
"action_mode": "policy",
"action_chunk_size": 2,
"raw_action_dim": 2,
"domain_name": "bridge_orig_lerobot",
},
),
)
)
assert captured["diffuse_calls"][-1]["shared_kwargs"]["action_domain_ids"].tolist() == [7]
assert output.custom_output["action"].shape == (1, 2, 2)
@pytest.mark.parametrize(
("prompt", "sampling_params", "message"),
[
@@ -30,8 +30,9 @@ def _tiny_cosmos3_config(**overrides):
return config
def test_mrope_position_ids_cover_text_video_and_sound() -> None:
def test_mrope_position_ids_cover_text_video_sound_and_action() -> None:
from vllm_omni.diffusion.models.cosmos3.transformer_cosmos3 import (
compute_mrope_position_ids_action,
compute_mrope_position_ids_sound,
compute_mrope_position_ids_text,
compute_mrope_position_ids_vision,
@@ -62,6 +63,10 @@ def test_mrope_position_ids_cover_text_video_and_sound() -> None:
torch.testing.assert_close(sound_ids[0], torch.tensor([10.0, 10.96, 11.92]))
assert sound_offset == 12
action_ids, action_offset = compute_mrope_position_ids_action(3, temporal_offset=10, action_fps=None)
assert action_ids.tolist() == [[11, 12, 13], [0, 0, 0], [0, 0, 0]]
assert action_offset == 14
@pytest.mark.parametrize(
("key", "value"),
@@ -126,7 +131,7 @@ def test_forward_returns_video_prediction(monkeypatch: pytest.MonkeyPatch) -> No
assert tuple(output.shape) == (1, 2, 1, 2, 2)
def test_sound_modules_follow_injected_sound_dim() -> None:
def test_sound_and_action_modules_follow_config() -> None:
from vllm_omni.diffusion.models.cosmos3.transformer_cosmos3 import Cosmos3VFMTransformer
tiny = _tiny_cosmos3_config()
@@ -137,12 +142,22 @@ def test_sound_modules_follow_injected_sound_dim() -> None:
sound_dim=5,
sound_latent_fps=40.0,
)
with_action = Cosmos3VFMTransformer(
SimpleNamespace(
tf_model_config={**tiny, "action_gen": True, "max_action_dim": 6, "num_embodiment_domains": 9},
dtype=torch.float32,
)
)
assert no_modal.sound_gen is False
assert no_modal.action_gen is False
assert not hasattr(no_modal, "audio_proj_in")
assert not hasattr(no_modal, "action_proj_in")
assert with_sound.sound_dim == 5
assert with_sound.sound_latent_fps == 40.0
assert with_sound.audio_proj_in.in_features == 5
assert with_action.action_dim == 6
assert with_action.action_proj_in.num_domains == 9
@pytest.mark.parametrize(
@@ -163,33 +178,56 @@ def test_transformer_requires_sound_dim_and_fps_when_sound_gen_true(kwargs: dict
)
def test_sound_pack_unpack_validate_shapes() -> None:
def test_sound_and_action_pack_unpack_validate_shapes() -> None:
from vllm_omni.diffusion.models.cosmos3.transformer_cosmos3 import Cosmos3VFMTransformer
model = object.__new__(Cosmos3VFMTransformer)
nn.Module.__init__(model)
model.sound_dim = 3
model.action_dim = 3
sound = torch.arange(2 * 3 * 4, dtype=torch.float32).reshape(2, 3, 4)
action = torch.arange(2 * 4 * 3, dtype=torch.float32).reshape(2, 4, 3)
torch.testing.assert_close(model.unpack_sound(model.pack_sound(sound)), sound)
torch.testing.assert_close(model.unpack_action(model.pack_action(action)), action)
with pytest.raises(ValueError, match="channel mismatch"):
model.pack_sound(torch.zeros(1, 4, 2))
with pytest.raises(ValueError, match="dimension mismatch"):
model.pack_action(torch.zeros(1, 2, 4))
def test_forward_returns_video_and_sound_predictions(monkeypatch: pytest.MonkeyPatch) -> None:
@pytest.mark.parametrize(
("config", "transformer_kwargs", "extra_kwargs", "expected_shapes"),
[
(
_tiny_cosmos3_config(),
{"sound_gen": True, "sound_dim": 3, "sound_latent_fps": 24.0},
{"sound_latents": torch.zeros(1, 3, 4)},
[(1, 2, 1, 2, 2), (1, 3, 4)],
),
(
_tiny_cosmos3_config(action_gen=True, max_action_dim=3, num_embodiment_domains=4),
{},
{"action_latents": torch.zeros(1, 5, 3), "action_domain_ids": torch.tensor([2])},
[(1, 2, 1, 2, 2), (1, 5, 3)],
),
],
)
def test_forward_returns_video_plus_optional_modality_predictions(
monkeypatch: pytest.MonkeyPatch,
config,
transformer_kwargs,
extra_kwargs,
expected_shapes,
) -> None:
from vllm_omni.diffusion.models.cosmos3 import transformer_cosmos3
monkeypatch.setattr(transformer_cosmos3, "_get_ulysses_state", lambda: (1, 0, None))
output = transformer_cosmos3.Cosmos3VFMTransformer(
SimpleNamespace(
tf_model_config=_tiny_cosmos3_config(),
dtype=torch.float32,
),
sound_gen=True,
sound_dim=3,
sound_latent_fps=40.0,
SimpleNamespace(tf_model_config=config, dtype=torch.float32),
**transformer_kwargs,
)(
hidden_states=torch.zeros(1, 2, 1, 2, 2),
timestep=torch.tensor([1.0]),
@@ -197,11 +235,12 @@ def test_forward_returns_video_and_sound_predictions(monkeypatch: pytest.MonkeyP
text_mask=torch.ones(1, 2, dtype=torch.long),
video_shape=(1, 2, 2),
fps=24.0,
sound_latents=torch.zeros(1, 3, 4),
action_noisy_mask=torch.ones(1, 5, 1),
**extra_kwargs,
)
assert isinstance(output, tuple)
assert [tuple(tensor.shape) for tensor in output] == [(1, 2, 1, 2, 2), (1, 3, 4)]
assert [tuple(tensor.shape) for tensor in output] == expected_shapes
def test_forward_with_sound_ulysses_error_mentions_combined_sequence(monkeypatch: pytest.MonkeyPatch) -> None:
@@ -227,7 +266,7 @@ def test_forward_with_sound_ulysses_error_mentions_combined_sequence(monkeypatch
)
def test_compute_rope_freqs_places_text_video_and_sound_positions() -> None:
def test_compute_rope_freqs_places_text_video_action_and_sound_positions() -> None:
from vllm_omni.diffusion.models.cosmos3.transformer_cosmos3 import Cosmos3VFMTransformer
class FakeRotary:
@@ -275,9 +314,11 @@ def test_compute_rope_freqs_places_text_video_and_sound_positions() -> None:
fps=24.0,
device=torch.device("cpu"),
dtype=torch.float32,
t_action=2,
action_start_frame_offset=1,
t_sound=1,
)
_, gen_pos = rotary.position_ids
assert gen_pos.shape == (3, 1, 3)
assert gen_pos[0, 0].tolist() == [102, 103, 102]
assert gen_pos.shape == (3, 1, 5)
assert gen_pos[0, 0].tolist() == [102, 103, 103, 104, 102]