Merge branch 'main' into qwen-image-2.1

Signed-off-by: Isotr0py <Isotr0py@outlook.com>
This commit is contained in:
Isotr0py authored and GitHub committed 2026-09-22 10:11:12 +08:00
commit 8e0695f362
51 files changed
+2189 -70

No files matched your search

@@ -505,6 +505,75 @@ class TestSpeechAPI:
assert voice_info["file_size"] == len(audio_content)
response = client.delete("/v1/audio/voices/test_voice")
def test_upload_voice_rejects_builtin_name(self, client):
"""A name colliding with a built-in or precomputed voice is rejected."""
handler = client.app.state.openai_serving_speech
handler._adapter.capabilities.supported_speakers = {"vivian"}
handler._adapter.capabilities.precomputed_speakers = {"ono_anna": {}}
files = {"audio_sample": ("test.wav", b"fake audio content" * 1000, "audio/wav")}
for reserved in ("Vivian", "ono_anna"):
response = client.post("/v1/audio/voices", files=files, data={"consent": "c1", "name": reserved})
assert response.status_code == 400
assert "reserved" in response.json()["detail"]
assert not handler.uploaded_speakers
def test_upload_voice_overwrites_same_name_by_default(self, client):
"""Default policy: re-registering an uploaded name replaces it in place."""
files = {"audio_sample": ("test.wav", b"first take", "audio/wav")}
data = {"consent": "c1", "name": "test_voice_ow"}
assert client.post("/v1/audio/voices", files=files, data=data).status_code == 200
files = {"audio_sample": ("test.wav", b"second take, longer content", "audio/wav")}
response = client.post("/v1/audio/voices", files=files, data=data)
assert response.status_code == 200
assert response.json()["voice"]["file_size"] == len(b"second take, longer content")
client.delete("/v1/audio/voices/test_voice_ow")
def test_upload_voice_immutable_policy_requires_delete(self, client):
"""VLLM_OMNI_SPEAKER_REGISTRATION_POLICY=immutable rejects duplicates until deleted."""
handler = client.app.state.openai_serving_speech
handler._registration_policy = "immutable"
try:
files = {"audio_sample": ("test.wav", b"fake audio content" * 1000, "audio/wav")}
data = {"consent": "c1", "name": "test_voice_imm"}
assert client.post("/v1/audio/voices", files=files, data=data).status_code == 200
response = client.post("/v1/audio/voices", files=files, data=data)
assert response.status_code == 400
assert "immutable" in response.json()["detail"]
assert client.delete("/v1/audio/voices/test_voice_imm").status_code == 200
assert client.post("/v1/audio/voices", files=files, data=data).status_code == 200
finally:
handler._registration_policy = "overwrite"
client.delete("/v1/audio/voices/test_voice_imm")
def test_invalid_registration_policy_fails_startup(self, mocker, monkeypatch, tmp_path):
"""An unknown VLLM_OMNI_SPEAKER_REGISTRATION_POLICY is a configuration error, not a silent fallback."""
monkeypatch.setenv("SPEAKER_SAMPLES_DIR", str(tmp_path))
monkeypatch.setenv("VLLM_OMNI_SPEAKER_REGISTRATION_POLICY", "append")
engine_client = mocker.MagicMock()
engine_client.default_sampling_params_list = [{}]
with pytest.raises(ValueError, match="VLLM_OMNI_SPEAKER_REGISTRATION_POLICY"):
OmniOpenAIServingSpeech(
engine_client=engine_client, models=mocker.MagicMock(), request_logger=mocker.MagicMock()
)
def test_restored_upload_shadowing_builtin_is_dropped(self, client):
"""A pre-guard upload restored under a built-in name must not keep shadowing it."""
handler = client.app.state.openai_serving_speech
handler._adapter.capabilities.supported_speakers = {"vivian"}
handler._adapter.capabilities.precomputed_speakers = {}
handler.uploaded_speakers["vivian"] = {"name": "vivian", "file_path": "/tmp/vivian.safetensors"}
handler.uploaded_speakers["keep_me"] = {"name": "keep_me", "file_path": "/tmp/keep_me.safetensors"}
handler._drop_shadowing_uploads()
assert "vivian" not in handler.uploaded_speakers
assert "keep_me" in handler.uploaded_speakers
handler.uploaded_speakers.pop("keep_me")
def test_upload_voice_with_ref_text(self, client, tmp_path):
"""Test voice upload with ref_text enables in-context cloning."""
audio_content = b"fake audio content" * 1000
@@ -33,9 +33,9 @@ def _make_omni(*, stage_types: list[str]) -> AsyncOmni:
omni._final_output_handler = lambda: None
omni.reset_mm_cache = AsyncMock()
stage_clients = [SimpleNamespace(stage_type=stage_type) for stage_type in stage_types]
stage_configs = [SimpleNamespace(stage_type=stage_type) for stage_type in stage_types]
omni.engine = SimpleNamespace(
stage_clients=stage_clients,
stage_configs=stage_configs,
stage_vllm_configs=[None] * len(stage_types),
collective_rpc_async=AsyncMock(return_value=[True]),
)
+31 -6
View File
@@ -19,7 +19,7 @@ from vllm.v1.engine.exceptions import EngineDeadError, EngineGenerateError
from vllm_omni.config.omni_config import VllmOmniConfig
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
from vllm_omni.config.stage_config import StageConfig
from vllm_omni.config.stage_config import StageConfig, StageType
from vllm_omni.engine.async_omni_engine import StageRuntimeInfo
from vllm_omni.engine.messages import ErrorMessage, OutputMessage
from vllm_omni.entrypoints.async_omni import AsyncOmni
@@ -107,7 +107,13 @@ class FakeAsyncOmniEngine:
self.stage_metadata = stage_metadata or [THREE_STAGE_META[-1]]
self.num_stages = len(self.stage_metadata)
self.stage_configs = [
StageConfig(stage_id=i, model_stage="dummy-model").to_omegaconf() for i in range(self.num_stages)
StageConfig(
stage_id=i,
model_stage="dummy-model",
stage_type=StageType(metadata.stage_type),
is_comprehension=getattr(metadata, "is_comprehension", False),
).to_omegaconf()
for i, metadata in enumerate(self.stage_metadata)
]
self.default_sampling_params_list = default_sampling_params_list or [
SamplingParams(max_tokens=8) for _ in range(self.num_stages)
@@ -481,7 +487,7 @@ def test_model_config_and_vllm_config_forward_from_comprehension_stage():
io_processor = SimpleNamespace(name="io-processor")
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_clients=[SimpleNamespace(is_comprehension=False), SimpleNamespace(is_comprehension=True)],
stage_configs=[SimpleNamespace(is_comprehension=False), SimpleNamespace(is_comprehension=True)],
stage_vllm_configs=[None, vllm_config],
)
omni.input_processor = input_processor
@@ -502,7 +508,7 @@ def test_openai_serving_models_can_consume_async_omni_compat_attrs():
io_processor = SimpleNamespace(name="io-processor")
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_clients=[SimpleNamespace(is_comprehension=True)],
stage_configs=[SimpleNamespace(is_comprehension=True)],
stage_vllm_configs=[vllm_config],
)
omni.input_processor = input_processor
@@ -524,10 +530,11 @@ def test_get_diffusion_od_config_returns_diffusion_stage_config():
diffusion_od_config = object()
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_configs=[SimpleNamespace(stage_type="llm"), SimpleNamespace(stage_type="diffusion")],
stage_clients=[
SimpleNamespace(stage_type="llm"),
SimpleNamespace(stage_type="diffusion", od_config=diffusion_od_config),
]
],
)
assert omni.get_diffusion_od_config() is diffusion_od_config
@@ -537,15 +544,33 @@ def test_get_diffusion_od_config_falls_back_to_inner_engine():
diffusion_od_config = object()
omni = object.__new__(AsyncOmni)
omni.engine = SimpleNamespace(
stage_configs=[SimpleNamespace(stage_type="llm"), SimpleNamespace(stage_type="diffusion")],
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_rejects_diffusion_list_prompt_without_stage_clients(
monkeypatch: pytest.MonkeyPatch,
):
engine = FakeAsyncOmniEngine(stage_metadata=DIFFUSION_ONLY_META)
engine.stage_clients = []
_patch_engine(monkeypatch, engine)
app = AsyncOmni("dummy-model")
try:
with pytest.raises(ValueError, match="single prompt per request"):
async for _ in app.generate(prompt=["prompt-1", "prompt-2"], request_id="req-1"):
pass
finally:
app.shutdown()
@pytest.mark.asyncio
async def test_async_omni_yields_only_final_stage_outputs(monkeypatch: pytest.MonkeyPatch):
engine = FakeAsyncOmniEngine(