[Rebase] Align vLLM-Omni with vLLM v0.31.0 (#8459)

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
This commit is contained in:
Zhou Taichang authored and GitHub committed 2026-10-05 14:11:01 +08:00
1 parent 1f47fa2f29
commit 0b0d2d7009
141 files changed
+2885 -721

No files matched your search

+34
View File
@@ -20,7 +20,11 @@ import pytest
from omegaconf import OmegaConf
from pydantic import ValidationError
from transformers import PretrainedConfig, Qwen3OmniMoeConfig
from vllm.config import VllmConfig
from vllm.engine.arg_utils import EngineArgs
from vllm.exceptions import VLLMValidationError
from vllm.v1.engine.input_processor import InputProcessor
from vllm.v1.worker.gpu.sample.logit_bias import LogitBiasState
from tests.helpers.mock import patch_hf_snapshot_download
from vllm_omni.config.model import OmniModelConfig
@@ -32,6 +36,36 @@ from vllm_omni.worker.omni_connector_model_runner_mixin import OmniConnectorMode
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@pytest.mark.parametrize("use_v2", [False, True])
def test_stage_runner_selects_matching_input_processor_validator(monkeypatch, use_v2):
from vllm.sampling_params import SamplingParams
from vllm_omni.model_executor.models.audex.cfg import AudexCFGLogitsProcessor
# Upstream's process-wide preference must not override a stage's deploy choice.
monkeypatch.setattr("vllm.envs.VLLM_USE_V2_MODEL_RUNNER", not use_v2)
config = VllmConfig()
model_config = object.__new__(OmniModelConfig)
model_config.use_v2_model_runner = use_v2
model_config.runner_type = "generate"
model_config.logits_processors = [LogitBiasState if use_v2 else AudexCFGLogitsProcessor]
config.model_config = model_config
assert config.use_v2_model_runner is use_v2
processor = object.__new__(InputProcessor)
processor.vllm_config, processor.model_config = config, model_config
validator = processor._build_logits_processors_params_validator()
validator(SamplingParams(extra_args={"cfg_role": "cond", "cfg_scale": 1.5}))
if not use_v2:
with pytest.raises(VLLMValidationError, match="cfg_role"):
validator(SamplingParams(extra_args={"cfg_role": "invalid"}))
@pytest.mark.parametrize("use_v2", [False, True])
def test_plain_vllm_config_retains_upstream_runner_preference(monkeypatch, use_v2):
monkeypatch.setattr("vllm.envs.VLLM_USE_V2_MODEL_RUNNER", use_v2)
assert VllmConfig().use_v2_model_runner is use_v2
def test_sync_config_is_omni():
"""Ensure create_model_config gives the right type."""
cfg = OmniEngineArgs().create_model_config()
@@ -8,6 +8,7 @@ from __future__ import annotations
from dataclasses import fields
import pytest
from vllm.config import AuxOutputConfig
from vllm_omni.engine.arg_utils import (
SHARED_FIELDS,
@@ -49,3 +50,26 @@ def test_internal_blacklist_keys_derived_from_orchestrator():
"""
blacklist = internal_blacklist_keys()
assert blacklist == orchestrator_field_names() - SHARED_FIELDS
@pytest.mark.parametrize(
"aux_args",
[
{"enable_return_routed_experts": True},
{"aux_output_config": {"enable_return_routed_experts": True}},
{"aux_output_config": AuxOutputConfig(enable_return_routed_experts=True)},
],
)
@pytest.mark.parametrize("use_v2_model_runner", [False, True])
@pytest.mark.parametrize("is_npu", [False, True])
def test_aux_outputs_require_a_supported_omni_stage_runner(aux_args, use_v2_model_runner, is_npu, monkeypatch):
# Upstream runner selection reads this environment variable; Omni workers
# select their runner from the separate per-stage flag instead.
monkeypatch.setenv("VLLM_USE_V2_MODEL_RUNNER", "1")
monkeypatch.setattr("vllm_omni.engine.arg_utils.current_omni_platform.is_npu", lambda: is_npu)
if use_v2_model_runner or is_npu:
args = OmniEngineArgs(model="unused", use_v2_model_runner=use_v2_model_runner, **aux_args)
assert args.aux_output_config.enabled
else:
with pytest.raises(ValueError, match="use_v2_model_runner=True for this Omni stage"):
OmniEngineArgs(model="unused", **aux_args)
@@ -168,6 +168,37 @@ def test_build_add_request_message_preserves_additional_information(mocker: Mock
output_processor.add_request.assert_not_called()
def test_build_add_request_message_preserves_kv_hints_after_omni_payload_upgrade(mocker):
from vllm.v1.engine.input_processor import InputProcessor
from vllm.v1.kv_hints import KvHintsEnvelope
engine = object.__new__(AsyncOmniEngine)
params = SamplingParams(max_tokens=8)
engine.default_sampling_params_list = [params]
engine.stage_metadata = [StageRuntimeInfo(final_output=False, final_output_type=None, stage_type="llm")]
engine.supported_tasks = ("generate",)
processor = mocker.create_autospec(InputProcessor, instance=True)
hints = KvHintsEnvelope("1", "hint-message", [])
def process(**kwargs):
request = _make_engine_core_request()
request.kv_hints = kwargs["kv_hints"]
return request
processor.process_inputs.side_effect = process
engine.input_processor = processor
msg = engine._build_add_request_message(
request_id="req-1",
prompt={"prompt_token_ids": [1], "additional_information": {"speaker": "voice"}},
sampling_params_list=[params],
kv_hints=hints,
)
assert processor.process_inputs.call_args.kwargs["kv_hints"] is hints
assert isinstance(msg.prompt, OmniEngineCoreRequest)
assert msg.prompt.kv_hints is hints
assert msg.prompt.additional_information is not None
def test_build_add_request_message_injects_global_id_before_prompt_transform(mocker: MockerFixture):
engine = object.__new__(AsyncOmniEngine)
params = SamplingParams(max_tokens=8)
@@ -296,11 +296,21 @@ def test_stage_engine_core_client_module_reload_keeps_forward_refs_deferred():
"""Regression test for forward references in make_async_mp_client."""
import vllm_omni.engine.stage_engine_core_client as client_mod
importlib.reload(client_mod)
# Snapshot the module namespace so a reload cannot strand other test
# modules that captured the pre-reload class objects (isinstance checks
# against the stale class would then fail). ``importlib.reload`` mutates
# the module dict in place; restoring it leaves the runtime exactly as it
# was before this test ran.
saved = dict(client_mod.__dict__)
try:
importlib.reload(client_mod)
assert client_mod.StageEngineCoreClientBase.make_async_mp_client.__annotations__["return"] == (
"StageEngineCoreClient | DPLBStageEngineCoreClient"
)
assert client_mod.StageEngineCoreClientBase.make_async_mp_client.__annotations__["return"] == (
"StageEngineCoreClient | DPLBStageEngineCoreClient"
)
finally:
client_mod.__dict__.clear()
client_mod.__dict__.update(saved)
def test_async_omni_engine_initialize_stages_passes_log_stats_and_client_config_to_runtime(monkeypatch):
+27 -1
View File
@@ -13,13 +13,16 @@ substitutable with the base class — including base-style calls that pass
import inspect
import msgspec
import numpy as np
import pytest
import torch
from vllm.sampling_params import SamplingParams
from vllm.v1.engine import EngineCoreRequest
from vllm.v1.kv_hints import KvHintAction, KvHintsEnvelope
from vllm.v1.request import Request
from vllm_omni.engine import PromptEmbedsPayload
from vllm_omni.engine import OmniEngineCoreRequest, PromptEmbedsPayload
from vllm_omni.request import OmniRequest, OmniStreamingUpdate
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -111,3 +114,26 @@ def test_model_intermediate_buffer_round_trips_to_streaming_update():
assert update is not None
assert request.model_intermediate_buffer is buffer
assert update.model_intermediate_buffer is buffer
def test_kv_hints_survive_omni_upgrade_wire_transport_and_scheduler_conversion():
hints = KvHintsEnvelope("1", "hint-message", [KvHintAction("a", "prefetch", "1", {"blocks": [7]})])
core = EngineCoreRequest(
request_id="hint-request",
prompt_token_ids=[1, 2],
mm_features=None,
sampling_params=SamplingParams(max_tokens=2),
pooling_params=None,
arrival_time=0.0,
lora_request=None,
cache_salt=None,
data_parallel_rank=None,
session_id="session",
kv_hints=hints,
)
upgraded = OmniEngineCoreRequest.from_request(core)
decoded = msgspec.msgpack.decode(msgspec.msgpack.encode(upgraded), type=OmniEngineCoreRequest)
scheduled = OmniRequest.from_engine_core_request(decoded, block_hasher=None)
assert decoded.session_id == "session"
assert scheduled.kv_hints == hints
assert scheduled.session_id == "session"
+13
View File
@@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Tests for OmniRequestOutput class."""
import numpy as np
import pytest
from PIL import Image
from vllm.outputs import CompletionOutput, RequestOutput
@@ -157,6 +158,7 @@ class TestMsgpackRoundTrip:
)
source = _make_text_request_output(text="round trip")
source.prompt_token_id_logprobs = np.array([[-0.5, -1.5], [-0.2, -2.2]], dtype=np.float32)
out = OmniRequestOutput.from_stage_output(
source,
request_id="rt-1",
@@ -178,6 +180,7 @@ class TestMsgpackRoundTrip:
assert decoded.outputs[0].text == "round trip"
assert decoded.prompt == "test prompt"
assert decoded.prompt_token_ids == [1, 2, 3]
np.testing.assert_array_equal(decoded.prompt_token_id_logprobs, source.prompt_token_id_logprobs)
def test_round_trip_diffusion_output(self):
"""A diffusion output round-trips without data loss."""
@@ -302,6 +305,16 @@ class TestRequestOutputFieldParity:
assert omni.num_cached_tokens is None
assert omni.num_cache_creation_tokens is None
def test_fixed_token_scores_survive_stage_output_wrapping(self) -> None:
source = _make_text_request_output()
source.prompt_token_id_logprobs = np.array([[-0.5, -1.5], [-0.2, -2.2]], dtype=np.float32)
omni = OmniRequestOutput.from_stage_output(source, stage_id=0)
forwarded = OmniRequestOutput.from_stage_output(omni, stage_id=1)
np.testing.assert_array_equal(forwarded.prompt_token_id_logprobs, source.prompt_token_id_logprobs)
assert OmniRequestOutput(request_id="unscored").prompt_token_id_logprobs is None
def test_transfer_param_fields_are_copied_from_the_stage_output(self) -> None:
"""Declaring the fields is not enough — they must also be copied.
@@ -1282,6 +1282,7 @@ async def test_cache_reset_rpc_failure_is_nonfatal_and_visits_all_replicas(metho
[
("reset_mm_cache", {}, True),
("sleep", {}, True),
("release_kv_cache_memory", {}, True),
("pause_scheduler", {"clear_cache": True}, True),
("pause_scheduler", {"clear_cache": False}, False),
("reset_prefix_cache", {}, False),
@@ -7,8 +7,9 @@ from unittest.mock import MagicMock
import pytest
import torch
from vllm.sampling_params import RequestOutputKind
from vllm.v1.engine import FinishReason
from vllm.sampling_params import RequestOutputKind, SamplingParams
from vllm.v1.engine import EngineCoreRequest, FinishReason
from vllm.v1.engine.logprobs import LogprobsProcessor
from vllm_omni.engine import OmniEngineCoreOutput
from vllm_omni.engine.output_modality import OutputModality
@@ -16,6 +17,7 @@ from vllm_omni.engine.output_processor import (
MultimodalOutputProcessor,
OmniRequestState,
)
from vllm_omni.outputs import OmniRequestOutput
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -109,6 +111,37 @@ def test_text_tokens_are_detokenized_when_mrv2_ar_output_has_pooling_payload():
assert not state.mm_accumulated.is_empty
def test_fixed_token_scores_survive_output_processing_and_stage_wrapping():
processor, state = _make_processor(OutputModality.TEXT, _Detokenizer())
request = EngineCoreRequest(
request_id="r",
prompt_token_ids=[1, 2, 3],
mm_features=None,
sampling_params=SamplingParams(prompt_logprob_token_ids=[4, 5]),
pooling_params=None,
arrival_time=0.0,
lora_request=None,
cache_salt=None,
data_parallel_rank=None,
)
state.logprobs_processor = LogprobsProcessor.from_new_request(None, request)
scores = torch.tensor([[-0.5, -1.5], [-0.2, -2.2]])
processed = processor.process_outputs(
[
OmniEngineCoreOutput(
request_id="r",
new_token_ids=[42],
prompt_token_id_logprobs=scores,
finish_reason=FinishReason.STOP,
)
]
)
(raw_output,) = processed.request_outputs
final_output = OmniRequestOutput.from_stage_output(raw_output, stage_id=0)
torch.testing.assert_close(torch.from_numpy(final_output.prompt_token_id_logprobs), scores)
@pytest.mark.parametrize("streaming", [False, True])
def test_generation_stage_mm_delivered_only_with_terminal(streaming):
# Non-terminal chunks accumulate; the terminal output carries the audio.
-1
View File
@@ -97,7 +97,6 @@ _OMNI_ONLY_LLM_STAGE_ENGINE_FIELDS = frozenset(
"enable_multithread_weight_load",
"env",
"has_sampling_extra_args",
"log_level",
"log_stats",
"model_arch",
"model_subdir",
+52 -1
View File
@@ -5,11 +5,19 @@
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import patch
import pytest
from vllm import SamplingParams
from vllm.v1.engine.core_client import AsyncMPClient, DPLBAsyncMPClient
from vllm.v1.engine.exceptions import EngineDeadError
from vllm_omni.engine.stage_engine_core_client import StageEngineCoreClient
from vllm_omni.engine.stage_engine_core_client import (
DPLBStageEngineCoreClient,
StageEngineCoreClient,
StageEngineCoreClientBase,
)
from vllm_omni.engine.stage_init_utils import StageMetadata
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -30,3 +38,46 @@ def test_check_health_raises_when_resources_engine_dead():
client = _make_client(engine_dead=True)
with pytest.raises(EngineDeadError, match="engine core is dead"):
client.check_health()
@pytest.mark.parametrize("dp_size, external_lb", [(1, False), (2, False), (2, True)])
@pytest.mark.parametrize("renderer", [None, object()])
def test_stage_client_forwards_renderer_to_the_actual_upstream_constructor(dp_size, external_lb, renderer):
config = SimpleNamespace(
model_config=None,
parallel_config=SimpleNamespace(data_parallel_size=dp_size, data_parallel_external_lb=external_lb),
)
metadata = StageMetadata(
stage_id=0,
stage_type="llm",
engine_output_type="text",
is_comprehension=True,
requires_multimodal_data=False,
engine_input_source=[],
final_output=True,
final_output_type="text",
default_sampling_params=SamplingParams(),
custom_process_input_func=None,
model_stage=None,
runtime_cfg=None,
)
internal_lb = dp_size > 1 and not external_lb
parent = DPLBAsyncMPClient if internal_lb else AsyncMPClient
# Keep Omni construction/factory routing real; intercept only upstream's
# subprocess/socket boundary, using its current signature via autospec.
with patch.object(parent, "__init__", autospec=True, return_value=None) as init:
addresses = {"input_address": "tcp://192.0.2.1:1"}
client = StageEngineCoreClientBase.make_async_mp_client(
config, object, metadata=metadata, client_addresses=addresses, renderer=renderer
)
assert isinstance(client, DPLBStageEngineCoreClient if internal_lb else StageEngineCoreClient)
init.assert_called_once_with(
client,
config,
object,
log_stats=False,
client_addresses=addresses,
client_count=1,
client_index=0,
renderer=renderer,
)
@@ -83,6 +83,27 @@ def test_collective_rpc_control_method_reraises_worker_error():
asyncio.run(run())
@pytest.mark.cpu
def test_kv_memory_release_uses_engine_core_and_propagates_pause_errors():
async def run() -> None:
release = AsyncMock(return_value=None)
pool, client = _make_pool(release_kv_cache_memory_async=release)
await pool.collective_rpc(0, "release_kv_cache_memory", timeout=2.0)
release.assert_awaited_once_with()
client.collective_rpc_async.assert_not_awaited()
release.side_effect = RuntimeError("requires a completed pause first")
with pytest.raises(RuntimeError, match="completed pause"):
await pool.collective_rpc(0, "release_kv_cache_memory")
del client.release_kv_cache_memory_async
result = await pool.collective_rpc(0, "release_kv_cache_memory")
assert result["supported"] is False
client.collective_rpc_async.assert_not_awaited()
asyncio.run(run())
@pytest.mark.cpu
def test_collective_rpc_reset_caches_use_control_helpers():
async def run() -> None: