Files
vllm-omni/tests/entrypoints/test_omni_entrypoints.py
T
Zhou TaichangandYuanheng Zhao 7f2b1e9386 [Rebase] Rebase to vllm 0.20.0 (#3232)
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
Signed-off-by: Zhou Taichang <tzhouam@connect.ust.hk>
Signed-off-by: Yuanheng Zhao <jonathan.zhaoyh@gmail.com>
Co-authored-by: Yuanheng Zhao <54058983+yuanheng-zhao@users.noreply.github.com>
2026-04-30 09:56:59 +12:00

997 lines
33 KiB
Python

from __future__ import annotations
import argparse
import queue
from collections.abc import Callable
from types import SimpleNamespace
from typing import Any
from unittest.mock import MagicMock
import pytest
from vllm.entrypoints.openai.models.protocol import BaseModelPath
from vllm.entrypoints.openai.models.serving import OpenAIServingModels
from vllm.sampling_params import RequestOutputKind, SamplingParams
from vllm.v1.engine.exceptions import EngineDeadError, EngineGenerateError
from vllm_omni.entrypoints.async_omni import AsyncOmni
from vllm_omni.entrypoints.omni import Omni
from vllm_omni.entrypoints.omni_base import OmniEngineDeadError
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
THREE_STAGE_META = [
{"stage_type": "llm", "final_output": True, "final_output_type": "text"},
{"stage_type": "llm", "final_output": False, "final_output_type": None},
{"stage_type": "diffusion", "final_output": True, "final_output_type": "image"},
]
DIFFUSION_ONLY_META = [
{"stage_type": "diffusion", "final_output": True, "final_output_type": "image"},
]
LLM_DIFFUSION_META = [
{"stage_type": "llm", "final_output": True, "final_output_type": "text"},
{"stage_type": "diffusion", "final_output": True, "final_output_type": "image"},
]
class FakeEngineOutput:
def __init__(
self,
*,
payload: str,
finished: bool,
images: list[str] | None = None,
stage_durations: dict[str, float] | None = None,
) -> None:
self.payload = payload
self.finished = finished
self.images = images or []
self.stage_durations = stage_durations or {}
def make_output_msg(
request_id: str,
stage_id: int,
*,
payload: str,
output_finished: bool,
finished: bool | None = None,
images: list[str] | None = None,
metrics: Any = None,
) -> dict[str, Any]:
if finished is None:
finished = output_finished
return {
"type": "output",
"request_id": request_id,
"stage_id": stage_id,
"engine_outputs": FakeEngineOutput(
payload=payload,
finished=output_finished,
images=images,
),
"finished": finished,
"metrics": metrics,
}
class FakeAsyncOmniEngine:
def __init__(
self,
model: str = "dummy-model",
*,
stage_metadata: list[dict[str, Any]] | None = None,
default_sampling_params_list: list[Any] | None = None,
on_add_request: Callable[[FakeAsyncOmniEngine, dict[str, Any]], None] | None = None,
rpc_results: list[Any] | None = None,
**_: Any,
) -> None:
self.model = model
self.config_path = None
self.stage_configs: list[Any] = []
self.stage_metadata = stage_metadata or [THREE_STAGE_META[-1]]
self.num_stages = len(self.stage_metadata)
self.default_sampling_params_list = default_sampling_params_list or [
SamplingParams(max_tokens=8) for _ in range(self.num_stages)
]
self.supported_tasks = ("generate",)
self.stage_clients = [SimpleNamespace(is_comprehension=False) for _ in range(self.num_stages)]
self.stage_vllm_configs = [None for _ in range(self.num_stages)]
self.output_processors = [SimpleNamespace(tokenizer=None) for _ in range(self.num_stages)]
self.input_processor = None
self.output_q: queue.Queue[dict[str, Any]] = queue.Queue()
self.submitted: list[dict[str, Any]] = []
self.aborted: list[list[str]] = []
self.rpc_results = rpc_results or []
self.on_add_request = on_add_request
self.shutdown_called = False
self._alive = True
def add_request(
self,
request_id: str,
prompt: Any,
sampling_params_list: list[Any] | None = None,
final_stage_id: int = 0,
arrival_time: float | None = None,
**kwargs: Any,
) -> None:
msg = {
"request_id": request_id,
"prompt": prompt,
"sampling_params_list": sampling_params_list,
"final_stage_id": final_stage_id,
"arrival_time": arrival_time,
}
self.submitted.append(msg)
if self.on_add_request is not None:
self.on_add_request(self, msg)
async def add_request_async(self, *args, **kwargs) -> None:
self.add_request(*args, **kwargs)
def try_get_output(self, timeout: float = 0.001) -> dict[str, Any] | None:
try:
return self.output_q.get_nowait()
except queue.Empty:
return None
async def try_get_output_async(self) -> dict[str, Any] | None:
return self.try_get_output()
def get_stage_metadata(self, stage_id: int) -> dict[str, Any]:
return self.stage_metadata[stage_id]
def abort(self, request_ids: list[str]) -> None:
self.aborted.append(list(request_ids))
async def abort_async(self, request_ids: list[str]) -> None:
self.abort(request_ids)
async def collective_rpc_async(self, **_: Any) -> list[Any]:
return list(self.rpc_results)
def is_alive(self) -> bool:
return self._alive
def shutdown(self) -> None:
self.shutdown_called = True
self._alive = False
def _patch_engine(monkeypatch: pytest.MonkeyPatch, engine: FakeAsyncOmniEngine) -> None:
monkeypatch.setattr("vllm_omni.entrypoints.omni_base.AsyncOmniEngine", lambda *args, **kwargs: engine)
monkeypatch.setattr("vllm_omni.entrypoints.omni_base.omni_snapshot_download", lambda model: model)
def test_from_cli_args_only_nulls_untyped_override_fields(monkeypatch: pytest.MonkeyPatch):
from vllm_omni.entrypoints.omni import Omni
captured: dict[str, Any] = {}
def fake_engine(*args: Any, **kwargs: Any) -> FakeAsyncOmniEngine:
captured.update(kwargs)
return FakeAsyncOmniEngine()
monkeypatch.setattr("vllm_omni.entrypoints.omni_base.AsyncOmniEngine", fake_engine)
monkeypatch.setattr("vllm_omni.entrypoints.omni_base.omni_snapshot_download", lambda model: model)
monkeypatch.setattr("sys.argv", ["prog"])
parser = argparse.ArgumentParser()
parser.add_argument("--gpu-memory-utilization", type=float, default=0.9)
parser.add_argument("--hsdp-shard-size", type=int, default=-1)
args = parser.parse_args([])
args.model = "fake-model"
Omni.from_cli_args(args, parser=parser)
assert captured["gpu_memory_utilization"] is None
assert captured["hsdp_shard_size"] == -1
def _make_base():
from vllm_omni.entrypoints.omni_base import OmniBase
obj = object.__new__(OmniBase)
obj.engine = MagicMock()
obj.request_states = {}
return obj
def _stage_spec(
stage_id: int,
*,
payloads: list[str],
finished: bool = False,
image_payloads: list[str] | None = None,
) -> dict[str, Any]:
return {
"stage_id": stage_id,
"payloads": payloads,
"finished": finished,
"image_payloads": image_payloads or [],
}
def _enqueue_outputs(
engine: FakeAsyncOmniEngine,
msg: dict[str, Any],
*,
stage_specs: list[dict[str, Any]],
) -> None:
request_id = msg["request_id"]
for spec in stage_specs:
payloads = spec["payloads"]
image_payloads = spec.get("image_payloads", [])
last_idx = len(payloads) - 1
for idx, payload_tmpl in enumerate(payloads):
images = []
if idx < len(image_payloads):
images = [image_payloads[idx].format(request_id=request_id, idx=idx)]
engine.output_q.put_nowait(
make_output_msg(
request_id,
spec["stage_id"],
payload=payload_tmpl.format(request_id=request_id, idx=idx),
output_finished=(idx == last_idx),
finished=bool(spec.get("finished")) and idx == last_idx,
images=images,
)
)
def _enqueue_omni_final_only_outputs(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
sampling_params_list = msg["sampling_params_list"]
llm_streaming = any(params.output_kind != RequestOutputKind.FINAL_ONLY for params in sampling_params_list[:2])
stage0_count = 3 if llm_streaming else 1
stage1_count = 3 if llm_streaming else 1
_enqueue_outputs(
engine,
msg,
stage_specs=[
_stage_spec(0, payloads=[f"{{request_id}}-stage0-{idx}" for idx in range(stage0_count)]),
_stage_spec(1, payloads=[f"{{request_id}}-stage1-{idx}" for idx in range(stage1_count)]),
_stage_spec(
2,
payloads=["{request_id}-stage2-final"],
finished=True,
image_payloads=["{request_id}-img-final"],
),
],
)
def _enqueue_omni_llm_diffusion_outputs(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
sampling_params_list = msg["sampling_params_list"]
llm_streaming = sampling_params_list[0].output_kind != RequestOutputKind.FINAL_ONLY
stage0_count = 3 if llm_streaming else 1
_enqueue_outputs(
engine,
msg,
stage_specs=[
_stage_spec(0, payloads=[f"{{request_id}}-text-{idx}" for idx in range(stage0_count)]),
_stage_spec(
1,
payloads=["{request_id}-image-final"],
finished=True,
image_payloads=["{request_id}-image"],
),
],
)
def _enqueue_async_three_stage_outputs(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
_enqueue_outputs(
engine,
msg,
stage_specs=[
_stage_spec(0, payloads=[f"{{request_id}}-stage0-{idx}" for idx in range(3)]),
_stage_spec(1, payloads=[f"{{request_id}}-stage1-{idx}" for idx in range(3)]),
_stage_spec(
2,
payloads=[f"{{request_id}}-stage2-{idx}" for idx in range(3)],
finished=True,
image_payloads=[f"{{request_id}}-img-{idx}" for idx in range(3)],
),
],
)
def _enqueue_async_finish_outputs(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
_enqueue_outputs(
engine,
msg,
stage_specs=[
_stage_spec(0, payloads=["{request_id}-stage0"]),
_stage_spec(
2,
payloads=["{request_id}-stage2-final"],
finished=True,
image_payloads=["{request_id}-img-final"],
),
],
)
def _enqueue_async_diffusion_only_output(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
_enqueue_outputs(
engine,
msg,
stage_specs=[
_stage_spec(
0,
payloads=["{request_id}-diffusion-final"],
finished=True,
image_payloads=["{request_id}-image"],
)
],
)
def _enqueue_async_llm_diffusion_outputs(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
_enqueue_outputs(
engine,
msg,
stage_specs=[
_stage_spec(0, payloads=[f"{{request_id}}-text-{idx}" for idx in range(3)]),
_stage_spec(
1,
payloads=["{request_id}-image-final"],
finished=True,
image_payloads=["{request_id}-image"],
),
],
)
def _enqueue_error_message(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
engine.output_q.put_nowait(
{
"type": "error",
"request_id": msg["request_id"],
"stage_id": 0,
"error": "engine boom",
}
)
def _enqueue_fatal_error_message(engine: FakeAsyncOmniEngine, msg: dict[str, Any]) -> None:
engine.output_q.put_nowait(
{
"type": "error",
"fatal": True,
"request_id": msg["request_id"],
"stage_id": 2,
"error": "engine dead",
}
)
@pytest.mark.asyncio
async def test_get_supported_tasks_returns_engine_supported_tasks():
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(supported_tasks=("generate", "speech"))
supported_tasks = await omni.get_supported_tasks()
assert supported_tasks == ("generate", "speech")
def test_model_config_and_vllm_config_forward_from_comprehension_stage():
model_config = SimpleNamespace(model="Qwen/Qwen3-TTS")
vllm_config = SimpleNamespace(model_config=model_config)
renderer = SimpleNamespace(name="renderer")
input_processor = SimpleNamespace(renderer=renderer)
io_processor = SimpleNamespace(name="io-processor")
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_clients=[SimpleNamespace(is_comprehension=False), SimpleNamespace(is_comprehension=True)],
stage_vllm_configs=[None, vllm_config],
)
omni.input_processor = input_processor
omni.io_processor = io_processor
assert omni.vllm_config is vllm_config
assert omni.model_config is model_config
assert omni.renderer is renderer
assert omni.input_processor is input_processor
assert omni.io_processor is io_processor
def test_openai_serving_models_can_consume_async_omni_compat_attrs():
model_config = SimpleNamespace(model="Qwen/Qwen3-TTS", max_model_len=32768)
vllm_config = SimpleNamespace(model_config=model_config)
renderer = SimpleNamespace(name="renderer")
input_processor = SimpleNamespace(renderer=renderer)
io_processor = SimpleNamespace(name="io-processor")
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_clients=[SimpleNamespace(is_comprehension=True)],
stage_vllm_configs=[vllm_config],
)
omni.input_processor = input_processor
omni.io_processor = io_processor
serving_models = OpenAIServingModels(
engine_client=omni,
base_model_paths=[BaseModelPath(name="tts-model", model_path="Qwen/Qwen3-TTS")],
)
assert serving_models.model_config is model_config
assert serving_models.renderer is renderer
assert serving_models.input_processor is input_processor
# vLLM 0.20 keeps io_processor on the engine client instead of copying it.
assert serving_models.engine_client.io_processor is io_processor
def test_get_diffusion_od_config_returns_diffusion_stage_config():
diffusion_od_config = object()
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_clients=[
SimpleNamespace(stage_type="llm"),
SimpleNamespace(stage_type="diffusion", _engine=SimpleNamespace(od_config=diffusion_od_config)),
]
)
assert omni.get_diffusion_od_config() is diffusion_od_config
@pytest.mark.asyncio
async def test_async_omni_yields_only_final_stage_outputs(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
on_add_request=lambda eng, msg: _enqueue_outputs(
eng,
msg,
stage_specs=[
_stage_spec(1, payloads=["non-final"]),
_stage_spec(2, payloads=["final"], finished=True, image_payloads=["final-img"]),
],
),
)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
outputs = []
async for output in app.generate(prompt="hello", request_id="req-1"):
outputs.append(output)
finally:
app.shutdown()
assert [output.stage_id for output in outputs] == [2]
assert [output.request_output.payload for output in outputs] == ["final"]
assert "req-1" not in app.request_states
@pytest.mark.asyncio
async def test_async_omni_accepts_multiple_final_stage_streams(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META, on_add_request=_enqueue_async_three_stage_outputs)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
outputs = []
async for output in app.generate(prompt="hello", request_id="req-1"):
outputs.append(output)
finally:
app.shutdown()
assert [output.stage_id for output in outputs] == [0, 0, 0, 2, 2, 2]
assert [output.request_output.payload for output in outputs] == [
"req-1-stage0-0",
"req-1-stage0-1",
"req-1-stage0-2",
"req-1-stage2-0",
"req-1-stage2-1",
"req-1-stage2-2",
]
@pytest.mark.asyncio
async def test_async_omni_stops_on_final_stage_finished(monkeypatch: pytest.MonkeyPatch):
# Intentionally jump from stage 0 to stage 2: stage 1 is a non-final stage
# and should be filtered out from the client-visible output stream.
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META, on_add_request=_enqueue_async_finish_outputs)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
outputs = []
async for output in app.generate(prompt="hello", request_id="req-1"):
outputs.append(output)
finally:
app.shutdown()
assert [output.request_output.payload for output in outputs] == [
"req-1-stage0",
"req-1-stage2-final",
]
assert "req-1" not in app.request_states
@pytest.mark.asyncio
async def test_async_omni_diffusion_only_yields_single_image_output(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(
stage_metadata=DIFFUSION_ONLY_META,
on_add_request=_enqueue_async_diffusion_only_output,
)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
outputs = []
async for output in app.generate(prompt="hello", request_id="req-1"):
outputs.append(output)
finally:
app.shutdown()
assert len(outputs) == 1
assert outputs[0].stage_id == 0
assert outputs[0].final_output_type == "image"
assert outputs[0].images == ["req-1-image"]
assert outputs[0].request_output.payload == "req-1-diffusion-final"
@pytest.mark.asyncio
async def test_async_omni_llm_diffusion_yields_text_stream_then_image(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(
stage_metadata=LLM_DIFFUSION_META,
on_add_request=_enqueue_async_llm_diffusion_outputs,
)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
outputs = []
async for output in app.generate(prompt="hello", request_id="req-1"):
outputs.append(output)
finally:
app.shutdown()
assert [output.stage_id for output in outputs] == [0, 0, 0, 1]
assert [output.final_output_type for output in outputs] == ["text", "text", "text", "image"]
assert [output.request_output.payload for output in outputs] == [
"req-1-text-0",
"req-1-text-1",
"req-1-text-2",
"req-1-image-final",
]
assert outputs[-1].images == ["req-1-image"]
assert "req-1" not in app.request_states
@pytest.mark.asyncio
async def test_async_omni_abort_forwards_to_engine(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
app.request_states["req-1"] = object()
await app.abort("req-1")
finally:
app.shutdown()
assert engine.aborted == [["req-1"]]
assert "req-1" not in app.request_states
@pytest.mark.asyncio
async def test_async_omni_propagates_fatal_error_context(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META, on_add_request=_enqueue_fatal_error_message)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
with pytest.raises(EngineDeadError, match="engine dead") as exc_info:
async for _ in app.generate(prompt="hello", request_id="req-1"):
pass
finally:
app.shutdown()
assert isinstance(exc_info.value, OmniEngineDeadError)
assert str(exc_info.value) == "engine dead"
assert getattr(exc_info.value, "error_stage_id") == 2
def test_omni_generate_py_generator_yields_final_outputs_for_each_request(monkeypatch: pytest.MonkeyPatch):
sampling_params = [SamplingParams(max_tokens=8) for _ in range(3)]
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
default_sampling_params_list=sampling_params,
on_add_request=_enqueue_omni_final_only_outputs,
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
outputs = list(app.generate(["p1", "p2"], py_generator=True, use_tqdm=False))
assert len(outputs) == 4
assert [output.stage_id for output in outputs] == [0, 2, 0, 2]
assert [output.request_output.payload for output in outputs] == [
f"{engine.submitted[0]['request_id']}-stage0-0",
f"{engine.submitted[0]['request_id']}-stage2-final",
f"{engine.submitted[1]['request_id']}-stage0-0",
f"{engine.submitted[1]['request_id']}-stage2-final",
]
assert engine.shutdown_called is True
def test_omni_generate_returns_list_when_not_using_generator(monkeypatch: pytest.MonkeyPatch):
sampling_params = [SamplingParams(max_tokens=8) for _ in range(3)]
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
default_sampling_params_list=sampling_params,
on_add_request=_enqueue_omni_final_only_outputs,
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
outputs = app.generate(["p1", "p2"], py_generator=False, use_tqdm=False)
finally:
app.shutdown()
assert isinstance(outputs, list)
assert len(outputs) == 4
assert [output.stage_id for output in outputs] == [0, 2, 0, 2]
def test_omni_generate_diffusion_only_yields_single_image_per_request(monkeypatch: pytest.MonkeyPatch):
sampling_params = [SamplingParams(max_tokens=8)]
engine = FakeAsyncOmniEngine(
stage_metadata=DIFFUSION_ONLY_META,
default_sampling_params_list=sampling_params,
on_add_request=_enqueue_async_diffusion_only_output,
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
outputs = list(app.generate(["p1", "p2"], py_generator=True, use_tqdm=False))
assert len(outputs) == 2
assert [output.stage_id for output in outputs] == [0, 0]
assert [output.final_output_type for output in outputs] == ["image", "image"]
assert [output.request_output.payload for output in outputs] == [
f"{engine.submitted[0]['request_id']}-diffusion-final",
f"{engine.submitted[1]['request_id']}-diffusion-final",
]
assert [output.images for output in outputs] == [
[f"{engine.submitted[0]['request_id']}-image"],
[f"{engine.submitted[1]['request_id']}-image"],
]
assert engine.shutdown_called is True
def test_omni_generate_llm_diffusion_yields_final_text_then_image_per_request(
monkeypatch: pytest.MonkeyPatch,
):
sampling_params = [SamplingParams(max_tokens=8) for _ in range(2)]
engine = FakeAsyncOmniEngine(
stage_metadata=LLM_DIFFUSION_META,
default_sampling_params_list=sampling_params,
on_add_request=_enqueue_omni_llm_diffusion_outputs,
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
outputs = list(app.generate(["p1", "p2"], py_generator=True, use_tqdm=False))
assert len(outputs) == 4
assert [output.stage_id for output in outputs] == [0, 1, 0, 1]
assert [output.final_output_type for output in outputs] == ["text", "image", "text", "image"]
assert [output.request_output.payload for output in outputs] == [
f"{engine.submitted[0]['request_id']}-text-0",
f"{engine.submitted[0]['request_id']}-image-final",
f"{engine.submitted[1]['request_id']}-text-0",
f"{engine.submitted[1]['request_id']}-image-final",
]
assert [output.images for output in outputs] == [
[],
[f"{engine.submitted[0]['request_id']}-image"],
[],
[f"{engine.submitted[1]['request_id']}-image"],
]
assert engine.submitted[0]["sampling_params_list"][0].output_kind == RequestOutputKind.FINAL_ONLY
assert engine.shutdown_called is True
def test_omni_abort_forwards_to_engine(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
app.request_states["req-1"] = object()
app.abort("req-1")
finally:
app.shutdown()
assert engine.aborted == [["req-1"]]
assert "req-1" not in app.request_states
def test_omni_forces_final_only_on_llm_stages(monkeypatch: pytest.MonkeyPatch):
sampling_params = [SamplingParams(max_tokens=8) for _ in range(3)]
original_diffusion_output_kind = sampling_params[2].output_kind
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
default_sampling_params_list=sampling_params,
on_add_request=_enqueue_omni_final_only_outputs,
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
outputs = list(app.generate(["p1"], py_generator=True, use_tqdm=False))
finally:
if not engine.shutdown_called:
app.shutdown()
submitted_params = engine.submitted[0]["sampling_params_list"]
assert submitted_params[0].output_kind == RequestOutputKind.FINAL_ONLY
assert submitted_params[1].output_kind == RequestOutputKind.FINAL_ONLY
assert submitted_params[2].output_kind == original_diffusion_output_kind
assert len(outputs) == 2
def test_fatal_error_raises_engine_dead():
base = _make_base()
msg = {"type": "error", "error": "orchestrator crashed", "fatal": True}
with pytest.raises(EngineDeadError, match="orchestrator crashed"):
base._handle_output_message(msg)
def test_non_fatal_error_raises_runtime():
base = _make_base()
msg = {"type": "error", "error": "something wrong"}
with pytest.raises(RuntimeError, match="something wrong"):
base._handle_output_message(msg)
def test_async_omni_errored_property_alive():
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
is_alive=lambda: True,
stage_clients=[SimpleNamespace(is_comprehension=False)],
)
assert omni.errored is False
def test_async_omni_errored_property_dead_engine():
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
is_alive=lambda: False,
stage_clients=[SimpleNamespace(is_comprehension=False)],
)
assert omni.errored is True
def test_async_omni_errored_property_dead_stage():
omni = object.__new__(AsyncOmni)
dead_stage = SimpleNamespace(is_comprehension=False, _engine_dead=True)
omni.engine = SimpleNamespace(
is_alive=lambda: True,
stage_clients=[dead_stage],
)
assert omni.errored is True
def _enqueue_stage_error(
engine: FakeAsyncOmniEngine,
msg,
*,
error_text: str,
kill_engine: bool = False,
):
"""Enqueue a stage error output, optionally killing the engine."""
if kill_engine:
engine._alive = False
engine.output_q.put_nowait(
{
"type": "output",
"request_id": msg["request_id"],
"stage_id": 0,
"engine_outputs": SimpleNamespace(
payload="",
finished=True,
images=[],
stage_durations={},
error=error_text,
),
"finished": False,
}
)
@pytest.mark.asyncio
async def test_async_omni_propagates_engine_dead_error(monkeypatch: pytest.MonkeyPatch):
"""When the engine is dead and an error output arrives, ``generate()``
must raise ``EngineDeadError`` (not plain ``RuntimeError``)."""
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
on_add_request=lambda eng, msg: _enqueue_stage_error(eng, msg, error_text="worker OOM", kill_engine=True),
)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
with pytest.raises(EngineDeadError, match="worker OOM"):
async for _ in app.generate(prompt="hello", request_id="req-dead"):
pass
finally:
app.shutdown()
@pytest.mark.asyncio
async def test_async_omni_propagates_engine_generate_error(monkeypatch: pytest.MonkeyPatch):
"""When the engine is alive but a stage error occurs, ``generate()``
must raise ``EngineGenerateError`` (recoverable, not ``EngineDeadError``)."""
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
on_add_request=lambda eng, msg: _enqueue_stage_error(eng, msg, error_text="diffusion step failed"),
)
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
with pytest.raises(EngineGenerateError):
async for _ in app.generate(prompt="hello", request_id="req-recover"):
pass
finally:
app.shutdown()
# ───────── OmniBase.check_health() aggregation ─────────
def test_check_health_passes_when_all_healthy():
base = _make_base()
healthy_stage = MagicMock()
healthy_stage.check_health = MagicMock()
base.engine.is_alive.return_value = True
base.engine.stage_clients = [healthy_stage]
base.check_health() # should not raise
def test_check_health_raises_when_stage_dead():
base = _make_base()
dead_stage = MagicMock()
dead_stage.check_health = MagicMock(side_effect=EngineDeadError("Stage-1 dead"))
base.engine.is_alive.return_value = True
base.engine.stage_clients = [dead_stage]
with pytest.raises(EngineDeadError, match="Stage-1 dead"):
base.check_health()
def test_check_health_raises_when_orchestrator_dead():
base = _make_base()
base.engine.is_alive.return_value = False
base.engine.stage_clients = []
with pytest.raises(EngineDeadError, match="not alive"):
base.check_health()
# ───────── OmniBase.errored property ─────────
def test_omni_base_errored_false_when_alive():
base = _make_base()
base.engine.is_alive.return_value = True
base.engine.stage_clients = [SimpleNamespace()]
assert base.errored is False
def test_omni_base_errored_true_when_orchestrator_dead():
base = _make_base()
base.engine.is_alive.return_value = False
base.engine.stage_clients = []
assert base.errored is True
def test_omni_base_errored_true_when_stage_engine_dead():
base = _make_base()
base.engine.is_alive.return_value = True
dead_stage = SimpleNamespace(_engine_dead=True)
base.engine.stage_clients = [dead_stage]
assert base.errored is True
def test_omni_base_errored_true_when_stage_resources_engine_dead():
base = _make_base()
base.engine.is_alive.return_value = True
dead_stage = SimpleNamespace(resources=SimpleNamespace(engine_dead=True))
base.engine.stage_clients = [dead_stage]
assert base.errored is True
# ───────── Omni (sync) EngineDeadError / EngineGenerateError ─────────
def test_omni_propagates_engine_dead_error(monkeypatch: pytest.MonkeyPatch):
"""When the engine is dead and a stage error output arrives,
``Omni.generate()`` must raise ``EngineDeadError``."""
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
on_add_request=lambda eng, msg: _enqueue_stage_error(eng, msg, error_text="worker OOM", kill_engine=True),
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
with pytest.raises(EngineDeadError, match="worker OOM"):
list(app.generate(["hello"], py_generator=False, use_tqdm=False))
finally:
app.shutdown()
def test_omni_propagates_engine_generate_error(monkeypatch: pytest.MonkeyPatch):
"""When the engine is alive but a stage error occurs,
``Omni.generate()`` must raise ``EngineGenerateError`` (recoverable)."""
engine = FakeAsyncOmniEngine(
stage_metadata=THREE_STAGE_META,
on_add_request=lambda eng, msg: _enqueue_stage_error(eng, msg, error_text="diffusion step failed"),
)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
with pytest.raises(EngineGenerateError):
list(app.generate(["hello"], py_generator=False, use_tqdm=False))
finally:
app.shutdown()
def test_omni_errored_property_alive(monkeypatch: pytest.MonkeyPatch):
"""Omni.errored (inherited from OmniBase) returns False when healthy."""
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
assert app.errored is False
finally:
app.shutdown()
def test_omni_errored_property_dead_engine(monkeypatch: pytest.MonkeyPatch):
"""Omni.errored returns True when the orchestrator is dead."""
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
engine._alive = False
assert app.errored is True
finally:
app.shutdown()
def test_omni_errored_property_dead_stage(monkeypatch: pytest.MonkeyPatch):
"""Omni.errored returns True when a stage client is marked dead."""
engine = FakeAsyncOmniEngine(stage_metadata=THREE_STAGE_META)
_patch_engine(monkeypatch, engine)
app = Omni("dummy-model")
try:
engine.stage_clients[0]._engine_dead = True
assert app.errored is True
finally:
app.shutdown()