Merge remote-tracking branch 'origin/main' into dev/vllm-align

This commit is contained in:
tzhouam committed 2026-02-25 08:15:55 +00:00
commit 22e01c416b
39 files changed
+2763 -518

No files matched your search

@@ -3,21 +3,23 @@
import pytest
from vllm_omni.entrypoints import omni as omni_module
from vllm_omni.entrypoints import utils as utils_module
from vllm_omni.entrypoints.async_omni import AsyncOmni
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
MODEL = "riverclouds/qwen_image_random"
def test_default_stage_config_includes_cache_backend(monkeypatch):
"""Ensure cache_backend/cache_config are preserved in default diffusion stage."""
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", lambda model, base_engine_args=None: [])
monkeypatch.setattr(omni_module, "resolve_model_config_path", lambda model: None)
monkeypatch.setattr(utils_module, "load_stage_configs_from_model", lambda model, base_engine_args=None: [])
monkeypatch.setattr(utils_module, "resolve_model_config_path", lambda model: None)
monkeypatch.setattr(AsyncOmni, "_start_stages", lambda self, model: None)
monkeypatch.setattr(AsyncOmni, "_wait_for_stages_ready", lambda self, timeout=0: None)
omni = AsyncOmni(
model="dummy-model",
model=MODEL,
cache_backend="cache_dit",
cache_config='{"Fn_compute_blocks": 2}',
vae_use_slicing=True,
@@ -41,13 +43,13 @@ def test_default_stage_config_includes_cache_backend(monkeypatch):
def test_default_cache_config_used_when_missing(monkeypatch):
"""Ensure default cache_config is applied when cache_backend is set."""
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", lambda model, base_engine_args=None: [])
monkeypatch.setattr(omni_module, "resolve_model_config_path", lambda model: None)
monkeypatch.setattr(utils_module, "load_stage_configs_from_model", lambda model, base_engine_args=None: [])
monkeypatch.setattr(utils_module, "resolve_model_config_path", lambda model: None)
monkeypatch.setattr(AsyncOmni, "_start_stages", lambda self, model: None)
monkeypatch.setattr(AsyncOmni, "_wait_for_stages_ready", lambda self, timeout=0: None)
omni = AsyncOmni(
model="dummy-model",
model=MODEL,
cache_backend="cache_dit",
)
@@ -59,13 +61,13 @@ def test_default_cache_config_used_when_missing(monkeypatch):
def test_default_stage_devices_from_sequence_parallel(monkeypatch):
"""Ensure devices list reflects sequence parallel size when no parallel_config is provided."""
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", lambda model, base_engine_args=None: [])
monkeypatch.setattr(omni_module, "resolve_model_config_path", lambda model: None)
monkeypatch.setattr(utils_module, "load_stage_configs_from_model", lambda model, base_engine_args=None: [])
monkeypatch.setattr(utils_module, "resolve_model_config_path", lambda model: None)
monkeypatch.setattr(AsyncOmni, "_start_stages", lambda self, model: None)
monkeypatch.setattr(AsyncOmni, "_wait_for_stages_ready", lambda self, timeout=0: None)
omni = AsyncOmni(
model="dummy-model",
model=MODEL,
ulysses_degree=2,
ring_degree=2,
)
+296 -73
View File
@@ -18,6 +18,37 @@ warnings.filterwarnings(
category=DeprecationWarning,
)
MODEL = "riverclouds/qwen_image_random"
class _FakeStageRequestStats:
"""Fake StageRequestStats object with necessary attributes aligned with real StageRequestStats."""
def __init__(self, **kwargs):
# Required fields (with defaults for testing)
self.batch_id = kwargs.get("batch_id", 0)
self.batch_size = kwargs.get("batch_size", 1)
self.num_tokens_in = kwargs.get("num_tokens_in", 0)
self.num_tokens_out = kwargs.get("num_tokens_out", 1)
self.stage_gen_time_ms = kwargs.get("stage_gen_time_ms", 10.0)
self.rx_transfer_bytes = kwargs.get("rx_transfer_bytes", 0)
self.rx_decode_time_ms = kwargs.get("rx_decode_time_ms", 0.0)
self.rx_in_flight_time_ms = kwargs.get("rx_in_flight_time_ms", 0.0)
self.stage_stats = kwargs.get("stage_stats", None)
# Optional fields
self.stage_id = kwargs.get("stage_id", None)
self.final_output_type = kwargs.get("final_output_type", None)
self.request_id = kwargs.get("request_id", None)
self.postprocess_time_ms = kwargs.get("postprocess_time_ms", 0.0)
self.diffusion_metrics = kwargs.get("diffusion_metrics", None)
self.audio_generated_frames = kwargs.get("audio_generated_frames", 0)
# Allow additional attributes for flexibility
for key, value in kwargs.items():
if not hasattr(self, key):
setattr(self, key, value)
class _FakeEngineArgs(dict):
"""Fake engine args that can be used both as object attributes and as **kwargs."""
@@ -60,8 +91,10 @@ class _FakeQueue:
def put_nowait(self, item):
self._queue.put_nowait(item)
def get(self):
return self._queue.get()
def get(self, timeout=None):
if timeout is None:
return self._queue.get()
return self._queue.get(timeout=timeout)
def get_nowait(self):
return self._queue.get_nowait()
@@ -70,6 +103,52 @@ class _FakeQueue:
return self._queue.empty()
class _FakeZmqQueue:
"""Fake ZmqQueue that wraps _FakeQueue and matches ZmqQueue interface."""
def __init__(
self,
ctx=None,
socket_type=None,
*,
bind: str | None = None,
connect: str | None = None,
recv_timeout_ms: int | None = None,
send_timeout_ms: int | None = None,
):
"""Initialize fake ZMQ queue with same signature as real ZmqQueue."""
self._queue = _FakeQueue(maxsize=0)
# Determine endpoint from bind or connect
path = bind if bind is not None else connect
self.endpoint = path or f"fake://zmq-endpoint-{id(self)}"
self._recv_timeout_ms = recv_timeout_ms
self._send_timeout_ms = send_timeout_ms
def put(self, obj: Any) -> None:
"""Send an object to the queue."""
self._queue.put(obj)
def put_nowait(self, obj: Any) -> None:
"""Send an object to the queue without blocking."""
self._queue.put_nowait(obj)
def get(self, timeout: float | None = None) -> Any:
"""Receive an object from the queue with optional timeout in seconds."""
return self._queue.get(timeout=timeout)
def get_nowait(self) -> Any:
"""Receive an object from the queue without blocking."""
return self._queue.get_nowait()
def empty(self) -> bool:
"""Check if the queue is empty without blocking."""
return self._queue.empty()
def close(self) -> None:
"""Close the queue."""
pass
class _FakeStage:
"""Lightweight Stage stub for multi-process pipeline version with queue support."""
@@ -272,6 +351,19 @@ def _setup_multiprocessing_mocks(monkeypatch):
monkeypatch.setattr(mp, "get_context", _mock_get_context, raising=False)
monkeypatch.setattr(mp, "Process", fake_process_class, raising=False)
# Mock ZmqQueue to use _FakeZmqQueue
monkeypatch.setattr(
"vllm_omni.entrypoints.zmq_utils.ZmqQueue",
_FakeZmqQueue,
raising=False,
)
# Also mock where ZmqQueue is imported/used
monkeypatch.setattr(
"vllm_omni.entrypoints.omni_stage.ZmqQueue",
_FakeZmqQueue,
raising=False,
)
def _setup_ipc_mocks(monkeypatch):
"""Helper function to set up IPC function mocks."""
@@ -324,6 +416,65 @@ def _setup_log_mocks(monkeypatch):
)
def _setup_connector_mocks(monkeypatch, omni_module=None):
"""Helper function to set up connector mocks for stage-to-stage forwarding.
If omni_module is provided, mocks directly on the module. Otherwise, uses string path.
"""
# Mock initialize_orchestrator_connectors to return fake connectors
def _fake_initialize_orchestrator_connectors(config_path, worker_backend=None, shm_threshold_bytes=None):
# Create fake connectors for all stage-to-stage edges
# Each connector is just a mock object that will be passed to try_send_via_connector
fake_connectors = {}
# Add connectors for common edges (0->1, 1->2, etc.)
for i in range(10): # Support up to 10 stages
fake_connectors[(str(i), str(i + 1))] = MagicMock()
return None, fake_connectors
if omni_module is not None:
# Mock directly on the omni module where it's used (after import)
monkeypatch.setattr(omni_module, "initialize_orchestrator_connectors", _fake_initialize_orchestrator_connectors)
else:
# Mock via string path (before import)
monkeypatch.setattr(
"vllm_omni.entrypoints.omni.initialize_orchestrator_connectors",
_fake_initialize_orchestrator_connectors,
raising=False,
)
def _setup_connector_adapter_mock(monkeypatch, omni_module):
"""Helper function to mock try_send_via_connector on the omni module.
This must be called AFTER importing omni module, to mock the function where it's actually used.
"""
# Mock try_send_via_connector to always succeed
def _fake_try_send_via_connector(
connector,
stage_id,
next_stage_id,
req_id,
next_inputs,
sampling_params,
original_prompt,
next_stage_queue_submit_fn,
metrics,
):
# Simulate successful send by calling the submit function
task = {
"request_id": req_id,
"engine_inputs": next_inputs,
"sampling_params": sampling_params,
}
next_stage_queue_submit_fn(task)
return True
# Mock directly on the omni module where it's used
monkeypatch.setattr(omni_module, "try_send_via_connector", _fake_try_send_via_connector)
@pytest.fixture(autouse=True)
def mock_get_config(monkeypatch):
"""Auto-mock get_config and related model loading functions to avoid model path validation."""
@@ -449,10 +600,19 @@ def mock_get_config(monkeypatch):
def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_config):
"""Test that stage configs are auto-loaded when stage_configs_path is None."""
def _fake_loader(model: str, base_engine_args=None):
return [
_FakeStageConfig(fake_stage_config),
_FakeStageConfig(fake_stage_config),
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
cfg0 = dict(fake_stage_config)
cfg0["stage_id"] = 0
cfg1 = dict(fake_stage_config)
cfg1["stage_id"] = 1
return None, [
_FakeStageConfig(cfg0),
_FakeStageConfig(cfg1),
]
# Remove modules from cache BEFORE setting mocks
@@ -471,10 +631,11 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
# Mock load_stage_configs_from_model
# Mock load_and_resolve_stage_configs
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -489,13 +650,13 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
# Import the module after mocks are set
import vllm_omni.entrypoints.omni as omni_module
# Patch the imported function and class in the module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
# Patch the imported class in the module
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Verify: auto-loaded stage_configs and stage_list have consistent count
assert isinstance(omni.stage_configs, list)
assert len(omni.stage_configs) == 2
@@ -514,8 +675,13 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
"""Test that generate raises ValueError when sampling_params_list length doesn't match."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(fake_stage_config)]
import sys
@@ -531,14 +697,10 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
_fake_loader,
raising=False,
)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -550,12 +712,12 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
with pytest.raises(ValueError):
omni.generate(prompts=["hi"], sampling_params_list=[])
@@ -563,11 +725,18 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
"""Test multi-stage generation pipeline with queue polling."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
stage_cfg1["processed_input"] = ["processed-for-stage-1"]
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -575,6 +744,8 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
"vllm_omni.entrypoints.utils",
"vllm_omni.entrypoints.omni",
"vllm_omni.entrypoints.omni_stage",
"vllm_omni.distributed.omni_connectors.adapter",
"vllm_omni.distributed.omni_connectors",
]:
if module_name in sys.modules:
del sys.modules[module_name]
@@ -585,12 +756,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
_setup_log_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
_fake_loader,
raising=False,
)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -602,8 +768,11 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -612,7 +781,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -625,7 +794,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 0, "text": "s0"}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
# Stage 1 output (will be collected after stage 0 forwards to it)
@@ -637,7 +806,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 1, "text": "s1"}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
@@ -662,11 +831,18 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
"""Test single-stage generation pipeline with multiple inputs in one batch."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg1 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg0["final_output"] = False
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -674,6 +850,8 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
"vllm_omni.entrypoints.utils",
"vllm_omni.entrypoints.omni",
"vllm_omni.entrypoints.omni_stage",
"vllm_omni.distributed.omni_connectors.adapter",
"vllm_omni.distributed.omni_connectors",
]:
if module_name in sys.modules:
del sys.modules[module_name]
@@ -684,7 +862,7 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
_setup_log_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -696,8 +874,11 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -706,7 +887,7 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -718,28 +899,28 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 0, "text": "s0"}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
omni.stage_list[0]._out_q.put_nowait(
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 0, "text": "s0"}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
omni.stage_list[1]._out_q.put_nowait(
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 1}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
omni.stage_list[1]._out_q.put_nowait(
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 1}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
@@ -768,12 +949,19 @@ def test_generate_pipeline_with_batch_input(monkeypatch, fake_stage_config):
def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
"""Test that generate returns empty list when all stages have final_output=False."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg1 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg0["final_output"] = False
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
stage_cfg1["final_output"] = False
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -781,6 +969,8 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
"vllm_omni.entrypoints.utils",
"vllm_omni.entrypoints.omni",
"vllm_omni.entrypoints.omni_stage",
"vllm_omni.distributed.omni_connectors.adapter",
"vllm_omni.distributed.omni_connectors",
]:
if module_name in sys.modules:
del sys.modules[module_name]
@@ -791,7 +981,7 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
_setup_log_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -803,8 +993,11 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -813,7 +1006,7 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -847,12 +1040,19 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_config):
"""Test that generate uses default sampling params when sampling_params_list is None."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg1 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg0["final_output"] = False
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
stage_cfg1["final_output"] = False
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -860,6 +1060,8 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
"vllm_omni.entrypoints.utils",
"vllm_omni.entrypoints.omni",
"vllm_omni.entrypoints.omni_stage",
"vllm_omni.distributed.omni_connectors.adapter",
"vllm_omni.distributed.omni_connectors",
]:
if module_name in sys.modules:
del sys.modules[module_name]
@@ -870,7 +1072,7 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
_setup_log_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -882,8 +1084,11 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -892,7 +1097,7 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -902,14 +1107,14 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 0}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
omni.stage_list[1]._out_q.put_nowait(
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 1}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
# Use the default sampling params
@@ -919,8 +1124,13 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
"""Test that _wait_for_stages_ready handles timeout correctly."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(fake_stage_config)]
import sys
@@ -936,9 +1146,10 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -961,13 +1172,13 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStageNoReady(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
from vllm_omni.entrypoints.omni import Omni
# Use very short timeout
omni = Omni(model="any", init_timeout=0.01)
omni = Omni(model=MODEL, init_timeout=0.01)
# Verify that no stages are ready
assert len(omni._stages_ready) == 0
@@ -975,8 +1186,13 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
"""Test that generate handles error messages from stages correctly."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(fake_stage_config)]
import sys
@@ -992,9 +1208,10 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -1006,8 +1223,8 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -1016,7 +1233,7 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -1034,7 +1251,7 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
{
"request_id": expected_request_id,
"engine_outputs": [{"stage": 0, "text": "result"}],
"metrics": {"num_tokens_out": 1, "stage_gen_time_ms": 10.0},
"metrics": _FakeStageRequestStats(num_tokens_out=1, stage_gen_time_ms=10.0),
}
)
@@ -1050,8 +1267,13 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
"""Test that close() sends shutdown signal to all input queues."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(fake_stage_config)]
import sys
@@ -1067,9 +1289,10 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -1081,12 +1304,12 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Call close
omni.close()
+246 -57
View File
@@ -19,6 +19,8 @@ warnings.filterwarnings(
category=DeprecationWarning,
)
MODEL = "riverclouds/qwen_image_random"
class _FakeEngineArgs(dict):
"""Fake engine args that can be used both as object attributes and as **kwargs."""
@@ -61,8 +63,10 @@ class _FakeQueue:
def put_nowait(self, item):
self._queue.put_nowait(item)
def get(self):
return self._queue.get()
def get(self, timeout=None):
if timeout is None:
return self._queue.get()
return self._queue.get(timeout=timeout)
def get_nowait(self):
return self._queue.get_nowait()
@@ -71,6 +75,52 @@ class _FakeQueue:
return self._queue.empty()
class _FakeZmqQueue:
"""Fake ZmqQueue that wraps _FakeQueue and matches ZmqQueue interface."""
def __init__(
self,
ctx=None,
socket_type=None,
*,
bind: str | None = None,
connect: str | None = None,
recv_timeout_ms: int | None = None,
send_timeout_ms: int | None = None,
):
"""Initialize fake ZMQ queue with same signature as real ZmqQueue."""
self._queue = _FakeQueue(maxsize=0)
# Determine endpoint from bind or connect
path = bind if bind is not None else connect
self.endpoint = path or f"fake://zmq-endpoint-{id(self)}"
self._recv_timeout_ms = recv_timeout_ms
self._send_timeout_ms = send_timeout_ms
def put(self, obj: Any) -> None:
"""Send an object to the queue."""
self._queue.put(obj)
def put_nowait(self, obj: Any) -> None:
"""Send an object to the queue without blocking."""
self._queue.put_nowait(obj)
def get(self, timeout: float | None = None) -> Any:
"""Receive an object from the queue with optional timeout in seconds."""
return self._queue.get(timeout=timeout)
def get_nowait(self) -> Any:
"""Receive an object from the queue without blocking."""
return self._queue.get_nowait()
def empty(self) -> bool:
"""Check if the queue is empty without blocking."""
return self._queue.empty()
def close(self) -> None:
"""Close the queue."""
pass
class _FakeStage:
"""Lightweight Stage stub for multi-process pipeline version with queue support."""
@@ -273,6 +323,19 @@ def _setup_multiprocessing_mocks(monkeypatch):
monkeypatch.setattr(mp, "get_context", _mock_get_context, raising=False)
monkeypatch.setattr(mp, "Process", fake_process_class, raising=False)
# Mock ZmqQueue to use _FakeZmqQueue
monkeypatch.setattr(
"vllm_omni.entrypoints.zmq_utils.ZmqQueue",
_FakeZmqQueue,
raising=False,
)
# Also mock where ZmqQueue is imported/used
monkeypatch.setattr(
"vllm_omni.entrypoints.omni_stage.ZmqQueue",
_FakeZmqQueue,
raising=False,
)
def _setup_ipc_mocks(monkeypatch):
"""Helper function to set up IPC function mocks."""
@@ -325,6 +388,65 @@ def _setup_log_mocks(monkeypatch):
)
def _setup_connector_mocks(monkeypatch, omni_module=None):
"""Helper function to set up connector mocks for stage-to-stage forwarding.
If omni_module is provided, mocks directly on the module. Otherwise, uses string path.
"""
# Mock initialize_orchestrator_connectors to return fake connectors
def _fake_initialize_orchestrator_connectors(config_path, worker_backend=None, shm_threshold_bytes=None):
# Create fake connectors for all stage-to-stage edges
# Each connector is just a mock object that will be passed to try_send_via_connector
fake_connectors = {}
# Add connectors for common edges (0->1, 1->2, etc.)
for i in range(10): # Support up to 10 stages
fake_connectors[(str(i), str(i + 1))] = MagicMock()
return None, fake_connectors
if omni_module is not None:
# Mock directly on the omni module where it's used (after import)
monkeypatch.setattr(omni_module, "initialize_orchestrator_connectors", _fake_initialize_orchestrator_connectors)
else:
# Mock via string path (before import)
monkeypatch.setattr(
"vllm_omni.entrypoints.omni.initialize_orchestrator_connectors",
_fake_initialize_orchestrator_connectors,
raising=False,
)
def _setup_connector_adapter_mock(monkeypatch, omni_module):
"""Helper function to mock try_send_via_connector on the omni module.
This must be called AFTER importing omni module, to mock the function where it's actually used.
"""
# Mock try_send_via_connector to always succeed
def _fake_try_send_via_connector(
connector,
stage_id,
next_stage_id,
req_id,
next_inputs,
sampling_params,
original_prompt,
next_stage_queue_submit_fn,
metrics,
):
# Simulate successful send by calling the submit function
task = {
"request_id": req_id,
"engine_inputs": next_inputs,
"sampling_params": sampling_params,
}
next_stage_queue_submit_fn(task)
return True
# Mock directly on the omni module where it's used
monkeypatch.setattr(omni_module, "try_send_via_connector", _fake_try_send_via_connector)
@pytest.fixture(autouse=True)
def mock_get_config(monkeypatch):
"""Auto-mock get_config and related model loading functions to avoid model path validation."""
@@ -450,10 +572,19 @@ def mock_get_config(monkeypatch):
def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_config):
"""Test that stage configs are auto-loaded when stage_configs_path is None."""
def _fake_loader(model: str, base_engine_args=None):
return [
_FakeStageConfig(fake_stage_config),
_FakeStageConfig(fake_stage_config),
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
cfg0 = dict(fake_stage_config)
cfg0["stage_id"] = 0
cfg1 = dict(fake_stage_config)
cfg1["stage_id"] = 1
return None, [
_FakeStageConfig(cfg0),
_FakeStageConfig(cfg1),
]
# Remove modules from cache BEFORE setting mocks
@@ -472,10 +603,11 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
# Mock load_stage_configs_from_model
# Mock load_and_resolve_stage_configs
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -491,12 +623,14 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
import vllm_omni.entrypoints.omni as omni_module
# Patch the imported function and class in the module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Verify: auto-loaded stage_configs and stage_list have consistent count
assert isinstance(omni.stage_configs, list)
assert len(omni.stage_configs) == 2
@@ -515,8 +649,15 @@ def test_initialize_stage_configs_called_when_none(monkeypatch, fake_stage_confi
def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
"""Test that generate raises ValueError when sampling_params_list length doesn't match."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
cfg0 = dict(fake_stage_config)
cfg0["stage_id"] = 0
return None, [_FakeStageConfig(cfg0)]
import sys
@@ -532,14 +673,10 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
_fake_loader,
raising=False,
)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -551,12 +688,12 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
with pytest.raises(ValueError):
omni.generate(prompts=["hi"], sampling_params_list=[])
@@ -564,11 +701,18 @@ def test_generate_raises_on_length_mismatch(monkeypatch, fake_stage_config):
def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
"""Test multi-stage generation pipeline with queue polling."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
stage_cfg1["processed_input"] = ["processed-for-stage-1"]
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -584,14 +728,10 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
_fake_loader,
raising=False,
)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -603,8 +743,11 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -613,7 +756,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -663,12 +806,19 @@ def test_generate_pipeline_and_final_outputs(monkeypatch, fake_stage_config):
def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
"""Test that generate returns empty list when all stages have final_output=False."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg1 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg0["final_output"] = False
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
stage_cfg1["final_output"] = False
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -684,9 +834,10 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -698,8 +849,11 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -708,7 +862,7 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -742,12 +896,19 @@ def test_generate_no_final_output_returns_empty(monkeypatch, fake_stage_config):
def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_config):
"""Test that generate uses default sampling params when sampling_params_list is None."""
stage_cfg0 = dict(fake_stage_config)
stage_cfg1 = dict(fake_stage_config)
stage_cfg0["stage_id"] = 0
stage_cfg0["final_output"] = False
stage_cfg1 = dict(fake_stage_config)
stage_cfg1["stage_id"] = 1
stage_cfg1["final_output"] = False
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
return None, [_FakeStageConfig(stage_cfg0), _FakeStageConfig(stage_cfg1)]
import sys
@@ -763,9 +924,10 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -777,8 +939,11 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
# Apply connector and adapter mocks after importing omni module
_setup_connector_mocks(monkeypatch, omni_module)
_setup_connector_adapter_mock(monkeypatch, omni_module)
# Mock uuid.uuid4() to return a predictable value for request ID generation
test_uuid = uuid.UUID("00000000-0000-0000-0000-000000000000")
@@ -787,7 +952,7 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -814,8 +979,15 @@ def test_generate_sampling_params_none_use_default(monkeypatch, fake_stage_confi
def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
"""Test that _wait_for_stages_ready handles timeout correctly."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
cfg0 = dict(fake_stage_config)
cfg0["stage_id"] = 0
return None, [_FakeStageConfig(cfg0)]
import sys
@@ -831,9 +1003,10 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -856,13 +1029,13 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStageNoReady(cfg, **kwargs))
from vllm_omni.entrypoints.omni import Omni
# Use very short timeout
omni = Omni(model="any", init_timeout=0.01)
omni = Omni(model=MODEL, init_timeout=0.01)
# Verify that no stages are ready
assert len(omni._stages_ready) == 0
@@ -870,8 +1043,15 @@ def test_wait_for_stages_ready_timeout(monkeypatch, fake_stage_config):
def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
"""Test that generate handles error messages from stages correctly."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
cfg0 = dict(fake_stage_config)
cfg0["stage_id"] = 0
return None, [_FakeStageConfig(cfg0)]
import sys
@@ -887,9 +1067,10 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -901,7 +1082,7 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
# Mock uuid.uuid4() to return a predictable value for request ID generation
@@ -911,7 +1092,7 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Generate the expected request ID format: "0_<uuid>"
expected_request_id = f"0_{test_uuid}"
@@ -945,8 +1126,15 @@ def test_generate_handles_error_messages(monkeypatch, fake_stage_config):
def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
"""Test that close() sends shutdown signal to all input queues."""
def _fake_loader(model: str, base_engine_args=None):
return [_FakeStageConfig(fake_stage_config)]
def _fake_loader(
model: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
):
cfg0 = dict(fake_stage_config)
cfg0["stage_id"] = 0
return None, [_FakeStageConfig(cfg0)]
import sys
@@ -962,9 +1150,10 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
_setup_multiprocessing_mocks(monkeypatch)
_setup_ipc_mocks(monkeypatch)
_setup_log_mocks(monkeypatch)
_setup_connector_mocks(monkeypatch)
monkeypatch.setattr(
"vllm_omni.entrypoints.utils.load_stage_configs_from_model",
"vllm_omni.entrypoints.utils.load_and_resolve_stage_configs",
_fake_loader,
raising=False,
)
@@ -976,12 +1165,12 @@ def test_close_sends_shutdown_signal(monkeypatch, fake_stage_config):
import vllm_omni.entrypoints.omni as omni_module
monkeypatch.setattr(omni_module, "load_stage_configs_from_model", _fake_loader)
monkeypatch.setattr(omni_module, "load_and_resolve_stage_configs", _fake_loader)
monkeypatch.setattr(omni_module, "OmniStage", lambda cfg, **kwargs: _FakeStage(cfg, **kwargs))
from vllm_omni.entrypoints.omni import Omni
omni = Omni(model="any", init_timeout=1)
omni = Omni(model=MODEL, init_timeout=1)
# Call close
omni.close()
+271
View File
@@ -0,0 +1,271 @@
"""Unit tests for vllm_omni.entrypoints.utils module."""
from collections import Counter
from dataclasses import dataclass
from unittest.mock import patch
import pytest
from vllm_omni.diffusion.data import OmniDiffusionConfig
from vllm_omni.engine.arg_utils import OmniEngineArgs
from vllm_omni.entrypoints.utils import (
_convert_dataclasses_to_dict,
_filter_dict_like_object,
filter_dataclass_kwargs,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class TestFilterDictLikeObject:
"""Test suite for _filter_dict_like_object function."""
def test_simple_dict(self):
"""Test filtering a simple dictionary with no callables."""
input_dict = {"key1": "value1", "key2": 42, "key3": [1, 2, 3]}
result = _filter_dict_like_object(input_dict)
assert result == input_dict
assert isinstance(result, dict)
def test_dict_with_nested_values(self):
"""Test filtering dict with nested dict and list values."""
input_dict = {
"level1": {
"level2": {"key": "value"},
"list": [1, 2, 3],
},
"simple": "string",
}
result = _filter_dict_like_object(input_dict)
# Nested dicts and lists should be recursively processed
assert result["simple"] == "string"
assert isinstance(result["level1"], dict)
def test_dict_with_dataclass_values(self):
"""Test filtering dict containing dataclass values."""
@dataclass
class TestDataclass:
field1: str
field2: int
obj = TestDataclass(field1="test", field2=42)
input_dict = {"data": obj, "normal": "value"}
result = _filter_dict_like_object(input_dict)
# Dataclass should be converted to dict by recursive _convert_dataclasses_to_dict
assert "data" in result
assert "normal" in result
assert result["normal"] == "value"
def test_dict_with_counter_values(self):
"""Test filtering dict containing Counter objects."""
counter_obj = Counter({"a": 1, "b": 2})
input_dict = {"counter": counter_obj, "normal": "value"}
result = _filter_dict_like_object(input_dict)
# Counter should be converted to regular dict
assert "counter" in result
assert "normal" in result
assert result["normal"] == "value"
def test_empty_dict(self):
"""Test filtering an empty dictionary."""
result = _filter_dict_like_object({})
assert result == {}
assert isinstance(result, dict)
def test_dict_with_set_values(self):
"""Test filtering dict with set values."""
input_dict = {"set_key": {1, 2, 3}, "normal": "value"}
result = _filter_dict_like_object(input_dict)
assert "set_key" in result
assert "normal" in result
# Set should be converted to list by _convert_dataclasses_to_dict
assert result["normal"] == "value"
def test_dict_with_none_values(self):
"""Test filtering dict with None values."""
input_dict = {"key1": None, "key2": "value", "key3": 0}
result = _filter_dict_like_object(input_dict)
assert result == input_dict
def test_dict_with_mixed_types(self):
"""Test filtering dict with mixed value types."""
input_dict = {
"string": "hello",
"int": 42,
"float": 3.14,
"bool": True,
"none": None,
"list": [1, 2, 3],
"tuple": (1, 2, 3),
"set": {1, 2, 3},
"dict": {"nested": "value"},
}
result = _filter_dict_like_object(input_dict)
assert "string" in result
assert "int" in result
assert "float" in result
assert "bool" in result
assert "none" in result
assert "list" in result
assert "tuple" in result
assert "set" in result
assert "dict" in result
def test_dict_preserves_key_types(self):
"""Test that dict key types are preserved."""
input_dict = {
"string_key": "value1",
42: "value2",
(1, 2): "value3", # tuple as key
}
result = _filter_dict_like_object(input_dict)
# Keys should remain the same
assert "string_key" in result
assert 42 in result
assert (1, 2) in result
def test_dict_with_recursive_structure(self):
"""Test filtering dict with recursive/complex nested structure."""
input_dict = {
"level1": {
"level2": {
"level3": {"key": "value"},
"callable": lambda x: x,
}
},
"normal": "value",
}
with patch("vllm_omni.entrypoints.utils.logger"):
result = _filter_dict_like_object(input_dict)
# Normal key should exist
assert "normal" in result
# Level1 should exist
assert "level1" in result
def test_integration_with_convert_dataclasses(self):
"""Test that _filter_dict_like_object integrates properly with _convert_dataclasses_to_dict."""
@dataclass
class Config:
name: str
count: int
input_dict = {
"config": Config(name="test", count=5),
"func": lambda x: x,
"normal": "value",
}
with patch("vllm_omni.entrypoints.utils.logger"):
result = _filter_dict_like_object(input_dict)
# Callable should be filtered
assert "func" not in result
# Config should be converted to dict
assert "config" in result
assert "normal" in result
class TestConvertDataclassesToDict:
"""Test suite for _convert_dataclasses_to_dict function."""
def test_uses_filter_dict_like_object(self):
"""Test that _convert_dataclasses_to_dict uses _filter_dict_like_object for dicts."""
input_dict = {
"normal": "value",
"callable": lambda x: x,
}
with patch("vllm_omni.entrypoints.utils.logger"):
result = _convert_dataclasses_to_dict(input_dict)
# Callable should be filtered out by _filter_dict_like_object
assert "normal" in result
assert "callable" not in result
class TestFilterDataclassKwargs:
"""Test basic functionality of filter_dataclass_kwargs."""
def test_simple_filtering(self):
"""Test basic dataclass kwargs filtering."""
@dataclass
class SimpleConfig:
name: str
count: int
kwargs = {"name": "test", "count": 42, "invalid": "should_be_removed"}
result = filter_dataclass_kwargs(SimpleConfig, kwargs)
assert "name" in result
assert "count" in result
assert "invalid" not in result
def test_invalid_dataclass_raises_error(self):
"""Test that non-dataclass raises ValueError."""
with pytest.raises(ValueError, match="is not a dataclass"):
filter_dataclass_kwargs(dict, {})
def test_invalid_kwargs_type_raises_error(self):
"""Test that non-dict kwargs raises ValueError."""
@dataclass
class SimpleConfig:
name: str
with pytest.raises(ValueError, match="kwargs must be a dictionary"):
filter_dataclass_kwargs(SimpleConfig, "invalid")
def test_filters_omni_engine_args_unknown_fields(self):
"""Test that OmniEngineArgs kwargs are filtered to valid fields only."""
kwargs = {
"model": "dummy",
"stage_id": 1,
"engine_output_type": "image",
"unknown_field": "drop_me",
}
result = filter_dataclass_kwargs(OmniEngineArgs, kwargs)
assert "model" in result
assert "stage_id" in result
assert "engine_output_type" in result
assert "unknown_field" not in result
def test_filters_omni_diffusion_config_union_dataclass(self):
"""Test that OmniDiffusionConfig filters nested dataclass in Union fields."""
kwargs = {
"model": "dummy",
"cache_config": {
"rel_l1_thresh": 0.3,
"extra_param": "should_drop",
},
"unknown_top": "drop_me",
}
result = filter_dataclass_kwargs(OmniDiffusionConfig, kwargs)
assert "model" in result
assert "cache_config" in result
assert "unknown_top" not in result
assert result["cache_config"]["rel_l1_thresh"] == 0.3
assert "extra_param" not in result["cache_config"]