[Model] Add LTX-2.5 Diffusers backend support

Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
Hongsheng Liu committed 2026-08-12 08:58:20 +08:00
1 parent 8b0a6779d4
commit 4b541e4d56
5 files changed
+480 -4

No files matched your search

@@ -17,6 +17,7 @@ from vllm_omni.diffusion.data import (
OmniDiffusionConfig,
)
from vllm_omni.diffusion.models.diffusers_adapter import DiffusersAdapterPipeline
from vllm_omni.diffusion.models.diffusers_adapter.pipeline_utils import LTX25_DISTILLED_SIGMAS
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
@@ -180,6 +181,32 @@ class TestPipelineArgumentsHandling:
with pytest.raises(NotImplementedError):
DiffusersAdapterPipeline(od_config=od_config)
def test_ltx25_original_repo_rejected_before_loading(self, mocker):
mock_from_pretrained = mocker.patch(
"vllm_omni.diffusion.models.diffusers_adapter.pipeline_diffusers_adapter.DiffusionPipeline.from_pretrained"
)
adapter = DiffusersAdapterPipeline(od_config=_make_od_config(model="Lightricks/LTX-2.5"))
with pytest.raises(ValueError, match="Lightricks/LTX-2.5-Diffusers"):
adapter.load_weights()
mock_from_pretrained.assert_not_called()
def test_ltx25_requires_upstream_diffusers_feature(self, mocker):
mocker.patch(
"vllm_omni.diffusion.models.diffusers_adapter.pipeline_diffusers_adapter._supports_ltx25_diffusers",
return_value=False,
)
mock_from_pretrained = mocker.patch(
"vllm_omni.diffusion.models.diffusers_adapter.pipeline_diffusers_adapter.DiffusionPipeline.from_pretrained"
)
adapter = DiffusersAdapterPipeline(od_config=_make_od_config(model="Lightricks/LTX-2.5-Diffusers"))
with pytest.raises(ImportError, match="7564fb016dabda0c943416190fc92398c50b1b20"):
adapter.load_weights()
mock_from_pretrained.assert_not_called()
def test_adapter_load_weights_injects_supported_quantization_config(self, mocker):
(
FakePipelineQuantizationConfig,
@@ -374,6 +401,22 @@ class TestPipelineArgumentsHandling:
assert isinstance(output, DiffusionOutput)
assert output.output == raw_output
def test_adapter_preserves_video_and_singular_audio_output(self):
frames = torch.zeros(1, 2, 3, 4, 3)
audio = torch.zeros(1, 48000)
adapter = DiffusersAdapterPipeline(od_config=_make_od_config())
adapter._pipeline = SimpleNamespace(vocoder=SimpleNamespace(config=SimpleNamespace(output_sampling_rate=48000)))
output = adapter._wrap_output(SimpleNamespace(frames=frames, audio=audio))
assert isinstance(output.output, dict)
assert output.output["payload"]["video"] is frames
assert output.output["payload"]["audio"] is audio
assert output.output["metadata"] == {"audio": {"sample_rate": 48000}}
frames_only = adapter._wrap_output(SimpleNamespace(frames=frames, audio=None))
assert frames_only.output is frames
def test_adapter_build_call_kwargs(self, mocker):
class MockPipeline:
def __call__(
@@ -385,6 +428,7 @@ class TestPipelineArgumentsHandling:
height=None,
width=None,
num_frames=None,
stg_scale=None,
num_images_per_prompt=None,
num_videos_per_prompt=None,
output_type=None,
@@ -406,6 +450,7 @@ class TestPipelineArgumentsHandling:
diffusers_call_kwargs={
"guidance_scale": 1.25,
"eta": 0.3,
"stg_scale": 0.25,
"output_type": "np",
}
)
@@ -426,6 +471,12 @@ class TestPipelineArgumentsHandling:
num_outputs_per_prompt=2,
seed=123,
output_type="pil",
extra_args={
"num_inference_steps": 99,
"stg_scale": 0.75,
"unsupported_argument": True,
"extra_args": {"must_not": "leak"},
},
),
)
@@ -438,6 +489,9 @@ class TestPipelineArgumentsHandling:
assert kwargs["height"] == 320
assert kwargs["width"] == 640
assert kwargs["num_frames"] == 8
assert kwargs["stg_scale"] == 0.75
assert "unsupported_argument" not in kwargs
assert "extra_args" not in kwargs
assert kwargs["num_images_per_prompt"] == 2
assert kwargs["num_videos_per_prompt"] == 2
assert kwargs["output_type"] == "pil"
@@ -629,6 +683,97 @@ class TestPipelineArgumentsHandling:
with pytest.raises(ValueError):
pipeline.forward(DiffusionRequestBatch(requests=[problematic_request]))
def test_ltx25_uses_distilled_sampling_defaults_from_renamed_local_directory(self, mocker, tmp_path):
class LTX2Pipeline:
def __call__(
self,
prompt=None,
num_inference_steps=None,
sigmas=None,
timesteps=None,
guidance_scale=None,
audio_guidance_scale=None,
stg_scale=None,
audio_stg_scale=None,
modality_scale=None,
audio_modality_scale=None,
guidance_rescale=None,
audio_guidance_rescale=None,
num_frames=None,
frame_rate=None,
output_type=None,
generator=None,
):
return None
def to(self, device):
return self
# Local model directories do not retain their Hub repository name.
# Detect 2.5 from its pipeline component metadata instead.
(tmp_path / "model_index.json").write_text(
'{"_class_name":"LTX2Pipeline","duration_head":["diffusers","LTX2DurationHead"]}',
encoding="utf-8",
)
enable_tiling = mocker.Mock()
loaded_pipeline = LTX2Pipeline()
loaded_pipeline.vae = SimpleNamespace(enable_tiling=enable_tiling)
mocker.patch(
"vllm_omni.diffusion.models.diffusers_adapter.pipeline_diffusers_adapter._supports_ltx25_diffusers",
return_value=True,
)
mocker.patch(
"vllm_omni.diffusion.models.diffusers_adapter.pipeline_diffusers_adapter.DiffusionPipeline.from_pretrained",
return_value=loaded_pipeline,
)
adapter = DiffusersAdapterPipeline(
od_config=_make_od_config(
model=str(tmp_path),
vae_use_tiling=True,
)
)
adapter.load_weights()
enable_tiling.assert_called_once_with()
sampling = OmniDiffusionSamplingParams(
num_frames=121,
frame_rate=24.0,
seed=42,
generator_device="cpu",
)
kwargs = adapter._build_call_kwargs(DiffusionRequestBatch(requests=[_make_request(sampling_params=sampling)]))
assert kwargs["sigmas"] == LTX25_DISTILLED_SIGMAS
assert kwargs["num_inference_steps"] == 8
assert kwargs["guidance_scale"] == 1.0
assert kwargs["audio_guidance_scale"] == 1.0
assert kwargs["stg_scale"] == 0.0
assert kwargs["audio_stg_scale"] == 0.0
assert kwargs["modality_scale"] == 1.0
assert kwargs["audio_modality_scale"] == 1.0
assert kwargs["guidance_rescale"] == 0.0
assert kwargs["audio_guidance_rescale"] == 0.0
explicit_steps = OmniDiffusionSamplingParams(num_inference_steps=12, generator_device="cpu")
step_kwargs = adapter._build_call_kwargs(
DiffusionRequestBatch(requests=[_make_request(sampling_params=explicit_steps)])
)
assert step_kwargs["num_inference_steps"] == 12
assert "sigmas" not in step_kwargs
custom_sigmas = [1.0, 0.5]
explicit_schedule = OmniDiffusionSamplingParams(
num_inference_steps=2,
sigmas=custom_sigmas,
generator_device="cpu",
)
schedule_kwargs = adapter._build_call_kwargs(
DiffusionRequestBatch(requests=[_make_request(sampling_params=explicit_schedule)])
)
assert schedule_kwargs["num_inference_steps"] == 2
assert schedule_kwargs["sigmas"] is custom_sigmas
@pytest.mark.advanced_model
@hardware_test(res={"cuda": "L4"}, num_cards=1)