Files

229 lines
9.7 KiB
Python
Raw Permalink Normal View History

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""
Unit tests for diffusion engine plugin extensibility hooks.
This module tests:
- Platform hooks: get_diffusion_worker_cls, get_diffusion_model_runner_cls
- Registry API: register_diffusion_model
- Worker integration: model runner resolved via platform hook
"""
from types import SimpleNamespace
from unittest.mock import Mock, patch
import pytest
from vllm_omni.diffusion.registry import (
_DIFFUSION_IR_OP_PRIORITY_FUNCS,
_DIFFUSION_MODELS,
_DIFFUSION_POST_PROCESS_FUNCS,
_DIFFUSION_PRE_PROCESS_FUNCS,
_DIFFUSION_PREFIX_CACHE_FUNCS,
get_diffusion_post_process_func,
get_diffusion_pre_process_func,
get_diffusion_prefix_cache_func,
register_diffusion_model,
)
from vllm_omni.platforms.interface import OmniPlatform, OmniPlatformEnum
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class TestPlatformDiffusionHooks:
"""Test OmniPlatform diffusion hook defaults."""
def test_get_diffusion_worker_cls_default(self):
"""Test default diffusion worker class path."""
result = OmniPlatform.get_diffusion_worker_cls()
assert result == "vllm_omni.diffusion.worker.diffusion_worker.DiffusionWorker"
def test_get_diffusion_model_runner_cls_default(self):
"""Test default diffusion model runner class path."""
result = OmniPlatform.get_diffusion_model_runner_cls()
assert result == "vllm_omni.diffusion.worker.diffusion_model_runner.DiffusionModelRunner"
def test_get_diffusion_kv_block_tables_cls_default(self):
"""Test the default paged-KV BlockTables implementation."""
from vllm.v1.worker.gpu.block_table import BlockTables
assert OmniPlatform.get_diffusion_kv_block_tables_cls() is BlockTables
def test_oot_enum_exists(self):
"""Test OOT is a valid platform enum value."""
assert OmniPlatformEnum.OOT.value == "oot"
def test_is_out_of_tree(self):
"""Test is_out_of_tree() returns True for OOT platform."""
platform = OmniPlatform.__new__(OmniPlatform)
platform._omni_enum = OmniPlatformEnum.OOT
assert platform.is_out_of_tree() is True
assert platform.is_cuda() is False
class TestRegisterDiffusionModel:
"""Test register_diffusion_model public API."""
@pytest.fixture(autouse=True)
def cleanup_registry(self):
"""Restore global registry dicts after each test."""
original_models = _DIFFUSION_MODELS.copy()
original_pre = _DIFFUSION_PRE_PROCESS_FUNCS.copy()
original_post = _DIFFUSION_POST_PROCESS_FUNCS.copy()
original_ir_op_priority = _DIFFUSION_IR_OP_PRIORITY_FUNCS.copy()
original_prefix_cache = _DIFFUSION_PREFIX_CACHE_FUNCS.copy()
yield
_DIFFUSION_MODELS.clear()
_DIFFUSION_MODELS.update(original_models)
_DIFFUSION_PRE_PROCESS_FUNCS.clear()
_DIFFUSION_PRE_PROCESS_FUNCS.update(original_pre)
_DIFFUSION_POST_PROCESS_FUNCS.clear()
_DIFFUSION_POST_PROCESS_FUNCS.update(original_post)
_DIFFUSION_IR_OP_PRIORITY_FUNCS.clear()
_DIFFUSION_IR_OP_PRIORITY_FUNCS.update(original_ir_op_priority)
_DIFFUSION_PREFIX_CACHE_FUNCS.clear()
_DIFFUSION_PREFIX_CACHE_FUNCS.update(original_prefix_cache)
def test_register_new_model(self):
"""Test registering a new diffusion model with pre/post process functions."""
register_diffusion_model(
model_arch="TestPipeline",
module_name="test_plugin.diffusion.pipeline",
class_name="TestPipeline",
pre_process_func_name="test_pre_process",
post_process_func_name="test_post_process",
ir_op_priority_func_name="test_ir_op_priority",
prefix_cache_func_name="test_prefix_cache",
)
assert "TestPipeline" in _DIFFUSION_MODELS
assert _DIFFUSION_MODELS["TestPipeline"] == (
"test_plugin.diffusion.pipeline",
"",
"TestPipeline",
)
assert _DIFFUSION_PRE_PROCESS_FUNCS["TestPipeline"] == "test_pre_process"
assert _DIFFUSION_POST_PROCESS_FUNCS["TestPipeline"] == "test_post_process"
assert _DIFFUSION_IR_OP_PRIORITY_FUNCS["TestPipeline"] == "test_ir_op_priority"
assert _DIFFUSION_PREFIX_CACHE_FUNCS["TestPipeline"] == "test_prefix_cache"
def test_registered_prefix_hook_is_loaded_from_plugin(self):
hook = Mock()
factory = Mock(return_value=hook)
register_diffusion_model(
"TestPrefixPipeline", "test_plugin.pipeline", "TestPrefixPipeline", prefix_cache_func_name="prefix_factory"
)
config = SimpleNamespace(model_class_name="TestPrefixPipeline")
with patch(
"vllm_omni.diffusion.registry.importlib.import_module", return_value=SimpleNamespace(prefix_factory=factory)
) as load:
assert get_diffusion_prefix_cache_func(config) is hook
load.assert_called_once_with("test_plugin.pipeline")
factory.assert_called_once_with(config)
def test_builtin_hunyuan_prefix_hook(self):
from vllm_omni.diffusion.models.hunyuan_image3.request_layout import prepare_hunyuan_prefix_cache
assert (
get_diffusion_prefix_cache_func(SimpleNamespace(model_class_name="HunyuanImage3ForCausalMM"))
is prepare_hunyuan_prefix_cache
)
def test_register_model_accepts_deprecated_action_postprocess_keyword(self):
"""Deprecated action hook keyword is accepted but not registered."""
register_diffusion_model(
model_arch="LegacyActionPipeline",
module_name="test_plugin.diffusion.pipeline",
class_name="LegacyActionPipeline",
post_process_func_name="test_post_process",
action_post_process_func_name="test_action_post_process",
)
assert "LegacyActionPipeline" in _DIFFUSION_MODELS
assert _DIFFUSION_POST_PROCESS_FUNCS["LegacyActionPipeline"] == "test_post_process"
@pytest.mark.parametrize(
("model_class_name", "get_process_func"),
[
("QwenImagePipeline", get_diffusion_post_process_func),
("WanImageToVideoPipeline", get_diffusion_post_process_func),
("WanImageToVideoPipeline", get_diffusion_pre_process_func),
],
)
def test_diffusers_backend_skips_native_process_hooks(model_class_name, get_process_func):
od_config = SimpleNamespace(
model_class_name=model_class_name,
custom_pipeline_args=None,
diffusion_load_format="diffusers",
)
with patch("vllm_omni.diffusion.registry._load_process_func") as load_process_func:
assert get_process_func(od_config) is None
load_process_func.assert_not_called()
@pytest.mark.parametrize(
("model_class_name", "get_process_func"),
[
("QwenImagePipeline", get_diffusion_post_process_func),
("WanImageToVideoPipeline", get_diffusion_post_process_func),
("WanImageToVideoPipeline", get_diffusion_pre_process_func),
],
)
def test_custom_pipeline_preserves_native_process_hooks(model_class_name, get_process_func):
process_func = Mock()
od_config = SimpleNamespace(
model_class_name=model_class_name,
custom_pipeline_args={"pipeline_class": "custom.Pipeline"},
diffusion_load_format="diffusers",
)
with patch("vllm_omni.diffusion.registry._load_process_func", return_value=process_func) as load_process_func:
assert get_process_func(od_config) is process_func
load_process_func.assert_called_once()
class TestWorkerUsesHook:
"""Test that DiffusionWorker resolves model runner via platform hook."""
@patch("vllm_omni.diffusion.worker.diffusion_worker.resolve_obj_by_qualname")
@patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
def test_model_runner_resolved_via_platform(self, mock_platform, mock_resolve):
"""Test model runner class is resolved from platform hook return value."""
from unittest.mock import Mock
from vllm_omni.diffusion.worker.diffusion_worker import DiffusionWorker
mock_runner_instance = Mock()
mock_runner_cls = Mock(return_value=mock_runner_instance)
mock_platform.get_diffusion_model_runner_cls.return_value = "custom.path"
mock_resolve.return_value = mock_runner_cls
with patch.object(DiffusionWorker, "init_device"):
worker = DiffusionWorker(local_rank=0, rank=0, od_config=Mock(), skip_load_model=True)
assert worker.model_runner is mock_runner_instance
mock_platform.get_diffusion_model_runner_cls.assert_called_once()
mock_resolve.assert_called_once_with("custom.path")
@patch("vllm_omni.diffusion.worker.diffusion_worker.get_diffusion_ir_op_priority_func")
@patch("vllm_omni.diffusion.worker.diffusion_worker.current_omni_platform")
def test_ir_op_priority_hook_receives_platform_default(self, mock_platform, mock_get_hook):
"""Test model IR priority hook merges from the platform default."""
from vllm_omni.diffusion.worker.diffusion_worker import _resolve_ir_op_priority
od_config = SimpleNamespace(model_class_name="TestPipeline")
vllm_config = SimpleNamespace()
default_priority = object()
merged_priority = object()
hook = Mock(return_value=merged_priority)
mock_platform.get_default_ir_op_priority.return_value = default_priority
mock_get_hook.return_value = hook
assert _resolve_ir_op_priority(od_config, vllm_config) is merged_priority
mock_platform.get_default_ir_op_priority.assert_called_once_with(vllm_config)
mock_get_hook.assert_called_once_with(od_config)
hook.assert_called_once_with(default_priority, vllm_config=vllm_config)