94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
748 lines
29 KiB
Python
748 lines
29 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Unit tests for Cosmos3 config, weight mapping, and sampling params."""
|
|
|
|
import importlib.util
|
|
import types
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
import torch
|
|
|
|
from sglang.multimodal_gen.configs.models.dits.cosmos3video import (
|
|
_build_cosmos3_param_names_mapping,
|
|
)
|
|
from sglang.multimodal_gen.configs.pipeline_configs.cosmos3 import Cosmos3Config
|
|
from sglang.multimodal_gen.configs.sample.cosmos3 import Cosmos3SamplingParams
|
|
from sglang.multimodal_gen.configs.sample.sampling_params import DataType
|
|
from sglang.multimodal_gen.registry import (
|
|
_get_config_info,
|
|
get_non_diffusers_pipeline_name,
|
|
)
|
|
from sglang.multimodal_gen.runtime.entrypoints.openai.protocol import (
|
|
ImageGenerationsRequest,
|
|
VideoGenerationsRequest,
|
|
)
|
|
from sglang.multimodal_gen.runtime.entrypoints.openai.video_api import (
|
|
_cosmos3_sampling_param_kwargs,
|
|
_resolve_sound_duration,
|
|
_resolve_video_path,
|
|
)
|
|
from sglang.multimodal_gen.runtime.loader.component_loaders import scheduler_loader
|
|
from sglang.multimodal_gen.runtime.loader.component_loaders.scheduler_loader import (
|
|
SchedulerLoader,
|
|
)
|
|
from sglang.multimodal_gen.runtime.loader.utils import get_param_names_mapping
|
|
from sglang.multimodal_gen.runtime.models.dits.cosmos3video import (
|
|
DomainAwareLinear,
|
|
compute_mrope_position_ids_action,
|
|
compute_mrope_position_ids_sound,
|
|
compute_mrope_position_ids_vision,
|
|
)
|
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.cosmos3 import (
|
|
Cosmos3ImagePreprocessStage,
|
|
Cosmos3LatentPreparationStage,
|
|
Cosmos3TimestepPreparationStage,
|
|
)
|
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.cosmos3_action import (
|
|
EMBODIMENT_TO_DOMAIN_ID,
|
|
)
|
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.cosmos3_guardrails import (
|
|
is_cosmos_guardrail_available,
|
|
)
|
|
|
|
|
|
def _apply(mapping_fn, key):
|
|
"""Return (target_key, merge_index, total_splits) for a diffusers weight key."""
|
|
return mapping_fn(key)
|
|
|
|
|
|
class TestCosmos3ParamNamesMapping(unittest.TestCase):
|
|
"""Verify diffusers → sglang weight key translations."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.fn = staticmethod(
|
|
get_param_names_mapping(_build_cosmos3_param_names_mapping())
|
|
)
|
|
|
|
# --- skipped / dropped weights ---
|
|
|
|
def test_lm_head_dropped(self):
|
|
key, idx, total = _apply(self.fn, "lm_head.weight")
|
|
self.assertEqual(key, "")
|
|
|
|
def test_norm_dropped(self):
|
|
key, idx, total = _apply(self.fn, "norm.weight")
|
|
self.assertEqual(key, "")
|
|
|
|
# --- top-level pass-through ---
|
|
|
|
def test_audio_proj_in_passthrough(self):
|
|
key, *_ = _apply(self.fn, "audio_proj_in.weight")
|
|
self.assertEqual(key, "audio_proj_in.weight")
|
|
|
|
def test_action_proj_in_passthrough(self):
|
|
key, *_ = _apply(self.fn, "action_proj_in.fc.weight")
|
|
self.assertEqual(key, "action_proj_in.fc.weight")
|
|
|
|
def test_embed_tokens(self):
|
|
key, *_ = _apply(self.fn, "embed_tokens.weight")
|
|
self.assertEqual(key, "language_model.embed_tokens.weight")
|
|
|
|
def test_norm_moe_gen(self):
|
|
key, *_ = _apply(self.fn, "norm_moe_gen.weight")
|
|
self.assertEqual(key, "norm_moe_gen.weight")
|
|
|
|
# --- time embedder (pass-through: checkpoint already uses linear_1/2) ---
|
|
|
|
def test_time_embedder_linear_1_passthrough(self):
|
|
key, *_ = _apply(self.fn, "time_embedder.linear_1.weight")
|
|
self.assertEqual(key, "time_embedder.linear_1.weight")
|
|
|
|
def test_time_embedder_linear_2_passthrough(self):
|
|
key, *_ = _apply(self.fn, "time_embedder.linear_2.bias")
|
|
self.assertEqual(key, "time_embedder.linear_2.bias")
|
|
|
|
# --- GEN pathway: Q/K/V merge (must not be claimed by UND catch-all) ---
|
|
|
|
def test_gen_q_proj_key_and_merge_index(self):
|
|
key, idx, total = _apply(self.fn, "layers.3.self_attn.add_q_proj.weight")
|
|
self.assertEqual(key, "gen_layers.3.cross_attention.to_qkv.weight")
|
|
self.assertEqual(idx, 0)
|
|
self.assertEqual(total, 3)
|
|
|
|
def test_gen_k_proj_merge_index(self):
|
|
_, idx, total = _apply(self.fn, "layers.0.self_attn.add_k_proj.weight")
|
|
self.assertEqual(idx, 1)
|
|
self.assertEqual(total, 3)
|
|
|
|
def test_gen_v_proj_merge_index(self):
|
|
_, idx, total = _apply(self.fn, "layers.0.self_attn.add_v_proj.weight")
|
|
self.assertEqual(idx, 2)
|
|
self.assertEqual(total, 3)
|
|
|
|
def test_gen_o_proj(self):
|
|
key, idx, total = _apply(self.fn, "layers.5.self_attn.to_add_out.weight")
|
|
self.assertEqual(key, "gen_layers.5.cross_attention.to_out.weight")
|
|
self.assertIsNone(idx)
|
|
|
|
def test_gen_norm_added_q(self):
|
|
key, idx, _ = _apply(self.fn, "layers.2.self_attn.norm_added_q.weight")
|
|
self.assertEqual(key, "gen_layers.2.cross_attention.norm_q.weight")
|
|
self.assertIsNone(idx)
|
|
|
|
def test_gen_norm_added_k(self):
|
|
key, idx, _ = _apply(self.fn, "layers.2.self_attn.norm_added_k.weight")
|
|
self.assertEqual(key, "gen_layers.2.cross_attention.norm_k.weight")
|
|
self.assertIsNone(idx)
|
|
|
|
def test_gen_mlp_gate_proj(self):
|
|
key, idx, total = _apply(self.fn, "layers.2.mlp_moe_gen.gate_proj.weight")
|
|
self.assertEqual(key, "gen_layers.2.mlp.gate_up_proj.weight")
|
|
self.assertEqual(idx, 0)
|
|
self.assertEqual(total, 2)
|
|
|
|
def test_gen_mlp_up_proj(self):
|
|
key, idx, total = _apply(self.fn, "layers.2.mlp_moe_gen.up_proj.weight")
|
|
self.assertEqual(key, "gen_layers.2.mlp.gate_up_proj.weight")
|
|
self.assertEqual(idx, 1)
|
|
self.assertEqual(total, 2)
|
|
|
|
def test_gen_mlp_down_proj_passthrough(self):
|
|
key, idx, _ = _apply(self.fn, "layers.2.mlp_moe_gen.down_proj.weight")
|
|
self.assertEqual(key, "gen_layers.2.mlp.down_proj.weight")
|
|
self.assertIsNone(idx)
|
|
|
|
# --- UND pathway: Q/K/V merge ---
|
|
|
|
def test_und_q_proj_key_and_merge_index(self):
|
|
key, idx, total = _apply(self.fn, "layers.7.self_attn.to_q.weight")
|
|
self.assertEqual(key, "language_model.layers.7.self_attn.to_qkv.weight")
|
|
self.assertEqual(idx, 0)
|
|
self.assertEqual(total, 3)
|
|
|
|
def test_und_k_proj_merge_index(self):
|
|
_, idx, total = _apply(self.fn, "layers.0.self_attn.to_k.weight")
|
|
self.assertEqual(idx, 1)
|
|
self.assertEqual(total, 3)
|
|
|
|
def test_und_v_proj_merge_index(self):
|
|
_, idx, total = _apply(self.fn, "layers.0.self_attn.to_v.weight")
|
|
self.assertEqual(idx, 2)
|
|
self.assertEqual(total, 3)
|
|
|
|
def test_und_mlp_gate_proj(self):
|
|
key, idx, total = _apply(self.fn, "layers.1.mlp.gate_proj.weight")
|
|
self.assertEqual(key, "language_model.layers.1.mlp.gate_up_proj.weight")
|
|
self.assertEqual(idx, 0)
|
|
self.assertEqual(total, 2)
|
|
|
|
def test_und_mlp_up_proj(self):
|
|
_, idx, total = _apply(self.fn, "layers.1.mlp.up_proj.weight")
|
|
self.assertEqual(idx, 1)
|
|
self.assertEqual(total, 2)
|
|
|
|
def test_und_layernorm_catch_all(self):
|
|
key, idx, _ = _apply(self.fn, "layers.0.input_layernorm.weight")
|
|
self.assertEqual(key, "language_model.layers.0.input_layernorm.weight")
|
|
self.assertIsNone(idx)
|
|
|
|
# --- ordering: GEN patterns must not be swallowed by UND catch-all ---
|
|
|
|
def test_gen_layernorm_not_mapped_to_und(self):
|
|
key, *_ = _apply(self.fn, "layers.0.input_layernorm_moe_gen.weight")
|
|
self.assertIn("gen_layers", key)
|
|
self.assertNotIn("language_model", key)
|
|
|
|
def test_gen_post_attention_layernorm_not_mapped_to_und(self):
|
|
key, *_ = _apply(self.fn, "layers.4.post_attention_layernorm_moe_gen.weight")
|
|
self.assertIn("gen_layers", key)
|
|
self.assertNotIn("language_model", key)
|
|
|
|
|
|
class TestCosmos3AdjustNumFrames(unittest.TestCase):
|
|
"""Verify VAE-aligned frame rounding in Cosmos3Config."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.cfg = Cosmos3Config()
|
|
|
|
def test_single_frame_t2i_bypass(self):
|
|
self.assertEqual(self.cfg.adjust_num_frames(1), 1)
|
|
|
|
def test_already_aligned(self):
|
|
# (81 - 1) = 80, 80 % 4 == 0
|
|
self.assertEqual(self.cfg.adjust_num_frames(81), 81)
|
|
|
|
def test_rounds_down_to_nearest_aligned(self):
|
|
# (83 - 1) = 82 → floor(82/4)*4 + 1 = 81
|
|
self.assertEqual(self.cfg.adjust_num_frames(83), 81)
|
|
# (6 - 1) = 5 → floor(5/4)*4 + 1 = 5
|
|
self.assertEqual(self.cfg.adjust_num_frames(6), 5)
|
|
|
|
def test_minimum_video_frame_count(self):
|
|
# 2 frames: (2-1)=1, 1//4=0, 0*4+1=1 → rounds to 1, but 1 is T2I — still valid
|
|
self.assertEqual(self.cfg.adjust_num_frames(2), 1)
|
|
|
|
|
|
class TestCosmos3SchedulerConfig(unittest.TestCase):
|
|
"""Verify Cosmos3 scheduler class and flow-shift defaults."""
|
|
|
|
def test_config_overrides_checkpoint_scheduler_class(self):
|
|
cfg = Cosmos3Config()
|
|
self.assertEqual(cfg.scheduler_class_override, "FlowUniPCMultistepScheduler")
|
|
self.assertIsNone(cfg.flow_shift)
|
|
|
|
def test_scheduler_loader_uses_configured_class_override(self):
|
|
class FakeScheduler:
|
|
def __init__(self, **config):
|
|
self.config = config
|
|
|
|
server_args = types.SimpleNamespace(
|
|
pipeline_config=types.SimpleNamespace(
|
|
scheduler_class_override="FlowUniPCMultistepScheduler",
|
|
flow_shift=None,
|
|
)
|
|
)
|
|
with (
|
|
mock.patch.object(
|
|
scheduler_loader,
|
|
"get_diffusers_component_config",
|
|
return_value={"_class_name": "CheckpointScheduler", "foo": "bar"},
|
|
),
|
|
mock.patch.object(
|
|
scheduler_loader.ModelRegistry,
|
|
"resolve_model_cls",
|
|
return_value=(FakeScheduler, None),
|
|
) as resolve,
|
|
):
|
|
scheduler = SchedulerLoader().load_customized("unused", server_args)
|
|
|
|
resolve.assert_called_once_with("FlowUniPCMultistepScheduler")
|
|
self.assertEqual(scheduler.config["foo"], "bar")
|
|
|
|
@staticmethod
|
|
def _stage():
|
|
stage = Cosmos3TimestepPreparationStage.__new__(Cosmos3TimestepPreparationStage)
|
|
stage.scheduler = types.SimpleNamespace(
|
|
config=types.SimpleNamespace(flow_shift=1.0)
|
|
)
|
|
return stage
|
|
|
|
@staticmethod
|
|
def _batch(**kwargs):
|
|
sp_kwargs = kwargs.pop("sp_kwargs", {})
|
|
return types.SimpleNamespace(
|
|
sampling_params=Cosmos3SamplingParams(prompt="t", **sp_kwargs),
|
|
data_type=kwargs.pop("data_type", DataType.VIDEO),
|
|
preprocessed_image=kwargs.pop("preprocessed_image", None),
|
|
preprocessed_video=kwargs.pop("preprocessed_video", None),
|
|
)
|
|
|
|
def test_per_mode_flow_shift_defaults(self):
|
|
stage = self._stage()
|
|
self.assertEqual(
|
|
stage._default_flow_shift_for_mode(self._batch(data_type=DataType.IMAGE)),
|
|
3.0,
|
|
)
|
|
self.assertEqual(
|
|
stage._default_flow_shift_for_mode(
|
|
self._batch(preprocessed_image=torch.empty(1))
|
|
),
|
|
10.0,
|
|
)
|
|
self.assertEqual(
|
|
stage._default_flow_shift_for_mode(
|
|
self._batch(preprocessed_video=torch.empty(1))
|
|
),
|
|
10.0,
|
|
)
|
|
self.assertEqual(stage._default_flow_shift_for_mode(self._batch()), 10.0)
|
|
self.assertEqual(
|
|
stage._default_flow_shift_for_mode(
|
|
self._batch(sp_kwargs={"action_mode": "policy"})
|
|
),
|
|
10.0,
|
|
)
|
|
|
|
|
|
class TestCosmos3SamplingParamsDataType(unittest.TestCase):
|
|
"""Verify num_frames==1 flips data_type to IMAGE before file name derivation."""
|
|
|
|
def test_single_frame_sets_image_data_type(self):
|
|
params = Cosmos3SamplingParams(prompt="test", num_frames=1)
|
|
params._set_output_file_name()
|
|
self.assertEqual(params.data_type, DataType.IMAGE)
|
|
self.assertTrue(
|
|
params.output_file_name.endswith((".png", ".jpg", ".jpeg", ".webp")),
|
|
f"Expected image extension, got: {params.output_file_name}",
|
|
)
|
|
|
|
def test_multi_frame_keeps_video_data_type(self):
|
|
params = Cosmos3SamplingParams(prompt="test", num_frames=81)
|
|
params._set_output_file_name()
|
|
self.assertEqual(params.data_type, DataType.VIDEO)
|
|
|
|
def test_default_num_frames_is_video(self):
|
|
params = Cosmos3SamplingParams(prompt="test")
|
|
params._set_output_file_name()
|
|
self.assertEqual(params.data_type, DataType.VIDEO)
|
|
|
|
|
|
class TestCosmos3ModelResolution(unittest.TestCase):
|
|
"""Verify Cosmos3 checkpoints resolve to the native SGLang pipeline."""
|
|
|
|
def test_hf_checkpoint_uses_registered_native_pipeline_config(self):
|
|
for model_path in (
|
|
"nvidia/Cosmos3-Nano",
|
|
"nvidia/Cosmos3-Super",
|
|
"nvidia/Cosmos3-Super-Text2Image",
|
|
"nvidia/Cosmos3-Super-Image2Video",
|
|
):
|
|
with self.subTest(model_path=model_path):
|
|
self.assertIsNone(get_non_diffusers_pipeline_name(model_path))
|
|
config_info = _get_config_info(model_path)
|
|
self.assertIsNotNone(config_info)
|
|
self.assertIs(config_info.sampling_param_cls, Cosmos3SamplingParams)
|
|
self.assertIs(config_info.pipeline_config_cls, Cosmos3Config)
|
|
|
|
|
|
class TestCosmos3OpenAIProtocol(unittest.TestCase):
|
|
"""Verify Cosmos3 modality knobs are exposed by the video HTTP schema."""
|
|
|
|
def test_cosmos3_template_fields_remain_extra_fields(self):
|
|
for request_cls in (ImageGenerationsRequest, VideoGenerationsRequest):
|
|
with self.subTest(request_cls=request_cls.__name__):
|
|
self.assertIn("max_sequence_length", request_cls.model_fields)
|
|
self.assertIn("flow_shift", request_cls.model_fields)
|
|
self.assertNotIn("use_duration_template", request_cls.model_fields)
|
|
self.assertNotIn("use_resolution_template", request_cls.model_fields)
|
|
self.assertNotIn("use_system_prompt", request_cls.model_fields)
|
|
self.assertNotIn("use_guardrails", request_cls.model_fields)
|
|
|
|
def test_cosmos3_modal_fields_pass_through_as_extras(self):
|
|
for field_name in ("video_path", "video_url"):
|
|
with self.subTest(field_name=field_name):
|
|
self.assertIn(field_name, VideoGenerationsRequest.model_fields)
|
|
|
|
modal_values = {
|
|
"generate_sound": True,
|
|
"sound_duration": 3.0,
|
|
"condition_frame_indexes": [0, 2],
|
|
"condition_frame_indexes_vision": [0, 2],
|
|
"condition_video_keep": "last",
|
|
"action_mode": "policy",
|
|
"domain_id": 1,
|
|
"domain_name": "umi",
|
|
"raw_action_dim": 9,
|
|
"action_fps": 30.0,
|
|
"action": [0.0, 1.0],
|
|
"action_view_point": "ego_view",
|
|
"action_normalization": "mean_std",
|
|
}
|
|
req = VideoGenerationsRequest(prompt="test", **modal_values)
|
|
for field_name, value in modal_values.items():
|
|
with self.subTest(field_name=field_name):
|
|
self.assertNotIn(field_name, VideoGenerationsRequest.model_fields)
|
|
self.assertEqual(getattr(req, field_name), value)
|
|
|
|
def test_cosmos3_http_aliases_map_to_sampling_params(self):
|
|
req = VideoGenerationsRequest(
|
|
prompt="test",
|
|
video_url="https://example.com/input.mp4",
|
|
generate_sound=True,
|
|
condition_frame_indexes_vision=[0, 2],
|
|
condition_video_keep="last",
|
|
action_mode="policy",
|
|
domain_name="umi",
|
|
raw_action_dim=9,
|
|
action_fps=30.0,
|
|
action_view_point="ego_view",
|
|
)
|
|
|
|
self.assertEqual(_resolve_video_path(req), "https://example.com/input.mp4")
|
|
|
|
kwargs = _cosmos3_sampling_param_kwargs(req, num_frames=48, fps=24)
|
|
self.assertEqual(kwargs["sound_duration"], 2.0)
|
|
self.assertEqual(kwargs["condition_frame_indexes"], [0, 2])
|
|
self.assertEqual(kwargs["condition_video_keep"], "last")
|
|
self.assertEqual(kwargs["action_mode"], "policy")
|
|
self.assertEqual(kwargs["domain_name"], "umi")
|
|
self.assertEqual(kwargs["raw_action_dim"], 9)
|
|
self.assertEqual(kwargs["action_fps"], 30.0)
|
|
self.assertEqual(kwargs["action_view_point"], "ego_view")
|
|
|
|
def test_generate_sound_false_disables_sound_duration(self):
|
|
req = VideoGenerationsRequest(
|
|
prompt="test", generate_sound=False, sound_duration=3.0
|
|
)
|
|
self.assertEqual(
|
|
_resolve_sound_duration(req, num_frames=48, fps=24),
|
|
0.0,
|
|
)
|
|
|
|
|
|
class TestCosmos3Guardrails(unittest.TestCase):
|
|
"""Verify optional guardrail dependency handling."""
|
|
|
|
def setUp(self):
|
|
is_cosmos_guardrail_available.cache_clear()
|
|
|
|
def tearDown(self):
|
|
is_cosmos_guardrail_available.cache_clear()
|
|
|
|
def test_guardrail_availability_matches_package_spec(self):
|
|
self.assertEqual(
|
|
is_cosmos_guardrail_available(),
|
|
importlib.util.find_spec("cosmos_guardrail") is not None,
|
|
)
|
|
|
|
@mock.patch("importlib.util.find_spec", return_value=None)
|
|
def test_missing_guardrail_package_reports_unavailable(self, _):
|
|
self.assertFalse(is_cosmos_guardrail_available())
|
|
|
|
|
|
class TestCosmos3MRoPE(unittest.TestCase):
|
|
"""mRoPE position-ID computation for vision / sound / action token grids."""
|
|
|
|
DEVICE = torch.device("cpu")
|
|
|
|
def test_vision_default_args_unchanged(self):
|
|
# With base_temporal_compression_factor=None and start_frame_offset=0
|
|
# the token and base rates cancel, so t-index == frame index.
|
|
pos, _ = compute_mrope_position_ids_vision(
|
|
grid_t=21,
|
|
grid_h=1,
|
|
grid_w=1,
|
|
temporal_offset=0,
|
|
device=self.DEVICE,
|
|
fps=24.0,
|
|
base_fps=24.0,
|
|
temporal_compression_factor=4,
|
|
)
|
|
self.assertEqual(tuple(pos.shape), (3, 21))
|
|
self.assertAlmostEqual(float(pos[0, 0]), 0.0, places=5)
|
|
self.assertAlmostEqual(float(pos[0, 20]), 20.0, places=5)
|
|
|
|
def test_sound_grid_shape_and_scaling(self):
|
|
pos, _ = compute_mrope_position_ids_sound(
|
|
grid_t=10,
|
|
temporal_offset=0,
|
|
sound_latent_fps=25.0,
|
|
device=self.DEVICE,
|
|
base_fps=24.0,
|
|
temporal_compression_factor_sound=1,
|
|
)
|
|
# (T, 1, 1) grid -> spatial axes are all zero.
|
|
self.assertEqual(tuple(pos.shape), (3, 10))
|
|
self.assertTrue(torch.all(pos[1] == 0))
|
|
self.assertTrue(torch.all(pos[2] == 0))
|
|
# t-index = i / sound_fps * base_fps = i / 25 * 24.
|
|
self.assertAlmostEqual(float(pos[0, 5]), 5 / 25 * 24, places=4)
|
|
|
|
def test_action_uses_video_base_compression_and_offset(self):
|
|
# Action runs at frame rate (tcf=1) but is scaled by the video's
|
|
# base_temporal_compression_factor=4, and shifted by start_frame_offset.
|
|
pos, _ = compute_mrope_position_ids_action(
|
|
grid_t=16,
|
|
temporal_offset=0,
|
|
action_fps=10.0,
|
|
device=self.DEVICE,
|
|
base_fps=24.0,
|
|
base_temporal_compression_factor=4,
|
|
start_frame_offset=1,
|
|
)
|
|
self.assertEqual(tuple(pos.shape), (3, 16))
|
|
self.assertTrue(torch.all(pos[1] == 0))
|
|
self.assertTrue(torch.all(pos[2] == 0))
|
|
# t-index[i] = (i + start_frame_offset) / action_fps * (base_fps / base_tcf)
|
|
# = (i + 1) / 10 * (24 / 4) = (i + 1) * 0.6
|
|
self.assertAlmostEqual(float(pos[0, 0]), 0.6, places=4)
|
|
self.assertAlmostEqual(float(pos[0, 15]), 16 * 0.6, places=4)
|
|
|
|
def test_action_offset_zero(self):
|
|
pos, _ = compute_mrope_position_ids_action(
|
|
grid_t=8,
|
|
temporal_offset=0,
|
|
action_fps=10.0,
|
|
device=self.DEVICE,
|
|
base_fps=24.0,
|
|
base_temporal_compression_factor=4,
|
|
start_frame_offset=0,
|
|
)
|
|
self.assertAlmostEqual(float(pos[0, 0]), 0.0, places=5)
|
|
|
|
def test_action_aligns_with_video_positions(self):
|
|
# Action frames at frame rate should share the video's temporal frame:
|
|
# every 4th action token lands on the next video latent-frame position.
|
|
media_offset = 100
|
|
vid, _ = compute_mrope_position_ids_vision(
|
|
grid_t=5,
|
|
grid_h=1,
|
|
grid_w=1,
|
|
temporal_offset=media_offset,
|
|
device=self.DEVICE,
|
|
fps=24.0,
|
|
base_fps=24.0,
|
|
temporal_compression_factor=4,
|
|
)
|
|
act, _ = compute_mrope_position_ids_action(
|
|
grid_t=16,
|
|
temporal_offset=media_offset,
|
|
action_fps=24.0,
|
|
device=self.DEVICE,
|
|
base_fps=24.0,
|
|
base_temporal_compression_factor=4,
|
|
start_frame_offset=0,
|
|
)
|
|
# video latent frame 1 sits at media_offset+1; action frame 4 (4 frames
|
|
# per latent at tcf=4) lands at the same temporal position.
|
|
self.assertAlmostEqual(float(vid[0, 1]), float(act[0, 4]), places=4)
|
|
|
|
|
|
class TestCosmos3DomainAwareLinear(unittest.TestCase):
|
|
"""Per-domain action projection."""
|
|
|
|
def test_rank3_and_rank2_shapes(self):
|
|
layer = DomainAwareLinear(input_size=7, output_size=64, num_domains=32)
|
|
x3 = torch.randn(2, 16, 7)
|
|
out3 = layer(x3, torch.tensor([1, 5]))
|
|
self.assertEqual(tuple(out3.shape), (2, 16, 64))
|
|
x2 = torch.randn(3, 7)
|
|
out2 = layer(x2, torch.tensor([0, 1, 2]))
|
|
self.assertEqual(tuple(out2.shape), (3, 64))
|
|
|
|
def test_distinct_domains_give_distinct_outputs(self):
|
|
torch.manual_seed(0)
|
|
layer = DomainAwareLinear(input_size=4, output_size=8, num_domains=4)
|
|
x = torch.randn(1, 3, 4)
|
|
out_a = layer(x, torch.tensor([0]))
|
|
out_b = layer(x, torch.tensor([2]))
|
|
self.assertFalse(torch.allclose(out_a, out_b))
|
|
|
|
def test_scalar_domain_id_promoted(self):
|
|
layer = DomainAwareLinear(input_size=4, output_size=8, num_domains=4)
|
|
out = layer(torch.randn(1, 2, 4), torch.tensor(3))
|
|
self.assertEqual(tuple(out.shape), (1, 2, 8))
|
|
|
|
|
|
class TestCosmos3ConditionIndexes(unittest.TestCase):
|
|
"""Vision condition-frame resolution across V2V and action modes."""
|
|
|
|
@staticmethod
|
|
def _batch(num_frames=61, **sp_kwargs):
|
|
sp = Cosmos3SamplingParams(prompt="t", num_frames=num_frames, **sp_kwargs)
|
|
return types.SimpleNamespace(sampling_params=sp, num_frames=num_frames)
|
|
|
|
def test_v2v_default(self):
|
|
idx = Cosmos3ImagePreprocessStage._resolve_condition_indexes(self._batch())
|
|
self.assertEqual(idx, [0, 1])
|
|
|
|
def test_v2v_explicit_sorted_unique(self):
|
|
idx = Cosmos3ImagePreprocessStage._resolve_condition_indexes(
|
|
self._batch(condition_frame_indexes=[2, 0, 2])
|
|
)
|
|
self.assertEqual(idx, [0, 2])
|
|
|
|
def test_inverse_dynamics_conditions_all_latent_frames(self):
|
|
# 61 frames -> (61-1)//4 + 1 = 16 latent frames, all locked.
|
|
idx = Cosmos3ImagePreprocessStage._resolve_condition_indexes(
|
|
self._batch(num_frames=61, action_mode="inverse_dynamics")
|
|
)
|
|
self.assertEqual(idx, list(range(16)))
|
|
|
|
|
|
class TestCosmos3DomainResolution(unittest.TestCase):
|
|
"""Embodiment domain-id resolution for action generation."""
|
|
|
|
@staticmethod
|
|
def _batch(**sp_kwargs):
|
|
sp = Cosmos3SamplingParams(prompt="t", **sp_kwargs)
|
|
return types.SimpleNamespace(sampling_params=sp)
|
|
|
|
def test_explicit_domain_id(self):
|
|
self.assertEqual(
|
|
Cosmos3LatentPreparationStage._resolve_domain_id(self._batch(domain_id=7)),
|
|
7,
|
|
)
|
|
|
|
def test_domain_name_lookup(self):
|
|
self.assertEqual(
|
|
Cosmos3LatentPreparationStage._resolve_domain_id(
|
|
self._batch(domain_name="av")
|
|
),
|
|
EMBODIMENT_TO_DOMAIN_ID["av"],
|
|
)
|
|
self.assertEqual(
|
|
Cosmos3LatentPreparationStage._resolve_domain_id(
|
|
self._batch(domain_name="umi")
|
|
),
|
|
EMBODIMENT_TO_DOMAIN_ID["umi"],
|
|
)
|
|
|
|
def test_missing_domain_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
Cosmos3LatentPreparationStage._resolve_domain_id(self._batch())
|
|
|
|
def test_unknown_domain_name_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
Cosmos3LatentPreparationStage._resolve_domain_id(
|
|
self._batch(domain_name="not_a_robot")
|
|
)
|
|
|
|
|
|
class TestCosmos3ActionLatentPrep(unittest.TestCase):
|
|
"""Action latent / mask preparation per action mode."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# Bypass PipelineStage.__init__ (needs global server args); only the
|
|
# transformer's action_dim and log_info are used by _prepare_action_latents.
|
|
cls.stage = Cosmos3LatentPreparationStage.__new__(Cosmos3LatentPreparationStage)
|
|
cls.stage.transformer = types.SimpleNamespace(action_dim=64)
|
|
cls.stage.log_info = lambda *a, **k: None
|
|
cls.device = torch.device("cpu")
|
|
cls.dtype = torch.float32
|
|
|
|
def _run(self, num_frames=17, **sp_kwargs):
|
|
sp = Cosmos3SamplingParams(prompt="t", num_frames=num_frames, **sp_kwargs)
|
|
batch = types.SimpleNamespace(
|
|
sampling_params=sp, num_frames=num_frames, extra={}
|
|
)
|
|
gen = torch.Generator(device=self.device).manual_seed(0)
|
|
self.stage._prepare_action_latents(batch, gen, self.device, self.dtype)
|
|
return batch
|
|
|
|
def test_forward_dynamics_clean_conditioning(self):
|
|
batch = self._run(
|
|
action_mode="forward_dynamics",
|
|
domain_name="agibotworld",
|
|
action=[[0.1] * 29 for _ in range(16)],
|
|
)
|
|
# action_chunk_size = num_frames - 1 = 16; padded to action_dim 64.
|
|
self.assertEqual(tuple(batch.action_latents.shape), (1, 16, 64))
|
|
# raw_action_dim is derived from the embodiment (agibotworld -> 29).
|
|
self.assertEqual(batch.extra["raw_action_dim"], 29)
|
|
self.assertEqual(batch.extra["action_start_frame_offset"], 1)
|
|
self.assertEqual(
|
|
int(batch.extra["action_domain_ids"][0]),
|
|
EMBODIMENT_TO_DOMAIN_ID["agibotworld"],
|
|
)
|
|
# forward_dynamics: action is clean conditioning -> velocity mask all zero.
|
|
self.assertTrue(torch.all(batch.extra["action_velocity_mask"] == 0))
|
|
|
|
def test_policy_denoises_from_noise(self):
|
|
batch = self._run(
|
|
action_mode="policy", domain_name="droid_lerobot", raw_action_dim=10
|
|
)
|
|
self.assertEqual(tuple(batch.action_latents.shape), (1, 16, 64))
|
|
self.assertEqual(batch.extra["raw_action_dim"], 10)
|
|
# policy: action fully denoised -> velocity mask all one.
|
|
self.assertTrue(torch.all(batch.extra["action_velocity_mask"] == 1))
|
|
# padding dims beyond raw_action_dim start at zero.
|
|
self.assertTrue(torch.all(batch.action_latents[:, :, 10:] == 0))
|
|
|
|
def test_inverse_dynamics_denoises_from_noise(self):
|
|
batch = self._run(
|
|
num_frames=61,
|
|
action_mode="inverse_dynamics",
|
|
domain_name="av",
|
|
raw_action_dim=9,
|
|
)
|
|
self.assertEqual(tuple(batch.action_latents.shape), (1, 60, 64))
|
|
self.assertTrue(torch.all(batch.extra["action_velocity_mask"] == 1))
|
|
|
|
def test_forward_dynamics_requires_action(self):
|
|
with self.assertRaises(ValueError):
|
|
self._run(action_mode="forward_dynamics", domain_name="agibotworld")
|
|
|
|
def test_policy_requires_raw_action_dim(self):
|
|
# policy has no input action to infer from, so it needs raw_action_dim
|
|
# from either the embodiment or an explicit value. With only a numeric
|
|
# domain_id (no embodiment name) and no raw_action_dim, it must raise.
|
|
with self.assertRaises(ValueError):
|
|
self._run(action_mode="policy", domain_id=0)
|
|
|
|
def test_policy_raw_action_dim_from_embodiment(self):
|
|
# droid_lerobot -> 10, so policy no longer needs an explicit raw dim.
|
|
batch = self._run(action_mode="policy", domain_name="droid_lerobot")
|
|
self.assertEqual(batch.extra["raw_action_dim"], 10)
|
|
|
|
def test_unknown_action_mode_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
self._run(action_mode="teleport", domain_id=0)
|
|
|
|
|
|
class TestCosmos3ModalitySamplingParams(unittest.TestCase):
|
|
"""Sound / V2V / action sampling-param fields and defaults."""
|
|
|
|
def test_sound_duration_default_and_set(self):
|
|
self.assertEqual(Cosmos3SamplingParams(prompt="t").sound_duration, 0.0)
|
|
self.assertEqual(
|
|
Cosmos3SamplingParams(prompt="t", sound_duration=3.0).sound_duration, 3.0
|
|
)
|
|
|
|
def test_v2v_fields(self):
|
|
sp = Cosmos3SamplingParams(
|
|
prompt="t", video_path="in.mp4", condition_frame_indexes=[0, 1]
|
|
)
|
|
self.assertEqual(sp.video_path, "in.mp4")
|
|
self.assertEqual(sp.condition_frame_indexes, [0, 1])
|
|
self.assertEqual(sp.condition_video_keep, "first")
|
|
|
|
def test_action_fields_default_none(self):
|
|
sp = Cosmos3SamplingParams(prompt="t")
|
|
for field in (
|
|
"action_mode",
|
|
"domain_id",
|
|
"domain_name",
|
|
"raw_action_dim",
|
|
"action_fps",
|
|
"action",
|
|
):
|
|
self.assertIsNone(getattr(sp, field))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|