156 lines
5.1 KiB
Python
156 lines
5.1 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import importlib.util
|
|
import sys
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
from tests.examples.helpers import EXAMPLES
|
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
|
|
|
|
|
@functools.cache
|
|
def _load_text_to_video():
|
|
path = EXAMPLES / "offline_inference" / "text_to_video" / "text_to_video.py"
|
|
spec = importlib.util.spec_from_file_location("text_to_video_example", path)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("arguments", "expected"),
|
|
[
|
|
(["--quantization", "mxfp4"], {"quantization": "mxfp4"}),
|
|
(
|
|
["--quantization-config", '{"method":"mxfp4","w4a8_fallback_steps":[0,2]}'],
|
|
{"quantization_config": {"method": "mxfp4", "w4a8_fallback_steps": [0, 2]}},
|
|
),
|
|
],
|
|
)
|
|
def test_quantization_cli_reaches_omni(arguments, expected, monkeypatch):
|
|
mod = _load_text_to_video()
|
|
captured = {}
|
|
|
|
class StopBeforeModelLoadError(Exception):
|
|
pass
|
|
|
|
def capture_omni(**kwargs):
|
|
captured.update(kwargs)
|
|
raise StopBeforeModelLoadError
|
|
|
|
monkeypatch.setattr(sys, "argv", ["text_to_video.py", "--model", "Wan-AI/Wan2.2-T2V-A14B-Diffusers", *arguments])
|
|
monkeypatch.setattr(mod, "current_omni_platform", SimpleNamespace(device_type="cpu"))
|
|
monkeypatch.setattr(mod, "Omni", capture_omni)
|
|
|
|
with pytest.raises(StopBeforeModelLoadError):
|
|
mod.main()
|
|
|
|
assert {key: captured[key] for key in ("quantization", "quantization_config") if key in captured} == expected
|
|
|
|
|
|
def test_quantization_cli_options_are_mutually_exclusive(monkeypatch):
|
|
mod = _load_text_to_video()
|
|
monkeypatch.setattr(
|
|
sys,
|
|
"argv",
|
|
["text_to_video.py", "--quantization", "mxfp4", "--quantization-config", '{"method":"mxfp4"}'],
|
|
)
|
|
|
|
with pytest.raises(SystemExit) as exc:
|
|
mod.parse_args()
|
|
|
|
assert exc.value.code == 2
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("negative_prompt", "expected_negative_prompt"),
|
|
[(None, None), ("", ""), ("blurry", "blurry")],
|
|
)
|
|
def test_text_to_video_builds_canonical_prompt(
|
|
negative_prompt: str | None,
|
|
expected_negative_prompt: str | None,
|
|
) -> None:
|
|
mod = _load_text_to_video()
|
|
result = mod.build_text_to_video_prompt("a robot waves", negative_prompt)
|
|
|
|
assert result["prompt"] == "a robot waves"
|
|
assert result["modalities"] == ["video"]
|
|
if expected_negative_prompt is None:
|
|
assert "negative_prompt" not in result
|
|
else:
|
|
assert result["negative_prompt"] == expected_negative_prompt
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("model", "model_class_name", "preset_name"),
|
|
[
|
|
("Efficient-Large-Model/SANA-Video_2B_480p_diffusers", None, "sana_480p"),
|
|
("Efficient-Large-Model/SANA-Video_2B_720p_diffusers", None, "sana_720p"),
|
|
("/models/custom-checkpoint", "SanaVideoPipeline", "sana_480p"),
|
|
("robbyant/lingbot-video-dense-1.3b", None, "lingbot"),
|
|
("/models/custom-checkpoint", "LingBotVideoPipeline", "lingbot"),
|
|
("Lightricks/LTX-2", "LTX2DistilledPipeline", "ltx2_distilled"),
|
|
("BestWishYsh/Helios-Distilled", None, "helios"),
|
|
("/models/cosmos-edge", "Cosmos3EdgeVFMTransformer", "cosmos3_edge"),
|
|
("/models/vace", "WanVACEPipeline", "vace"),
|
|
],
|
|
)
|
|
def test_detect_preset_is_scoped_to_model_family(
|
|
model: str,
|
|
model_class_name: str | None,
|
|
preset_name: str,
|
|
) -> None:
|
|
mod = _load_text_to_video()
|
|
assert mod._detect_preset(model, model_class_name) is mod._MODEL_PRESETS[preset_name]
|
|
|
|
|
|
def test_lingbot_preset_matches_lingbot_defaults() -> None:
|
|
mod = _load_text_to_video()
|
|
assert mod._MODEL_PRESETS["lingbot"] == {
|
|
"model_class_name": "LingBotVideoPipeline",
|
|
"height": 192,
|
|
"width": 320,
|
|
"num_frames": 9,
|
|
"num_inference_steps": 2,
|
|
"guidance_scale": 3.0,
|
|
"fps": 24,
|
|
"flow_shift": 3.0,
|
|
"output": "lingbot_video_output.mp4",
|
|
}
|
|
|
|
|
|
def test_normalize_float_tensor_preserves_zero_to_one_frames() -> None:
|
|
mod = _load_text_to_video()
|
|
frames = torch.tensor([0.0, 0.25, 1.0, 1.5])
|
|
|
|
assert torch.equal(mod._normalize_float_tensor(frames, "zero_to_one"), torch.tensor([0.0, 0.25, 1.0, 1.0]))
|
|
|
|
|
|
def test_normalize_float_tensor_maps_negative_one_to_one_frames() -> None:
|
|
mod = _load_text_to_video()
|
|
frames = torch.tensor([-1.5, -1.0, 0.0, 1.0, 1.5])
|
|
|
|
assert torch.equal(
|
|
mod._normalize_float_tensor(frames, "negative_one_to_one"),
|
|
torch.tensor([0.0, 0.0, 0.5, 1.0, 1.0]),
|
|
)
|
|
|
|
|
|
def test_normalize_float_tensor_uses_one_contract_for_ambiguous_frames() -> None:
|
|
mod = _load_text_to_video()
|
|
frames = [torch.tensor([0.0, 0.5, 1.0]), torch.tensor([-1.0, 0.0, 1.0])]
|
|
|
|
normalized = [mod._normalize_float_tensor(frame, "negative_one_to_one") for frame in frames]
|
|
|
|
assert torch.equal(normalized[0], torch.tensor([0.5, 0.75, 1.0]))
|
|
assert torch.equal(normalized[1], torch.tensor([0.0, 0.5, 1.0]))
|